- 1. 🔅 Brief
- 2. 🔅 Versioning
- 3. 🔅 Path or Query
- 4. 🔅 Name Convention
- 5. 🔅 Data Convention
- 6. 🔅 Authentication
- 7. 🔅 Envelope (信封)
- 8. 🔅 跨域
- 9. 🔅 Pagination
- 10. 🔅 (Embed or Expansion) & Fields
- 11. 🔅 SEARCH (filtering | sorting | searching)
- 12. 🔅 HTTP status codes
- 13. 🔅 ERROR
- 14. 🔅 Caching
- 15. 🔅 Rate Limit
- 16. 🔅 Custom HTTP Header
- 17. 🔅 REF::
RESTful 更好的架构指导
🔅 Brief
接口是一种双方的约定,一旦定义好之后不能更改,只能升级。
但后台的实现可以随意改变,这样就决定了这种接口定义的重要性。
RESTful 将互联网内容定义为资源,所有的资源都有其唯一的地址(URI),对应每个资源也都有其操作的动作 (Actions: Get/Post/Patch/Update/Delete)。
本文主要参考:
Facebook 用了 GapleQL,不在 RESTful 参考范围内。
🔅 Versioning
在 API 上加入版本信息可以有效的防止用户访问已经更新了的 API,同时也能让不同主要版本之间平稳过渡,目前主流的两种方案:
- HTTP HEAD
- URL Path
/v2
关于是否将版本信息放入 url 还是放入请求头有过争论。学术界说它应该放到 header 里面去,但是如果放到 URL 里面我们就可以跨版本的访问资源了。。(参考 openstack)。
strip 使用的方法就很好:它的 url 里面有主版本信息,同时请求头俩面有子版本信息。这样在子版本变化过程中 url 的稳定的。变化有时是不可避免的,关键是如何管理变化。完整的文档和合理的时间表都会使得 API 使用者使用的更加轻松。
🔅 Path or Query
As per the REST standards
> Path variables
Used for the direct action on the resources, like a contact or a song, will return respective data.
GET | /api/resource/songid |
GET | /api/resource/contactid |
> Query perms/argument
Used for the in-direct resources like metadata of a song, it will return the genres data for that particular song.
GET | /api/resource/songid?metadata=genres |
> 关于 Path 和 Parameter 的最佳实践
- http://www.soapui.org/REST-Testing/understanding-rest-parameters.html
- http://stackoverflow.com/questions/4024271/rest-api-best-practices-where-to-put-parameters
🔅 Name Convention
URL length limit of 2,083 characters.
可分为 4 个部分:
- Path: 一般都是用下划线
snake_case
,参见 github, twitter, weibo, 中划线的例子 Instagram - Query (Parameters): 也建议用
snake_case
,参见 github, weibo, ins。 - Request Body / Response Data (JSON): 建议用
JSON
来上传数据,JSON 中的 key 全部使用小写,多个单词用sanke_case
。
🔅 Data Convention
> Format
request 和 response body 永远是 JSON
。
> Data Type
数据和类型永远匹配,不会出现约定的是 string,而返回数字或 null。
type | default value | |
---|---|---|
number | 0, 1, -1 | |
string | ‘’ | |
array | [] | |
object | {} | |
null | null |
输出的数据结构中 空对象
字段的值一律为 null
。
> Timestamp
timestamp 使用 ISO 8601 的标准输出。
参考 HTTP Date
🔅 Authentication
> JSON Web Token
支持通过登录接口使用账号密码获取,在请求接口时使用 Authorization: Bearer #{token} 头标或者 token 参数的值的方式进行验证。
> OAuth 2.0
Authen 现在都是通过 OAuth 2
了,为了支持 JS 项目,需要通过 Parameter
传递 access_token
。
1 | https://api.weibo.com/2/direct_messages/conversation.json?access_token=2.00jU2OJBfj3PXCb134b9e1734Bx88D&count=30&since_id=3708839931591097&uid=1307864360 |
Credentials if it receives a 401 Unauthorized status code from the server.
OAuth 2 should be used to provide secure token transfer to a third party. OAuth 2 uses Bearer tokens & also depends on SSL for its underlying transport encryption.
🔅 Envelope (信封)
Don’t use an envelope by default, but make it possible when needed
一些时候,客户端拿不到 HTTP Header 信息,所以需要外面包一层,但。。。没这种情况吧。
> 通过参数来开启
1 | http://xx.com/api/v1/users?envelope=true |
返回数据中会在外层带上一些额外信息:
1 | { |
🔅 跨域
跨域是一个比较麻烦的问题。
> “跨域资源共享”(Cross Origin Resource Sharing, CORS)
简单示例:
1 | $ curl -i https://api.example.com -H "Origin: http://example.com" |
预检请求的响应示例:
1 | $ curl -i https://api.example.com -H "Origin: http://example.com" -X OPTIONS |
- https://www.w3.org/TR/cors/
- http://enable-cors.org/
- Guide to Secure Implementation of HTML5’s Cross Origin Requests
- http://newhtml.net/using-cors/
> JSONP | callback (optional)
H5 跨域访问时,需要通过 JSONP 来进行 GET 请求,所以支持 JSONP 还是很有用的。
如果在任何 GET 请求中带有参数 callback ,且值为非空字符串,那么接口将返回如下格式的数据
1 | $ curl http://api.example.com/#{RESOURCE_URI}?callback=foo |
If supplied, the response will use the JSONP format with a callback of the given name. The usefulness of this parameter is somewhat diminished by the requirement of authentication for requests to this endpoint.
Example Values: processTweets
- https://api.twitter.com/1.1/search/tweets.json?q=%23freebandnames&since_id=24012619984051000&max_id=250126199840518145&result_type=mixed&count=4
- https://api.twitter.com/1.1/search/tweets.json?q=#freebandnames&callback=processTweets
🔅 Pagination
> 标准
在输入时,通过 URL Paramaters 在参数中传入 offset, limit, page 来控制分页的数据
- page: The page to return (default: 1)
- limit: The number of entries to return per page (default: 30, maximum: 100)
- offset: offset 和 page 只能用一个,有 offset,page 不用
1 | http://api.example.com/res?page=1&limit=30 |
返回时通过以下字段进行分页的标准输出对象:
1 | { |
> Web Linking
新的标准 RFC5988 推荐将分页信息放到 Link Header 里面:
使用 Link Header 的 API 应该返回一系列组合好了的 url 而不是让用户自己再去拼。这点在基于游标的分页中尤为重要。例如下面,来自 Github 的文档
> 实例
如果是第二页,会返回这样的地址:它返回了 Link,包含了下一页和最后一页。
1 | $ curl -I "https://api.github.com/search/code?q=addClass+user:mozilla&page=2" |
> 更多参考:
- http://blog.lanvige.com/tech/restful/#Pagination
- https://stormpath.com/blog/linking-and-resource-expansion-rest-api-tips/
- http://docs.stormpath.com/rest/product-guide/#ReferenceExpansion
- http://docs.stormpath.com/rest/product-guide/#links
- Grapepi-pagination
🔅 (Embed or Expansion) & Fields
Auto loading related resource representations
用户有时需要一些自定义请求内容,把多个相关资源组合在一起返回使用,如果同时发多个请求,一来耗费网络资源,再者增加出错概率,同时也给客户端带来不便。
所以在实践中加入了 Expansion 的功能,用于实现上述需求。在该需求中,普遍的做法是加入 expand
字段,在其后,通过拼接新的对象名,来实现扩展的对象数据获取。也有不少实践中使用 embed,但意思都是相同的。
RESTful 在这一块实现的并不好,它没有一个共同的库来处理这些相同的逻辑,反之,GraphQL 则重点在这一块,不仅定义清晰,而且像 Applo 这个的库,实现了大多数的基准需求。降低了开发门槛和增加效率。
> Expand
$ expand
GET | /ticket/12?expand=customer,customer.address,assigned_user |
> Fields
$ fields
Directs that related records should be retrieved in the record or collection being retrieved.
客户端不是需要所有的字段,可以让客户自己选。
GET | /tickets?fields=id,subject,customer_name,updated_at |
> 更多参考
- Microsoft OData
- https://docs.atlassian.com/jira/REST/5.2.7/
- https://developer.atlassian.com/confdev/confluence-rest-api/expansions-in-the-rest-api
- http://docs.stormpath.com/rest/product-guide/#link-expansion
- Configurable serializers for “expand” and “fields” parameters as REST API conventions
🔅 SEARCH (filtering | sorting | searching)
像我们之前所做的按各种条件进行筛选,都只是 filtering,当然也有排序。
Method | URL |
---|---|
GET | /tickets?q=return&state=open&sort=-priority,created_at |
> $filter
Specifies an expression or function that must evaluate to ‘true’ for a record to be returned in the collection.
> $orderby
Determines what values are used to order a collection of records.
> $select
Specifies a sub set of properties to return.
> $skip
Sets the number of records to skip before it retrieves records in a collection.
> $top
Determines the maximum number of records to return.
总结
- 对于经常使用的搜索查询,我们可以为他们设立别名,这样会让 API 更加优雅。例如:
get /tickets?q=recently_closed -> get /tickets/recently_closed.
- 关于这些定义,其实最好的参考是 OData http://www.odata.org/documentation/
🔅 HTTP status codes
HTTP defines a bunch of meaningful status codes that can be returned from your API. These can be leveraged to help the API consumers route their responses accordingly. I’ve curated a short list of the ones that you definitely should be using:
The API should always return sensible HTTP status codes. API errors typically break down into 2 types: 400 series status codes for client issues & 500 series status codes for server issues. At a minimum, the API should standardize that all 400 series errors come with consumable JSON error representation. If possible (i.e. if load balancers & reverse proxies can create custom error bodies), this should extend to 500 series status codes.
关于 RESTful Status Code 的更多详解和案例,更写一文:RESTful Status in Deep
🔅 ERROR
Error 一般是在 Status 4XX & 5XX 情况下出现的。
常用的标准格式有两种,Error Object & Error Array:如下
为什么是一个数组?因为 Form 表单式的可能有多个错误,希望一次性返回。
1 | { |
1 | "errors": [{ |
> 参考
1 | { |
Weibo:
1 | { |
🔅 Caching
HTTP provides a built-in caching framework! All you have to do is include some additional outbound response headers and do a little validation when you receive some inbound request headers.
There are 2 approaches: ETag and Last-Modified
> ETag
When generating a request, include a HTTP header ETag containing a hash or checksum of the representation. This value should change whenever the output representation changes. Now, if an inbound HTTP requests contains a If-None-Match header with a matching ETag value, the API should return a 304 Not Modified status code instead of the output representation of the resource.
> Last-Modified
This basically works like to ETag, except that it uses timestamps. The response header Last-Modified contains a timestamp in RFC 1123 format which is validated against If-Modified-Since. Note that the HTTP spec has had 3 different acceptable date formats and the server should be prepared to accept any one of them.
🔅 Rate Limit
为了避免请求泛滥,给 API 设置速度限制很重要。为此 RFC 6585
引入了 HTTP 状态码 429
(too many requests)。加入速度设置之后,应该提示用户,至于如何提示标准上没有说明,不过流行的方法是使用 HTTP 的返回头。
下面是几个必须的返回头(依照 twitter 的命名规则):
- X-Rate-Limit-Limit :当前时间段允许的并发请求数
- X-Rate-Limit-Remaining:当前时间段保留的请求数。
- X-Rate-Limit-Reset:当前时间段剩余秒数
Q: 为什么使用当前时间段剩余秒数而不是时间戳?
时间戳保存的信息很多,但是也包含了很多不必要的信息,用户只需要知道还剩几秒就可以再发请求了这样也避免了 clock skew 问题。
🔅 Custom HTTP Header
- User-Agent
- X-Server-Name
- X-Server-IP
🔅 REF::
- 博士的原文 http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
- ✦ Best Practices for Designing a Pragmatic RESTful API
- ★ HTTP 接口设计指北
- RESTful API 设计最佳实践
- 最佳实践:更好的设计你的 REST API
- API Best Practices Blog
- API Design - Matt Gemmell
- http://codebetter.com/howarddierking/2013/09/12/versioning-restful-services-v2/
- http://msdn.microsoft.com/en-us/library/gg309461.aspx
- Atlassian REST API Design Guidelines version 1
http://zh.wikipedia.org/wiki/REST
http://en.wikipedia.org/wiki/Representational_state_transfer