restful api的实践总结
我最早接触RESTful API这个东西,是2011年初在中国传媒大学读研的时候,一起做项目的同学侯振宇告诉我的。到现在已经7年了。作为一种设计风格,RESTful现在依然很流行。在带学生做可视分析系统的时候,我们依然在采用这种模式。不过很多学生对RESTful的概念还不是很清晰。为此我觉得有必要写篇说明一下。
目录:
- 什么是restful API
- restful API 设计指南
- 极端情况下查询是该用get还是post?
- 参考文章
一, 什么是RESTful API?
RESTful API是目前比较成熟的一套互联网应用程序的API设计风格。它的目的就是让符合该风格的web应用程序的接口变得构清晰、符合标准、易于理解、扩展方便。
下面是github网站的restful风格API的示例文档,我想能够给想要了解什么是restful api的朋友非常直观的感受。那就是,基于restful风格设计的API,在最佳的应用场景中,你甚至不用阅读API说明文档,只要阅读这个URL,就能搞清楚这个API是做什么的,有哪些参数。
{ "current_user_url": "https://api.github.com/user", "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}", "authorizations_url": "https://api.github.com/authorizations", "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}", "emails_url": "https://api.github.com/user/emails", "emojis_url": "https://api.github.com/emojis", "events_url": "https://api.github.com/events", "feeds_url": "https://api.github.com/feeds", "following_url": "https://api.github.com/user/following{/target}", "gists_url": "https://api.github.com/gists{/gist_id}", "hub_url": "https://api.github.com/hub", "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}", "issues_url": "https://api.github.com/issues", "keys_url": "https://api.github.com/user/keys", "notifications_url": "https://api.github.com/notifications", "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}", "organization_url": "https://api.github.com/orgs/{org}", "public_gists_url": "https://api.github.com/gists/public", "rate_limit_url": "https://api.github.com/rate_limit", "repository_url": "https://api.github.com/repos/{owner}/{repo}", "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}", "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}", "starred_url": "https://api.github.com/user/starred{/owner}{/repo}", "starred_gists_url": "https://api.github.com/gists/starred", "team_url": "https://api.github.com/teams", "user_url": "https://api.github.com/users/{user}", "user_organizations_url": "https://api.github.com/user/orgs", "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}", "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}" }
RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构。例如,GET/user, GET是动词,表示要做什么,user是名词,表示代表了什么资源。
为了避免随意定义动词造成的语义混乱,RESTful要求API设计者都使用HTTP协议指定的5种方法作为动词。这五种方法是:
GET:读取某个资源(Read)
POST:新建某个资源(Create)
PUT:更新某个资源(Update)
PATCH:部分更新某个资源(Update)
DELETE:删除某个资源(Delete)
有些客户端只能使用GET和POST这两种方法。那么PUT、PATCH、DELETE就得用POST方法模拟。这时,客户端发出的 HTTP 请求,要加上X-HTTP-Method-Override
属性,告诉服务器应该使用哪一个动词,覆盖POST
方法。
限定了动词的情况下,API设计者只需要想好用什么名词来表示资源就可以了。就像github做的那样。
二, restful API 设计指南
(1) 简单CRUD的API对比:
非restful风格url:
查询一个用户 /user/query?name=tom (GET)
查询用户详情 /user/query?id=1(GET)
创建一个用户 /user/create?name=tom (POST)
修改一个用户 /user/update?id=1&name=jerry (POST)
删除一个用户 /user/delete?id=1 (GET)restful风格的url:
查询一个用户 /user?name=tom (GET)
查询用户详情 /user/1 (GET)
创建一个用户 /user (POST)
修改一个用户 /user/1 (PUT)
删除一个用户 /user/1 (DELETE)
是不是发现restful风格的API简洁了很多?
(2)对于条件有2项的查询,避免写成多级的URL,比如比如获取某个作者的某一类文章,不应该写成:
GET /authors/12/categories/2
而应写成:
GET /authors/12?categories=2
(3)如果查询条件大于3项怎么办?
这里我们参考github设计较为复杂查询的例子:
"user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
这是一个用户查询接口,有最多5个参数:query(查询内容),page(页码),per_page(每页显示多少个结果),sort (排序方式), order(指令)。除了query以外其他4个参数都是可选的。github的接口设计方式是,把这些参数组成一个长字符串来发送,中间用&和逗号分隔。
三,极端情况下查询是该用get还是post?
由于我们做的是数据分析项目,因此经常会遇到大规模的数据查询,并且查询参数非常复杂。
考虑一种极端情况:查询参数是一个超长的数组,就像这个样子:[931, 241, 1181,1131,6311 ......] 这个数组中每个数字都是一个节点id,没有顺序性科研,整个可能有数百、上千个字符那么长,。最后要求API返回该数组提供的每个节点id对应的全部统计项。返回结果大概是这样:
[
{id:931, degree_in:31, degree_out:101, total_num: 3111},
{id:241, degree_in:51, degree_out:90, total_num: 123},
......
]
像这样的复杂查询,该怎么实现?
显然,这不是一个通用API,需要文档才能读懂结果。
其次,查询参数过长,很可能超过get请求2K长度的限制。
所以,这种时候就别拘泥于restful标准下只有get请求才能做查询的要求了。最好的解决方法还是将查询参数封装成JSON,使用POST请求传输,然后返回JSON。
看看stackoverflow上关于这种complex query问题的解答,其他回答者也是推荐用post
https://stackoverflow.com/questions/37166714/restful-api-complex-queries
https://stackoverflow.com/questions/381983/what-is-the-best-way-to-create-restful-complex-queries
POST is the method to use for any operation that isn't standardized by HTTP. Retrieval is standardized by the GET method, so using POST to retrieve information that corresponds to a potential resource is never RESTful. In theory, you should use GET, regardless of how convoluted your URI turns out to be.
However, since you're performing a query for which there isn't a single resource you could perform a GET to, it seems fine to use POST, as long as you're aware of the disadvantages and your documentation is clear about it. Frankly, I think using a POST is much clearer than encoding that payload as a JSON + base64 and sending it as a querystring merely for purism.
The real issue with using POST is when people use it in such a way that it avoids or prevents using a real URI. That doesn't seem to be the issue in your case, since you have a valid URI for the collection, but the semantics of your query are too complex to be expressed easily.
If you decide to go with GET, there's a catch. While the HTTP specs establish no limit for URIs, most implementations do, and you might hit that limit if you need to feed all those parameters as a querystring. In that case, it's RESTful to circumvent limitations of the underlying implementation, as long as that's decoupled from your application. A convention for doing what you want would be to use the POST method with the payload you described above, and the
X-HTTP-Method-Override: GET
header.
如果你是restful原教旨主义者,那么有两条路可走: 方法一,还是用GET请求,用JSON + base64 的方式构造请求参数,如果请求参数太长,就分几次传输。显然这种方式是非常麻烦的。方法二,用post请求,然后加上 X-HTTP-Method-Override: GET,来假装它是一个满足restful风格要求的GET请求。既满足了原教旨主义需求,又节省了功夫,岂不美哉?
当然我觉得我不是一个原教旨主义者。
四,参考文章
以下是一些非常好的参考文章,可以用来扩大视野,更好地设计API