文章目录
设计原则
基本原则
使用 HTTP 设计 RESTful API 时的一些主要原则:
-
REST API 围绕资源设计,资源是可由客户端访问的任何类型的对象、数据或服务。
-
每个资源有一个标识符,即,唯一标识该资源的 URI。 例如,特定客户订单的 URI 可能是:
https://adventure-works.com/orders/1
-
客户端通过交换资源的表示形式来与服务交互。 许多 Web API 使用 JSON 作为交换格式。 例如,对上面所列的 URI 发出 GET 请求可能返回以下响应正文:
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links": [
{"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" },
{"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" }
]
}
-
对于基于 HTTP 构建的 REST API,统一接口包括使用标准 HTTP 谓词对资源执行操作。 最常见的操作是 GET、POST、PUT、PATCH 和 DELETE。
-
使用无状态请求模型,不保留请求之间的状态信息。每个请求应是原子操作。
围绕资源组织 API:
-
侧重于 Web API 公开的业务实体。 资源 URI 应基于名词(资源)而不是动词(对资源执行的操作)。
-
实体通常分组成集合。集合是不同于集合中的子项的资源,应具有自身的 URI。
-
需要考虑不同类型的资源之间的关系
-
避免请求复杂度超过
集合/项目/集合
的资源 URI。
根据 HTTP 方法定义操作
-
GET 检索位于指定 URI 处的资源的表示形式。 响应消息的正文包含所请求资源的详细信息。
-
POST 在指定的 URI 处创建新资源。 请求消息的正文将提供新资源的详细信息。 请注意,POST 还用于触发不实际创建资源的操作。
-
PUT 在指定的 URI 处创建或替换资源。 请求消息的正文指定要创建或更新的资源。
-
PATCH 对资源执行部分更新。 请求正文包含要应用到资源的一组更改。
-
DELETE 删除位于指定 URI 处的资源。
数据筛选和分页
-
API 可以允许在 URI 的查询字符串中传递筛选器,例如
/orders?minCost=n
。 然后,Web API 负责分析和处理查询字符串中的minCost
参数并在服务器端返回筛选后的结果。 -
将 Web API 设计为限制任何单个请求返回的数据量。 考虑支持查询字符串指定要检索的最大项数和集合中的起始偏移量。 例如:
/orders?limit=25&offset=50
使用 HATEOAS 导航到相关资源
每个 HTTP GET 请求应通过响应中包含的超链接返回查找与所请求的对象直接相关的资源所需的信息,还应为它提供描述其中每个资源提供的操作的信息。
例如,若要处理订单与客户之间的关系,可以在订单的表示形式中包含链接,用于指定下单客户可以执行的操作。 下面是可能的表示形式:
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links":[
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"DELETE",
"types":[]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"DELETE",
"types":[]
}]
}
一个博客网站的API
模仿 Github,设计一个博客网站Ablog的 API。
所有的请求:
https://api.Ablog.com
Current version
如果有不同版本的话,我们可以将所有向https://api.blog.com
的请求都接受某个版本的API。
可以通过Accept
头明确请求此版本。以v3为例:
Accept: application/vnd.Ablog.v3+json
Schema
所有的API访问都是通过HTTP:https://api.blog.com
。所有的数据发送与接受都以JSON格式。例如,当获取 账户为12345的用户 的 编号为67890的博客时:
curl -i https://api.Ablog.com/users/12345/blog/67890
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 17 Nov 2019 11:21:14 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Status: 200 OK
ETag: "a00049ba79152d03380c34652f2cb612"
X-Ablog-Media-Type: Ablog.v3
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4987
X-RateLimit-Reset: 1350085394
Content-Length: 5
Cache-Control: max-age=0, private, must-revalidate
-
空白字段用
null
表示。 -
所有的时间戳以ISO 8601格式返回:
YYYY-MM-DDTHH:MM:SSZ
博客摘要
获取资源列表时,响应包括该资源的属性子集。摘要表示排除了某些不必要的属性。
当获取用户12345的所有博客时,将获得每个博客的摘要显示:
GET /user/12345/blogs
博客详细内容
当您获取单个资源时,响应通常包括该资源的所有属性。
当获取用户12345的某个博客(这个博客的编号是67890)时,将获得这个博客的详细表示:
GET /user/12345/blog/67890
认证方式
在某些地方,需要认证的地方会返回404 Not Found
而不是403 Forbidden
,防止某篇私密博客被未经授权的用户发现。
基本认证
curl -u "userID" https://api.Ablog.com
登录失败
使用错误的用户ID/无效的密码进行身份验证将返回401 Unauthorized
curl -i https://api.Ablog.com -u invalid_foo:invalid_bar
HTTP/1.1 401 Unauthorized
{
"message": "Bad credentials",
}
在短时间内检测到多个具有无效凭据的请求后,API会临时拒绝该用户的所有身份验证尝试(包括具有有效凭据的请求):
curl -i https://api.Ablog.com -u valid_username:valid_password
HTTP/1.1 403 Forbidden
{
"message": "Maximum number of login attempts exceeded. Please try again later.",
}
参数
对于GET
请求,任何未在路径中指定为段的参数都可以作为HTTP查询字符串参数传递:
curl -i "https://api.Ablog.com/blogs/12345/67890/issues?state=closed"
在上面的例子中,12345
作为user
的参数,67890
作为blog
的参数,:state
在查询字符串时传递路径。
对于POST
,PATCH
,PUT
,DELETE
请求,不在URL中的参数应该被编码为JSON,同时具有'application/json'
的一个Content-Type
curl -i -u userID -d '{"scopes":["public_blogs"]}' https://api.Ablog.com/authorizations
root
可以通过GET
向根端点发出请求,以获取REST API支持的所有端点类别
curl https://api.Ablog.com
客户端错误
请求调用API时可能会发生三种错误:
- 发送无效的JSON将导致
400 Bad Request
响应
HTTP/1.1 400 Bad Request
Content-Length: 35
{"message":"Problems parsing JSON"}
- 发送错误类型的JSON值将导致
400 Bad Request
响应
HTTP/1.1 400 Bad Request
Content-Length: 40
{"message":"Body should be a JSON object"}
- 发送无效的字段将导致
422 Unprocessable Entity
响应
HTTP/1.1 422 Unprocessable Entity
Content-Length: 149
{
"message": "Validation Failed",
"errors": [
{
"resource": "Issue",
"field": "title",
"code": "missing_field"
}
]
}
所有错误对象都具有资源和字段属性,以便客户端可以知道问题所在。
还有错误代码,可以知道该字段出了什么问题。这些是可能的验证错误代码:
错误名称 | 描述 |
---|---|
missing | 资源不存在 |
missing_field | 尚未设置资源上的必填字段 |
invalid | 字段格式无效 |
already_exists | 另一个资源具有与此字段相同的值。在必须具有某些唯一键(例如标签名称)的资源中可能会发生这种情况 |
HTTP verbs
Verb | Description |
---|---|
HEAD | 可以针对任何资源发出以仅获取HTTP标头信息。 |
GET | 用于检索资源。 |
POST | 用于创建资源。 |
PATCH | 用于通过部分JSON数据更新资源。 |
PUT | 用于替换资源或集合。 |
DELETE | 用于删除资源。 |
分页
某个请求(比如查询某用户所有的博客)可能会返回多个项目,默认情况下将其分为20个项目。
可以使用?page
参数指定其他分页方式。还可以使用?per_page
参数设置最大100的自定义页面大小。
下面的例子查询编号为12345的用户的所有博客,100个项目一页,返回了2页。
curl 'https://api.Ablog.com/user/12345/blogs?page=2&per_page=100'
链接标题
使用Link标头值构成调用很重要,而不是构造自己的URL。
该链接头包括分页信息:
Link: <https://api.Ablog.com/user/12345/blogs?page=3&per_page=100>; rel="next",
<https://api.Ablog.com/user/12345/blogs?page=50&per_page=100>; rel="last"
此Link
响应标头包含一个或多个超媒体链接关系。
限速
对于使用基本身份验证,每小时最多可以进行5000个请求。对于未经身份验证的请求,速率限制允许每小时最多60个请求。
任何API请求返回的HTTP标头都会显示您当前的速率限制状态:
curl -i https://api.Ablog.com/users/octocat
HTTP/1.1 200 OK
Date: Mon, 17 Nov 2013 14:27:06 GMT
Status: 200 OK
X-RateLimit-Limit: 60 //每小时允许您发出的最大请求数
X-RateLimit-Remaining: 56 //当前速率限制窗口中剩余的请求数
X-RateLimit-Reset: 1372700873 //当前速率限制窗口重置的时间,以UTC纪元秒为单位
如果超出速率限制,则会返回错误响应:
HTTP / 1.1 403 Forbidden
Date:Tue,17 Nov 2019 14:50:41 GMT
Status:403 Forbidden
X-RateLimit-Limit:60
X-RateLimit-Remaining:0
X-RateLimit-Reset:1377013266
{
"message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)"
}
条件请求
大多数响应都返回ETag
标头。许多响应还返回Last-Modified
标头。您可以使用这些标头的值分别使用If-None-Match
和If-Modified-Since
标头对这些资源进行后续请求 。如果资源未更改,则服务器将返回304 Not Modified
。
发出条件请求并收到304响应不会计入“ 速率限制”。
curl -i https://api.Ablog.com/user
HTTP/1.1 200 OK
Cache-Control: private, max-age=60
ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
Status: 200 OK
Vary: Accept, Authorization, Cookie
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4996
X-RateLimit-Reset: 1372700873
curl -i https://api.Ablog.com/user -H 'If-None-Match: "644b5b0155e6404a9cc4bd9d8b1ae730"'
HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
Status: 304 Not Modified
Vary: Accept, Authorization, Cookie
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4996
X-RateLimit-Reset: 1372700873
curl -i https://api.Ablog.com/user -H "If-Modified-Since: Thu, 05 Jul 2012 15:31:30 GMT"
HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
Status: 304 Not Modified
Vary: Accept, Authorization, Cookie
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4996
X-RateLimit-Reset: 1372700873