REST API Design Guidelines
V 1.0.201208 Draft 5
Last Updated: 08/31/2012
1 简介
本文档旨在规范REST API的设计和开发。
1.1 REST API目标
REST API允许Newegg内部和外部开发人员通过编程方式访问Newegg系统的各种对象与资源。
REST API需最大限度地满足平台无关性。
2 REST基本原则
REST全称“Representational State Transfer”。REST是一组架构设计原则而不是规范。
1) 可寻址性(Addressability)
每个资源都至少有一个URI标识。每个URI标识只能代表唯一一个资源。
2) 无状态性(Statelessness)
服务器不应保存“应用状态”。相关状态由客户端自行保存。
3) 统一接口(Uniform Interface)
对所有资源的操作都采用一致的方式:使用GET,POST,PUT,DELETE等HTTP请求来浏览,添加,修改和删除资源。
4) 可连通性(Connectedness)
资源之间是彼此联系的。客户端的状态迁移是在服务端的指引下完成的,即服务端响应中附带相关链接,客服端根据该链接而不是预先约定的URI格式去请求相关资源。这避免了客户端和服务端的强耦合。
3 一般准则
1)不要假设你知道客户是谁
2)面向资源而不是对象或活动
3)Web服务应该是粗粒度的(Coarse Grained)。Web服务通常会映射到顶级域对象(Top Level Domain Objects)
4)每个API应专注一件事。API尽可能小,但也不是越小越好
5)不能影响已经存在的API
6)考虑API设计决策的性能后果。不好的设计影响性能
7)禁止任意改变实现
8)不要实现Service Contract中没有的功能特性
9)向外部客户隐藏内部商业实体。通过Web服务公开每一个持久对象不是一个好的做法。它暴露了内部应用程序设计,增加了冗余和未使用服务,并且使服务很难理解和使用
10)不要让实施细节“泄露”到API中,例如磁盘、传输格式等
11)显性边界:只能通过服务接口层访问
12)在服务和客户之间必须有共享词汇表
13)检测和管理重复请求(Idempotent)。确保重复请求不被处理
14)假设无效请求的存在
15)在发送和接收XML时,遵循“宽进严出”原则。在接收时,只校验真正需要Schema的最小集合
16)确保能处理不按顺序到达的消息
17)如果集合资源有可能有大量的数据返回,务必提供分页功能支持
18)避免需要异常处理的返回参数:返回0字节数组或空集合,不是 NULL
19)不要让客户端使用异常进行流程控制
20)容易学习和使用,甚至是在没有文档的情况下
21)不容易误使用
22)对于重用,需要好的设计和好的文档。
4 资源和资源建模(Resource & Resource Modeling)
资源是REST架构的基础,是系统中所有可用URI来定位的具体或抽象实体。所有的操作都是面向资源而不是对象或活动。设计REST API的第一步是设计资源模型。这个过程类似于对一个关系数据库系统的数据建模,或对一个面向对象系统的对象建模。
4.1 识别资源
资源模型识别和归类所有客户需要和服务进行交互的资源。
1) 个体资源
和集合资源相对而言。例如单一一个Customer,一张Sales Order,客户的一个Shipping Address等。每个个体资源都有唯一的ID。
2) 集合资源
包含0到多个个体资源。例如所有Customers,一个客户的所有Sales Orders等。通常将集合作为一个工厂,通过向集合提交一个HTTP POST请求来创建一个新成员。
3) 复合资源
由2种以上的资源构成。例如Customer资源可以包括Billing Address资源和Shipping Address资源。复合资源使得它们的呈现和别的资源有重叠,通常只用于查询。
4) 抽象资源
某些操作无法映射到普通的HTTP方法上,例如计算运费,校验信用卡等。这时将对应的处理函数抽象成一个资源,例如ShippingCalculator,CreditCardValidator。资源的呈现就是计算的结果。
5) Controller资源(事务)
为资源设计Controller可以将更新多个资源的操作作为原子操作处理,或者能够使客户触发一系列复杂的业务操作。例如:Void SO操作,假设需要1)更新Order资源,2)发送Email给客户,与其分别更新/order/{order#}资源和添加/order/{order#}/email资源,可以设计一个controller资源/order/cancellation/{order#}来处理Void SO 操作。
4.2 资源建模的基本原则
1) 从客户(使用者)的角度出发进行思考和设计
2) 数据库表或者对象模型和资源不一定是一一对应的
3) 资源粒度
- 根据资源表现层大小、性能考量、客户习惯和业界规范来决定资源粒度
- 将适合缓存、不经常变化或不变的数据同很少缓存、经常变化的数据分开
4) 关于Tunneling
当客户用同一URI来进行不同的操作,或导致所谓的“Tunneling”。Tunneling降低了协议级别的可见性,因为请求的可见部分如URI,HTTP方法,HTTP头和媒体类型并没有无歧义地描述操作。因此需要尽量避免Tunneling。
5 URI设计[1]
标识每个资源的URI必须具有唯一性。
5.1 有效的URL[2]
5.1.1 URL长度
HTTP协议规范[3]没有对URL长度进行限制。但特定的浏览器及服务器可能会对它有所限制。IE对URL长度的限制是2083字符[4]。其他浏览器(Firefox、Safari等)理论上对URL长度没有限制。
服务端可能返回的错误: 414 (Request-URI Too Long)
5.1.2 URL合法字符
1) URL必须符合W3 Uniform Resource Identifier语法规范。这意味着URL中的字符只能是ASCII字符集的一个特殊子集。
集合 |
字符 |
URL中用途 |
字母数字 |
a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 |
文本, 协议 (http), 端口 (8080)等. |
非保留字符 |
- _ . ~ |
文本 |
保留字符 |
! * ' ( ) ; : @ & = + $ , / ? % # [ ] |
控制字符, 文本 |
2) URL中的特殊字符(例如汉字)和用于文本的保留字符(例如“?”)必须经过URL编码(URL Encoding)。
3) 根据RFC3986,空格会被编码成 %20;但如果使用application/x-www-form-urlencoded媒体类型(HTTP form元素使用[5]),空格会被编码成 +。服务端需要正确地识别编码方式。
4) RFC3986定义URL是大小写敏感的,但协议、域名和参数部分除外。
5) 必须将任何用户的URL输入当作文本对待。
5.2 URI设计规定
1) 无歧义识别目标资源
2) 模块化。Web服务的资源和方法可能增长很快。使用Domain和sub domain对资源进行分组或分隔。只使用规定的domain和sub domain名字。
3) 在设计阶段就要规划好URI结构:{host: port}/{ domain}/{version#}/{sub domain}/{resource}
4) URL长度不超过2000字符(包括协议、域名和端口部分)
5) URL主干部分(不包括参数)全部小写;参数名称第一个字母小写
6) 资源全部使用单数形式。例如查询order集合,使用/order而不是/orders
7) 使用连字符 - ,不使用下划线 _
8) 使用 / 表示资源的层次关系。例如:GET /customer/{customer#}。
9) URL的末尾不添加 / 字符
10) 使用 ,或 ;表示平行资源。例如:POST /order/{order1},{order2} : 同时处理{order1}和{order2}。
11) URI标识的是资源而不是操作,URI中的资源尽量只有名词形式。例如,从一个账户转钱到另一个账户,不是使用动词transfer,而是把之看成是一次交易记录,使用名词形式transaction。例如:
Request POST /transactions HTTP/1.1 Host: <snip, and all other headers> from=1&to=2&amount=500.00 Response HTTP/1.1 201 OK Date: Sun, 3 Jul 2011 23:59:59 GMT Content-Type: application/json Content-Length: 12345 Location: http://foo.com/transactions/1 [6] {"transaction":{"id":1,"uri":"/transactions/1","type":"transfer"}} |
12) URI对客户是不透明(opaque)的,即客户不需要了解URL的结构以自行拼接URI。可将实际的URI包含在服务响应中。这样做的坏处是使输出变大,但可以很容易将客户引向新的URI,从而降低客户端和服务端的耦合。
13) URI尽量不要改变。如果必须改变,当客户访问旧的URI使用301(Move Permanently)将客户重新引导到新的URI;或者在指定的时间后,返回410(Gone)或者404(Not Found)。例如:
Request GET /order/12345678 HTTP/1.1 Host: <neweggcentral-rest> Accept: application/xml; charset=UTF-8 Response #1 HTTP/1.1 301 Move Permanently Location: http:// neweggcentral-rest2/order/12345678 Response #2 HTTP/1.1 410 Gone Content-Type:application/xml; charset=UTF-8 Expires: Sat, 01 Jan 2013 00:00:00 GMT |
6 统一接口(Uniform Interfaces)
6.1 HTTP方法
在HTTP协议中定义了8种方法[7]。REST一般只使用有限的HTTP操作集合,包括HTTP GET, PUT, DELETE和POST。
在处理HTTP方法时,RFC 2616规范定义了两个重要概念:”Safe”和”Idempotent”(幂等)。”Safe”指方法不能修改资源的状态;”Idempotent”指将一个请求发送多次和发送一次的结果是一样的。
下表列出了用于RESTful服务的4种HTTP操作和它们的基本语义:
方法 |
Safe |
Idempotent |
描述 |
GET |
Y |
Y |
根据URL获取资源的一个呈现。 GET请求永远不应导致资源状态的改变。 |
PUT |
N |
Y |
更新资源(如果指定资源不存在,在允许的条件下创建新资源) |
DELETE |
N |
Y |
删除资源 |
POST |
N |
N |
提交数据到服务器,对指定资源进行处理。用于创建新资源或者向已有资源添加数据。 |
通过HTTP Allow头可以知道资源支持哪些HTTP方法。例如:
Allow: GET, PUT, DELETE
6.2 关于统一接口的规定
1) 支持GET, POST, PUT, DELETE四种HTTP方法。使用GET查询资源;POST创建资源;PUT更新资源;DELETE删除资源
2) 如果服务器、防火墙或网络不支持PUT和DELETE操作,应允许使用method参数,通过POST方法执行相应操作。例如:
POST https://graph.facebook.com/COMMENT_ID?method=delete.
7 内容协商(Content Negotiation)
REST框架下资源呈现和资源本身是有区别的。资源具体呈现出来的形式,叫做它的"表现形式"(representation)。各种表现形式由已定义的媒体类型、字符集和编码的字节流构成。一个资源可以没有,或者有一个,或者有多个表现形式。如果有多个表现形式存在,则称该资源是可协商的(negotiable)。
7.1 代理驱动 vs. 服务驱动(Agent-Driven vs. Server-Driven Negotiation)
服务驱动的内容协商使用HTTP请求头来决定响应变体。服务驱动的局限:
1) 内容协商不包括货币单位、距离单位、日期格式等区域设置。
2) 有时候由于复杂的本地化需求,可能需要为不同的区域维护不同的资源。
3) Web浏览器普遍会为Accept头设置一个范围很广的媒体类型选项,使得浏览器难以通过内容协商机制来呈现资源。
代理驱动[8]的内容协商是对每种响应变体(资源呈现)都使用不同的URI。虽然可以为所有的Accept-*头实施代理协商,但最常使用的是媒体类型和语言。代理协商普遍使用的方法包括:
方法 |
示例 |
通过查询参数 |
/order/status/12345678? format=json /order/status/12345678? format=xml |
通过子域名 |
en.wikipedia.org (显示英语) de.wikipedia.org(显示德语) |
7.2 用于内容协商的HTTP头
客户端可通过HTTP头Accept-*将它的偏好和处理能力传递给服务端,包括表现形式(representation)、语言偏好、字符集、对压缩的支持等。
HTTP请求头(Accept-*[9]) |
描述 |
Accept |
设置可接受的媒体格式。可指定多种媒体格式,媒体格式越具体优先级越高。另外,通过在每种媒体格式后添加q参数[10]来设置相对偏好。q=0表示不接受,q=1表示最大偏好,q参数缺省默认为q=1。 |
Accept-Charset |
设置可接受的字符集。例如: Accept-Charset: iso-8859-5, unicode-1-1;q=0.8 |
Accept-Encoding |
设置可接受的数据压缩方式。例如: Accept-Encoding: compress, gzip, deflate |
Accept-Language |
设置可接受的语言。例如: Accept-Language: da,en-gb;q=0.8,en;q=0.7 |
服务响应中相关的HTTP头包括:
HTTP响应头(Content-*) |
描述 |
示例 |
Content-Type |
媒体类型(或称media-type, MIME type),可以有charset等参数。例如:如果是application/xml或者以+xml结尾,则可以用XML解析器处理消息。JSON媒体类型application/json不指定charset参数,默认使用UTF-8[11]。 |
application/xml;charset=UFT-8 application/json |
Content-Length |
内容长度(bytes) |
|
Content-Language |
本地化语言。使用两个字母的RFC5646语言标签。“-”+ 两个字母的国家代码是可选的。 |
en-US |
Content-MD5 |
内容的MD5摘要,用于一致性检查(注:TCP用户使用传输级别的校验和(checksum)进行一致性检查)。接收方可以用这个头校验数据的完整性,特别是在不稳定的网络上发送或接受大量数据时。(由于可能被篡改,不能用于安全控制目的) |
bbdc7bbb8ea589666e33ac922c0f83 |
Content-Encoding |
gzip, compress或deflate编码。如果是gzip,接收方在解析消息前必须先对消息进行解压。 客户端可用Accept-Encoding来设置对Content-Encoding的偏好。避免在HTTP请求中使用这个头,除非服务端支持。 |
gzip |
Last-Modified |
服务端最近一次修改资源或呈现的时间 |
Sun, 29 Mar 2009 04:51:38 GMT |
Accept-*通常指定的是一个范围 (Range) ; Content-*通常返回的是一个特定的值。
例如,如果客户:
- 偏好French但English也能接受
- 只能处理application/atom+xml和application/xml媒体类型
- 知道怎么处理使用gzip压缩的资源呈现
Request Accept: application/atom+xml;q=1.0, application/xml;q=0.6, */*;q=0.0 Accept-Language: fr;q=1.0, en;q=0.5 Accept-Encoding: gzip Response Content-Type: application/atom+xml; charset=UTF-8 Content-Language: fr Content-Encoding: gzip Vary: Accept-Encoding |
使用标准的HTTP头来使用/产生不同的MIME类型,在未来可以在不用改变服务接口的情况下很容易地支持新的内容类型。
7.3 资源呈现的请求和响应方式
有2种方式来请求一个资源的呈现类型:
类型 |
描述 |
服务响应优先级 |
使用查询参数 (代理驱动) |
可以使用一个特殊的查询参数forma:{JSON, XML, JSV}。例如:/invoice/11111?format=JSON |
高 |
HTTP请求头 (服务驱动) |
Accept: text/xml Accept: application/json |
低 |
如果在请求中以上两种方式同时指定,则按优先级最高的格式进行响应。
在服务响应中用Content-Type头表示。Content-Type使用MIME类型[12][13]定义。
7.4 资源呈现类型(资源的多重表述)
需要支持的类型有:
类型 |
请求方式 |
描述 |
XML |
application/xml[14] (资源扩展名.xml,参数名accept=xml) |
避免使用text/xml,因其默认字符集是us-ascii,而application/xml默认使用UTF-8. |
JSON |
application/json (资源路径扩展名.json,参数名accept=json) |
7.5 自定义媒体类型(Custom MIME Types)
自定义媒体类型使得用户可以定义公司专有的数据显示格式。这时它就成为客户和服务之间的数据格式合约。自定义媒体媒体类型可以和特定资源相关。
例如:
GET /customers/1234 HTTP/1.1
Host: newegg.com
Accept: application/vnd.newegg.customer+xml
7.6 语言(Language)
如果Response支持多语言,则使用Accept-Language HTTP头来指定语言选项。服务使用Content-Language头显示实际使用的语言。
如果业务数据支持多语言(例如item description),则可使用languageCode查询参数来指定业务数据语言选项。Response DTO中使用LanguageCode来表示业务数据的实际语言类型。
语言代码可以是两个字母(ISO-639代码)或者语言+’-‘+国别的形式。例如en, en-US。
7.7 字符编码(Character Encoding)
客户使用Accept-Charset头或者URI参数charset来指定字符编码选项。
在服务响应中,如果媒体类型是文本的并且支持charset参数,在Content-Type中包含charset属性以显示服务所使用的字符编码方式。注意在XML中:如果使用Content-Type: application/xml; charset=UTF-8,需和XML内的encoding类型一致:<xml version=”1.0” encoding=””>。
主要使用UTF-8来支持Unicode类型。
7.8 压缩(Compression)
HTTP压缩又称内容编码 (Content Encoding[15]) 。主要选项有gzip, deflate, compress, identity[16]。新的内容编码选项在IANA注册。
客户使用Accept-Encoding头来指定内容编码。服务使用Content-Encoding头显示实际使用的内容编码。默认使用identity。
7.9 关于HTTP Vary头
当存在多种资源呈现时,在响应中包含Vary头可以通知客户和缓存基于服务端驱动的内容协商的条件和结果。它的值是以逗号分隔的服务端使用的请求头列表。例如:
Request Accept-Language: en; q=1.0, */*; q=0.0 Response Content-Language: en Vary: Accept-Language |
当服务端没有使用HTTP请求头,而是使用客户IP地址、时间、用户个性化设置等信息时,Vary值为 * .
7.10 关于内容协商的规定
1)客户需明确关于内容协商的要求,除个别选项外,不应依赖服务端提供的默认值。
2)服务同时支持前文中2种关于资源呈现的请求方式(同时支持服务驱动和客户驱动的内容协商)。如果客户端同时指定了2种方式,优先顺序依次是查询参数和HTTP头。
3)服务至少支持XML和JSON2种资源呈现类型来传输一般文本数据(二进制数据请使用对应的媒体格式)。如果客户没有指定媒体格式,默认返回XML。
4)暂不允许自定义媒体格式。
5)对于大数据量传输,推荐使用JSON。
6)使用Accept-Language头来指定和业务数据无关的一般语言偏好;使用查询参数languageCode来匹配实际业务数据中的语言类型。
7)如果服务不支持请求的语言类型,将使用客户或BU(Business Unit)默认使用的语言类型。
8)不用添加Accept-Charset头(或者相应查询参数),除非客户端只能处理一种特定的字符集。
9)如果没有Accept-Charset头(或者相应查询参数),默认使用UTF-8编码[17]。
10)如果服务不支持任何指定的字符编码,且没有*;q=0限制,则使用UTF-8编码。
11)内部客户总是使用Accept-Encoding: gzip (生产环境IIS的 gzip压缩是打开的)。
12)使用Vary头以正确地缓存数据。
13)如果服务不支持请求的媒体格式且客户显示地设置了*/*;q=0.0,应返回406 (Not Acceptable) 错误。
14)如果服务不支持请求的压缩格式,直接返回非压缩原始数据。
15)暂不允许自定义HTTP头。
8 服务请求
8.1 请求参数
每一种请求都可以通过以下三种方式传值:
n URI 参数
n Request Body
n HTTP Header
8.1.1 URI参数
URI QueryString,主要用于GET和DELETE请求。
例如: GET /order?customerNumber=123456&orderStatus=open HTTP/1.1
8.1.2 Request Body
把相应的请求数据以XML / JSON / NameValue形式通过Request DTO POST/PUT到服务器上。
Request Body主要用于POST/PUT请求。不推荐在GET请求中包含Request Body[18]。
8.1.3 HTTP Header
任何HTTP协议允许的Request Headers都可以直接使用。但不应将放在实体主体里的信息放进报头。
暂不可以使用自定义Request Headers。
8.2 请求格式
当向服务端POST/PUT数据时, 可以通过HTTP头Content-Type来指定请求数据的格式。如果不能正确指定, 在服务器端会解析错误。
Content-Type一般有以下几种:
n XML:application/xml
n JSON:application/json
n JSON:application/jsv
n URL Encoded:application/x-www-form-urlencoded
如果不指定Content-Type, 会默认请求数据编码为:application/x-www-form-urlencoded.
请求格式示例请参考: /* Key/Cross-Team Projects/* API/4. API接口描述
8.3 批量请求(Batch Request)
允许客户同时创建、更新或删除多个资源。
在一个Request Body里包含多条记录,一次提交。
9 服务响应
9.1 响应格式
响应格式示例请参考: /* Key/Cross-Team Projects/* API/4. API接口描述
9.2 一般要求
- Links
- 使用HTTP Link头存储所有的Links
- 包含一个到自身的链接(<atom: link href= {href} rel=”self”/>)
- 包含相关操作(包括翻页操作)的链接
- 包含组成该资源的相关资源的链接
- 子资源的Links使用<link>标签
- XML
- 可以关闭XML声明以节省带宽
- 可以直接在XML消息体中包含业务数据的语言标签。例如
<text xml: lang=”en-US”>Newegg Inc. </text>
<text xml: lang=”cn”>新蛋</text>
- 空字段也要包含在返回结果中,显示为null
9.3 可携式数据格式(Portable Data Formats)
除非数据是面对最终用户的,不要使用本地化的数据格式,而应使用可携式数据格式:
- 使用W3C XML Schema里定义的数据类型decimal, float和double来格式化数字,包括货币
- 使用ISO 3166里定义的国家或地区代码。例如US, CN.
- 使用ISO 4217里的字母数字表示货币。例如USD, CAD.
- 使用RFC 3339[19]里定义的日期、时间和日期时间格式
- 使用BCP 47里定义的语言标签。例如en, en-CA.
- 使用Olson Time Zone Database里规定的时区代号
- 限制单次返回到客户的记录总数(除非用户自行指定最大返回记录数)
- 返回集合内的记录总数
- 如果记录总数大于单次返回记录数,提供翻页机制
- 通过the Link header存储翻页信息。例如:
9.4 集合
<https://api.github.com/repos?page=3&per_page=100>; rel="next",
<https://api.github.com/repos?page=50&per_page=100>; rel="last"
- rel值有:next, last, first, prev, self
9.5 封装集合资源
RESTful服务有3种主要方法来封装集合,分别是:容器对象、订阅列表和分段返回。
9.5.1 容器对象(Container Objects)
打包资源集合最常用方法是将它们包装(wrap)成一个更高级别的集合对象。
9.5.2 订阅列表(Feeds)
Feeds的两个主要标准是Atom和RSS。RSS能够用一个列表来表示一个带注释的超链集合。Atom也是用一个列表来表示一个带注释的超链集合,但允许在每个单项的”content”标签中嵌入该项的实际内容。
使用Feeds来表示资源集合的优点是RESTful服务的行为类似于在web上提供新闻提要。
9.5.3 分段返回(Multipart HTTP Responses)
HTTP协议允许通过使用Multipurpose Internet Mail Extensions (MIME) multipart content types [20]来在单一返回消息体中传递多种资源。这使得可以在单一的HTTP返回中传递资源集合。随着”HTTP Put”和”Comet”技术的出现,这种方法逐渐流行。
Comet技术[21]在持久HTTP连接上使用持久运行的多段返回,使得服务器可以向浏览器推送内容。使用这个技术的好处是: 运行时间较长或者结果集较大的查询,可以在查询结束前向请求者推送数据。
9.6 标题扩展
为了最大限度减少网络流量,并从客户的角度简化一些API,可提供标题扩展功能。即在返回中默认隐藏某些集合的明细列表,只有在用户需要时才提供对象的扩展显示。
利用开关参数expand={expand object}来打开/关闭扩展。参考Atlassian REST API Design Guidelines version 1: Title Expansion for Entities
9.7 二进制数据(Binary Data)
除了XML和JSON格式的数据,有时也需要在文本中嵌入二进制数据,例如显示图片或者电影片段。
通过分段消息(Multipart messages)可以将不同格式的数据合并成一个HTTP消息。使用下列媒体类型来处理二进制数据:[22]
- Multipart/mixed
- Multipart/related
- Multipart/alternative
- Multipart/form-data
例如:
Content-type: multipart/mixed; boundary=”abcd” --abcd Content-type: application/xml; charset=UTF-8 <movie>…</movie> --abcd Content-type: video/mpeg ---image here--- ---abcd--- |
9.8 移动设备
User-Agent头信息让你知道你在和移动设备打交道,因而返回内容的移动版本。
例如:iPhone User Agent
Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3
10 资源关联
REST的原则之一是HATEOAS(Hypermedia as the Engine of Application State),即服务为客户端提供所有信息使得客户端可以和服务进行交互。
可以使用<link>标签[23]或者HTTP Link头来关联资源。
10.1 在资源呈现中使用<link>标签
1) <link>标签属性
属性 |
描述 |
href |
目标资源的URI[24] |
rel(”relation”的缩写) |
Link关联类型 (Link Relation Type) 。主要目的是标识相关链接的语义。大小写敏感。可以有多个值,例如rel=”alternate help”。 |
title |
可选。对被链接资源的简短描述 |
type |
可选。该链接返回内容的媒体类型(media type) |
hreflang |
可选。该链接返回内容的语言 |
length |
可选。该链接返回内容的长度 |
2) 属性rel的可能值
- 注册的Link Relation Types[25]
- 常用的有:self, alternate, edit, related, previous, next, first, last
- 其它还有appendix, bookmark, chapter, contents, copyright, current, describeby, edit-media, enclosure, glossary, hub, index, latest-version, license, next-archive, payment, predecessor-version, prev-archive, replies, section, service, start, stylesheet, subsection, successor-version, up, version-history, via, working-copy, working-copy-of
- HTML5规范定义了额外的关联类型,例如archives, feed, pingback等
- Atom Syndication Format[26]提供了扩展机制来定义扩展的link关联类型。例如:
<review xmlns=”org:example:books” xmlns:atom=”http://www.w3.org/2005/atom”> <atom:link rel=”http://example.org/rels/book” href=”…”/> <atom:link rel=”http://example.org/rels/author” href=”…”/> <atom:link rel=”http://example.org/rels/add-review” href=”…”/> </review> |
3) 在JSON格式中使用Links
完整形式 {“links”: [ {“rel”: ”alternate”, “href”: ” http://example.org/rels/book” }, {“rel”: ”owner”, “href”: ” http://example.org/rels/author” } ]} 简化形式 { “alternate”: ”http://example.org/rels/book” “owner”: “http://example.org/rels/author” } |
10.2 HTTP头 Link
HTTP Link头提供了一种独立于资源呈现格式的link传送方式,并且在协议层可见。
格式:Link: <{URI}>;rel=”{relation}”;type=”{media type}”;title=”{title}” …
Link头适用于:
1) 资源呈现使用二进制格式,例如images,rich-text documents, spreadsheets等
2) 资源呈现的格式使得links不容易被找到(例如纯文本文件)
3) 希望在不解析内容的情况下添加或者读取links
10.3 HTTP头:Locaton vs Content-Location
Location:通常用于POST返回,将接收方重定向到Location所指向的URI。使用场景:
1) 当服务器创建新资源后,例如返回代码201
2) 在出现3xx错误时,将客户引向Location所指向的URI
Content-Location:通常用于GET返回,申明资源呈现的URI
10.4 URI模板 (URI Template)
URI模板允许服务端提供“半透明”的URIs给客户端。客户端填充缺失的内容以产生有效的URI。
例如:
XML格式 <link-template href=” http:// neweggcentral-rest/order/{order-number}” title=”…” rel=”…”/> <link-template href=” http:// neweggcentral-rest/order/search/customer-number={customer-number}” title=”…” rel=”…”/> JSON格式 “link-templates”: [{ “ref ”: “…”, “href”: “http:// neweggcentral-rest/order/{order-number}”, “title”: “…” }, { “ref”: “…”, “href”: “http:// neweggcentral-rest/order/search/customer-number={customer-number}”, “title”: “…” }] |
10.5 关于资源关联的规定
1) 通过atom:link元素或者HTTP Link头提供相关操作或者关联资源
2) 每一个资源都应使用HTTP Link头提供一个链接指向自身:Link: <{href}>;rel=”self”;…
3) 扩展Relation Types统一管理。不能自行随意添加扩展Relation Types
4) 目前提供的扩展Relation Types:/rel/add, /rel/void, /rel/delete
5) 所有Link Relation Types使用小写
6) 扩展Relation Type的URI指向对该扩展Relation Type的描述,包括:
- 目的
- 使用该Relation Type的资源类型
- 有效的HTTP方法
- 请求和响应的媒体类型等
7) 对URI模板中的参数提供说明
8) 如果用户指定了扩展名(例如.xml, .json),链接中尽可能使用相同的扩展名
9) 客户通过URI根可以最终找到所有的资源入口
11 查询(Query)
查询是HTTP GET方法最普遍的应用。针对极其复杂的查询也可使用POST方法。不过通常 POST 方法没有缓存机制,因此不是查询数据的首选。
11.1 查询方法(Query Methods)
11.1.1 使用HTTP GET
GET是安全的(Safe)和幂等的(Idempotent),是获取资源的默认HTTP方法。不能用GET查询来修改数据。
GET通过URI的Query String传递查询参数。查询参数包括过滤条件、排序字段、返回字段列表、返回记录数等。例如:
例如,按Invoice日期排序,从第80张invoice开始,返回20张invoices:
http://business.com/invoices?numInvoices=20&startIndex=80&sortOrder=invoiceDate
可以通过查询参数选择需要返回的字段。参考Facebook graph API.
https://graph.facebook.com/bgolub?fields=id,name,picture
查询参数的不同排列和组合会导致不同的URI。太多的URI会降低缓存的有效性。所以查询参数的排列顺序尽可能保持不变。
服务要默认所有查询参数都是可以省略的,并在客户省略查询参数的情况下提供参数的默认值或默认行为(对于确实不能省略的查询参数,返回明确的错误信息)。服务需要忽略多余的参数。服务需要对返回的记录总数进行限制(用户已指定最大返回记录数除外)。
查询参数最主要用来返回资源集合,不应用来提供资源ID和任何操作指示。将资源ID嵌入查询字符串的问题之一是系统不再能根据URL base和资源路径来判断是否同一资源。查询字符串不能有业务操作是因为它不应直接影响资源或者服务器的状态。例如:
❶ GET /order/123456 正确
❷ GET /order?orderNumber=123456 错误
如果order 123456不存在,URL❶会返回404错误,URL❷会返回空集合。
11.1.2 使用HTTP POST
如果查询条件太长使得URI超出浏览器或服务器对URI长度的限制,可以使用POST方法来执行查询。这时在请求Body中含有使用application/x-www-form-urlencoded编码的查询条件。例如:
Request POST /order HTTP/1.1 Host: neweggcentral-rest Content-Type: application/x-www-form-urlencoded zipcode=91765 & status=’V’ Response HTTP/1.1 200 OK Content-Type: application/xml; charset=UTF-8 … |
使用POST方法来获取资源的坏处:
1) 削弱了REST的“统一接口”原则
2) 返回结果不能缓存
3) 因不能缓存,翻页操作会反复重新执行查询
11.2 保存查询(Store Queries)
可以为每一次不同的查询创建一个新资源。这样通过资源可以访问和执行历史查询。
通过POST方法执行的查询,可以首先创建一个关于该查询的新资源,然后利用GET 方法去访问该查询资源,这样POST查询就转成了GET查询。这样查询的结果是可缓存的。任何同样的 查询都可通过重定向到该资源的URI得以执行。而且,该查询还可以接收新的条件。
资源创建成功后,返回代码201(Created)以及一个指向新资源的Location头。例如:
Request #1 POST /order HTTP/1.1 Host: neweggcentral-rest Content-Type: application/x-www-form-urlencoded zipcode=91765 & status=’V’ Response HTTP/1.1 201 Created Content-Type: application/xml; charset=UTF-8 Location: http:// neweggcentral-rest/order/query/1 Request #2 GET /order/query/1?start=10 HTTP/1.1 Host: neweggcentral-rest |
11.3 预定义查询
预定义普遍使用的查询,有助于服务端优化设计和提高响应速度。例如:返回order查询的summary视图:
/order? OrderDateFrom=2012-05-21 & view=summary
11.4 标准查询参数
为了保持各子系统统一接口和风格,有必要约定参数命名。
1) HTTP头相关
小写的HTTP头名称。例如:accept;
2) 业务对象相关
一般和字段命名相同,尽量和业界规范保持一致,各子系统务必统一。例如:customerNumber, orderNumber, vendorNumber,
3) 日期、时间相关
- 起始/截止日期:dateFrom, dateTo
- 起始/截止时间:timeFrom, timeTo
例如:订单起始/截止日期:orderDateFrom,orderDateTo
4) 控制有关
- 选择字段:fields= {return fields}
- 排序:sort= asc/desc
- sortFields
- 视图:view= summary/ meta/ status/ …
- 预定义查询:query={pre-defined query}
- 返回记录数:limit= 100
- 第n页:pageIndex={n} pageSize
- 第n条记录:startIndex={n}
- 方法:method= delete
- 展开子集合expand= order/ order.items/ …
- 返回格式:format
11.5 关于查询的规定
1) 使用查询参数来指定过滤条件、排序字段或返回字段列表等
2) 查询参数的排列顺序尽可能保持不变
3) 如果客户没有设置查询参数,服务返回默认视图
4) 为普遍使用的查询预定义命名查询
5) 服务要假设所有查询参数都是可以省略的,并在客户省略查询参数的情况下提供默认视图。
6) 服务需要忽略多余的参数。。
7) 提供通过HTTP Link头提供翻页机制
8) 对于隐藏内容,明确申明,不要让用户以为是服务端配置问题
9) 如果字段较多,考虑支持fields参数
10) 考虑支持标题扩展功能以支持返回子对象更详细的信息
11) 文档化每一个查询参数
12 缓存与并发(资源版本控制)
12.1 缓存(Caching)[27]
HTTP过期缓存机制(Expiration Caching)可以有效减少对服务端的请求数量。服务只应该缓存成功的GET请求。
过期缓存机制基于Cache-Control(HTTP 1.1)和Expires(HTTP 1.0)头。这些头信息告诉客户和缓存在一段特定的时间内保留服务端响应结果的一份拷贝。服务应在响应中添加这两个HTTP头,使得客户端可以决定是否可以缓存结果。例如:
First Request GET /order/12345678 HTTP/1.1 First Response HTTP/1.1 200 OK Date: Sun, 09 Aug 2009 00:44:14 GMT Last-Modified: Sun, 09 Aug 2009 00:40:14 GMT Expires: Sun, 09 Aug 2009 01:44:14 GMT Cache-Control: max-age=3600,must-revalidate Second Request (10分钟后) GET /order/12345678 HTTP/1.1 Second Response HTTP/1.1 200 OK Date: Sun, 09 Aug 2009 00:54:14 GMT Last-Modified: Sun, 09 Aug 2009 00:40:14 GMT Expires: Sun, 09 Aug 2009 01:44:14 GMT Cache-Control: max-age=3600,must-revalidate Age: 600[28] |
Cache-Control的主要参数有:
属性 |
描述 |
public |
默认值。即使请求是需要认证的,也可用之来使用共享缓存(Shared Cache) |
private |
客户端缓存(例如浏览器缓存或者正向代理(Forward Proxy))可以缓存结果,但共享缓存(例如服务端或者网络缓存)不能缓存结果。用于当响应只针对认证客户时。 |
no-cache & no-store |
禁止缓存。同时包含Pragma: no-cache头可以支持HTTP 1.0 |
max-age |
有效期(freshness lifetime),以秒为单位 |
must-revalidate |
告诉缓存返回过期结果前必须检查服务端数据(条件请求) |
proxy-revalidate |
和must-revalidate类似,但只用于共享缓存 |
此外,对可缓存的服务端响应,服务应该设置HTTP Vary头。例如:Vary: Content-Type,表示基于URL+返回的content-type服务响应是可缓存的。
12.2 条件请求(Conditional Request)
条件请求用于GET请求时,可以验证缓存是否过期;用于POST、PUT和DELETE时,可以提供并发控制。
服务端用Last-Modified和ETag(Entity Tag)响应头来驱动条件请求。
客户端:
- If-Modified-Since和If-None-Match用于校验缓存
- If-Unmodified-Since和If-Match作为并发控制的前提条件
服务端必须处理客户端提供的If-Match 和 If-None-Match头。客户端在使用If-Match 和If-None-Match头请求资源时需提供ETag值。ETag是由针对同一资源的前一次请求的结果提供的。服务端比较结果返回给用户最新内容,或者用 HTTP 响应码 304 告知用户,内容没有变化。
Last-Modified的刻度较粗(以秒为单位),因此属于“弱验证”。ETag属于“强验证”,因为每个不同的资源呈现都会不同。不必要同时使用Last-Modified和ETag。
如何生成Last-Modified和ETag标签?确保每次产生的ETag值都不重复。
1) 利用数据库表结构的时间戳或者序列号字段
2) 使用资源数据产生ETag值。将该值和时间戳保存在一个单独的表或数据源中
3) 如果资源呈现的数据量不大,可以将其进行MD5哈希后得到ETag值
12.3 并发控制(Concurrency Control)
12.3.1 并发控制方式
并发控制有两种方式:
1) “悲观”并发控制:客户首先加锁资源,然后修改,然后解锁。期间服务会阻止其它客户获得资源锁。关系数据库采用这种方式。
2) “乐观” 并发控制:客户首先得到一个token,并尝试包含有token的写请求。如果token仍然有效,写入会成功。
HTTP采用的是乐观并发控制:
1) 连同每个资源呈现,服务提供给客户一个token
2) 当资源状态发生改变时,token的服务端版本发生改变
3) 客户在请求修改或删除资源时,提供token给服务端,是为“条件请求”
4) 服务验证token是否仍然有效。如果无效,并发操作失败,服务中断请求
12.3.2 条件PUT/Delete请求
服务端:
如果资源不存在,返回404(Not Found)(或者在许可的情况下创建新资源)。
如果客户没有提供If-Unmodified-Since和If –Match头,返回403(Forbidden)。
如果客户提供的If-Unmodified-Since或If –Match值和服务端的实际修改日期和ETag值不符,返回412(Precondition Failed)。
如果条件满足,更新(或删除)资源,返回200(OK)或者204(No Content)。
客户端:
如果收到412错误,利用无条件GET请求得到最新的Last-Modified和ETag值,然后重新提交请求。
12.3.3 条件POST请求
条件POST请求用来发现和阻止重复提交。
如果用POST创建新资源,会返回201或者303+新的URI。在这种情况下,客户端本地没有该资源呈现的拷贝和与条件请求相关的HTTP头信息。
方法是通过为每个POST请求提供一个一次性的URI。URI里包含有一个服务端产生的token,只能用于一次POST请求。将所有使用过的token保存在服务端的事务日志里。例如:
Request GET /vendor/address/address-token HTTP/1.1 Response HTTP/1.1 204 No Content Cache-Control: no-cache Link: <http://neweggcentral-rest/address;t=360eerfg8gg34j4d6ff8>;rel=”http://neweggcentral-rest/rel/resource” |
如果客户提交的POST请求里含有已经使用过的token,返回403(Forbidden)。否则处理请求,返回201(Created)或者303(See Other),并标记该token为可使用。
可以通过MD5当前时间+随机数+ETag等来产生用于一次性URI的token。可以在URI中包含数字签名来提高其安全性。
12.4 关于缓存与并发的规定
1) 确定每种资源表示的缓存策略
2) 在使用no-cache或者no-store之前,考虑使用教小数值的max-age替代
3) 各子系统需要决定各资源的PUT操作在资源不存在时是否能创建新资源
4) 针对不同情况,确定是否使用条件请求和并发控制
13 异步服务(Asynchronous Services)
在面向service的过程
13.1 异步服务处理步骤
1) 收到请求后,马上返回ACK,包括:
- Received: 收到请求的时间
- RequestURI: 针对该请求新创建的Request资源, 可选。 客户端可据此检查服务端实际收到的请求内容
- Transaction: 原子操作,允许多个
例如:
<receipt xmlns="http://www.xml.org/2004/rest/receipt" requestUri = "http://www.example.com/xya343343" received = "2004-10-03T12:34:33+10:00"> |
2) 校验请求
3) 尽快处理请求
13.2 Transaction生命周期
Status资源可以看着是transaction资源的不同视图:
Transaction URI: http://www.example.com/xyz2343
Transaction Status URI: http://www.example.com/xyz2343?view=status
状态 |
描述 |
Received |
<status state="received" timestamp="2004-10-03T12:34:33+10:00" /> |
Processing |
|
Processed |
<status state="processed" timestamp="2004-10-03T12:34:33+10:00"
> |
Failed |
<status state="failed" timestamp="2002-10-03T12:34:33+10:00" > <error code="3" > <message>A bad request. </message> <exception>line 3234</exception> </error> </status> |
14 错误处理(Error Handling)
服务应坚持使用HTTP状态代码来标识成功/失败。在非常特殊的情况下可能需要添加自定义HTTP状态代码(只能作为例外处理,并且保持在最低水平)。任何有关该错误的上下文信息应包含在HTTP响应的Body中。
14.1 HTTP错误代码
- 4xx – 客户端错误 (400-417)
- 5xx – 服务端故障 (500-505)
Code |
Name |
Description |
Notes |
400 |
Bad Request |
请求没有包含Host头 |
|
401 |
Unauthorized |
客户无授权。如果客户经过认证后依然不允许访问,则返回403(Forbidden) |
返回该错误时,同时返回一个包含有认证方法的WWW-Authenticate头。普遍使用的认证方法有Basic和Digest。 |
403 |
Forbidden |
禁止客户访问该资源 |
|
404 |
Not found |
资源未找到 |
|
405 |
Not Allowed |
HTTP方法不允许 |
返回一个包含有效方法的Allow头 |
406 |
Not Acceptable |
请求返回的媒体格式不支持 |
|
409 |
Conflict |
请求和现有资源状态冲突 |
|
410 |
Gone |
资源已经不存在 |
|
412 |
Precondition Failed |
提供的If-Unmodified-Since或者If-Match和实际修改时间或ETag值不同 |
|
413 |
Request Entity Too Large |
POST或PUT的Body太大 |
在Body中指明允许的值,或者变通方法 |
422 |
Unprocessable |
请求数据格式正确但语义错误 |
|
415 |
Unsupported Media Type |
不认识请求消息格式 |
|
500 |
Internal Server Error |
由于服务实现代码的bug造成的失败 |
|
509 |
Service Unavailable |
服务不能在某些时间段或者确定的时间内完成请求。两种普遍的情况是server出现故障或者客户请求超过限额 |
可能的话返回一个Retry-After头 |
14.2 错误消息格式
GET https://api.newegg.com/customer
HTTP/1.1 401 Unauthorized
Link: <http://neweggcentral-rest/errors/20003>;rel=http://neweggcentral-rest/rel/error
Link:<http://neweggcentral-rest/doc/errors/20003>;rel=http://neweggcentral-rest/rel/error_doc
<ResponseBody>
<Errors>
<Error>
<Code>20003</Code>
<Message>Authenticate</Message>
<MoreInfo>{More detail info about the error}</MoreInfo>
</Error>
</Errors>
</ResponseBody>
14.3 用户请求错误
涉及用户输入数据的服务请求(POST,PUT),应在响应消息中额外包含用户错误信息列表,以清楚地展示纠正错误需要采取的行动。可以添加一个新的自定义HTTP状态代码以清楚地向客户端传达这里是一个用户数据校验错误。
用户输入错误对象有资源(Resource)和字段(Field)属性使得用户能够定位错误所在。也有一个错误代码(Error Code)使用户能够知道错误原因。可能的校验错误类型:
missing |
资源不存在 |
missing_field |
资源的必填字段缺值 |
invalid |
字段格式无效 |
already_exists |
字段值重复(别的资源已经有同样的值) |
15 版本控制
两种主要的版本定义方式:
- HTTT Accept头
- URI
15.1 使用HTTP Accept头传递版本信息
例如:
GET /account/1 HTTP/1.1
#1: Accept: application/vnd.steveklabnik-v2+json
#2: Accept: application/xml; version=1.0
15.2 使用URI传递版本信息
例如:
http://api.newegg.com / order_mgmt /order/v1/order
http://api.newegg.com /order_mgmt/claim/v1/claim
15.3 关于版本控制的规定
- 使用URI来传递版本信息
- 版本控制以Sub Domain为单位
- 如果API存在多版本,新旧版本必须同时支持
- 使用latest关键词提供指向最新版本的快捷方式
- 对旧版本,只修复严重bug
- 如果不支持传入的版本号,服务端应该返回代码415和提示信息
- 需要新版本(打破现有的客户端)
- 移除或重命名URI
- 同一URI返回不同的数据
- 针对已有资源移除HTTP方法
- 从数据类型移除一个字段(客户端可能未处理值缺少的情况)
- 重新排列字段顺序或者添加一个新字段(依赖字段顺序的客户会受到影响。客户端应基于字段名字定位)
- 改变URL的结构等(返回给用户301- Moved Permanently,同时在Location头返回新的资源URI)(最好保留旧结构的URL)
- 重命名字段等(XML和JSON都是大小写敏感的)
- 不需要新版本
- 新资源
- 新的资源呈现
- 针对已有资源的新的HTTP方法等
- 服务的版本号和开发的版本号是不同的。可能多次发布,但服务的版本号不用改变
16 客户端调用
不能假设服务的每次响应格式能够保持一致(例如某些元素可能丢失或出现在多个位置)。需要通过表达式(expressions)从Web服务的返回中动态抓取值。
解析模式取决于返回的是XML还是JSON格式。例如用XPath处理XML,用JavaScript处理JSON。
17 命名规范
1) 在比较URL时大小写是敏感的[29]
2) 使用小写字母
3) 不要使用下划线,用-代替(? Dropbox, Twitter, Google maps 和 Facebook都使用下划线)
4) 使用最简单的名词形式
5) 自解释:避免含义模糊的缩写
6) 一致性:同样的单词表示同样的意思
7) 尽量整齐匀称
18 文档要求(Documentation)
1) 方法列表
2) 对每个服务方法,应该包括:
- URI
- HTTP 方法
- HTTP 请求头Accept 和 Content-Type 等
- 自定义HTTP头
- 前置条件、后置条件、附带后果等
- 默认值
- 所有可能的HTTP返回代码
- 错误代码
- 请求示例
- 返回示例
19 附录
19.1 HTTP返回代码[30]
完整的HTTP返回代码列表请参考 section 6 of RFC 2616.
HTTP 响应代码 |
代码含义 |
200 |
已创建,请求成功且服务器已创建了新的资源。 |
201 |
是否只显示处于警告状态的应用实例 |
301 |
重定向 , 请求的网页已被永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。 |
302 |
重定向 , 请求的网页临时移动到新位置,但求者应继续使用原有位置来进行以后的请求。302 会自动将请求者转到不同的临时位置。 |
304 |
未修改,自从上次请求后,请求的网页未被修改过。服务器返回此响应时,不会返回网页内容。 |
19.2 参考资料
- Newegg文档
- Newegg Rest API接口约定.docx
- REST示例
- http://developer.github.com/v3/
- http://www.twilio.com/docs/api/rest
- http://developers.facebook.com/docs/reference/api/
- https://dev.twitter.com/docs/api
- http://docs.opensocial.org/display/OSD/Specs
- http://portablecontacts.net/draft-spec.html
- Atlassian REST API Design Guidelines
- SurveyGizmo REST API Documentation
- RESTful Services Guidance for Developers v1.0
- Google Maps API Web Services
- https://developers.google.com/gdata/
- 人人网开放平台API
- Frameworks
- http://code.google.com/p/implementing-rest/wiki/RESTFrameworks
- http://django-rest-framework.org/
- http://www.restlet.org/
- http://incubator.apache.org/wink/
- 其它
- REST APIs must be hypertext-driven
- Learn REST: A Tutorial
- The design of a RESTful web-service
- Restful Web-Services Best Practices
- Some People Understand REST and HTTP
- http://barelyenough.org/blog/tag/rest-versioning/
- A Guide to Designing and Building RESTful Web Services with WCF 3.5
- The Good, the Bad, and the Ugly of REST APIs
[1] 在本文中URI和URL等同使用,并不作特别区分。
[3] Sitemaps protocol 对URL长度的限制为2048字符。Sitemap 可方便管理员通知搜索引擎他们网站上有哪些可供抓取的网页。Google网站管理工具也使用了该协议。因此,如果要使用该特性进行SEO,URL长度不能超过2048字符。
[5] 例如:<form method=”get” action=http://www.google.com/search enctype=”application/x-www-form-urlencoded”>…</form>
[6] Location 头告知用户新资源在哪里。如果仅仅告诉用户新的ID,用户还得自己去组装URI。BODY中不需要新资源的URL。
[7] RFC 2616: http://tools.ietf.org/html/rfc2616
[8] 代理Agent指User Agents。最普编的User Agents是浏览器。
[10] q参数在Accept头上使用的最普遍,但可应用于所有的Accept-*头。另,并不是所有的服务器都支持q参数。
[11] RFC 4627描述了如何确定JSON格式数据的字符编码。
[13] 媒体类型的完整列表参见http://www.iana.org/assignments/media-types/
[14]不要使用text/xml,因其默认编码方式是US-ASCII
[16] Content-Encoding的说明中指出deflate指的是在RFC1950说明的zlib格式。也就是说当Content-Encoding为deflate时,内容应该为zlib格式。Deflate (RFC1951):一种压缩算法,使用LZ77和哈弗曼进行编码;zlib (RFC1950):一种格式,是对deflate进行了简单的封装;gzip (RFC1952):一种格式,也是对deflate进行的封装。gzip = gzip头(10字节) + deflate编码的实际内容 + gzip尾(8字节)。
[17] UTF是Unicode Transformation Format的缩写。UTF-8是Unicode的实现方式之一,也是application/xml和application/json的默认编码方式。UTF-8最大的好处是和ASCII兼容。参考http://www.ietf.org/rfc/rfc3629.txt
[20] 在RFC2046 5.1节定义 http://tools.ietf.org/html/rfc2046#section-5.1
[22] 避免使用Base64编码文本格式中的二进制数据。
[24] 对于XML,可通过在link元素或者父节点上使用xml: base属性来使用相对URI。但XML解析库并不自动使用xml: base来解析相对路径。
[25] 完整列表参见Web Linking draft-nottingham-http-link-header-10 Section 6.2.2: Initial Registry Contents
[27] “缓存”通常用于指对象缓存(Object Cache)(例如memcached)或者HTTP缓存(例如Squid或者Traffic Server)。它们最大的不同是HTTP缓存不需要调用任何编程API来存储、访问或删除缓存中的对象。
[28] 由缓存添加,显示该拷贝存在于缓存的时间