restful API的一些设计思路


什么是restful API

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。" 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。

RESTful是遵循了REST原则的web服务。在设计RESTful API时,需要关注的点有:

  • URL设计
  • 状态码使用
  • 返回结果

URL设计

RESTful 的核心思想是,客户端发出的数据操作指令都是"动词 + 宾语"的结构。比如,

GET /articles 在这个请求中,GET是动词,/articles是宾语。

HTTP动词

动词通常就是6种 HTTP 方法,对应 CRUD 操作。

  • GET:读取(Read)
  • POST:新建(Create)
  • PUT:更新(Update)
  • PATCH:更新(Update),通常是部分更新
  • DELETE:删除(Delete)
  • OPTIONS: 获取Server端支持的方法

在HTTP规范中,动词都是大写。

动词覆盖

有一些比较老的代理只支持HTTP1.0,所以像PATCH、PUT、DELETE这些动词的请求会被丢弃掉或者改为POST。此时可以通过在request的headre中添加:

X-HTTP-Method-Override: METHOD

这个header来告诉服务端真正使用的动词是哪个,例如:

POST /api/Person/4 HTTP/1.1
X-HTTP-Method-Override: PATCH

此时,X-HTTP-Method-Override指定本次的请求是PATCH而不是POST。这个动词的修改,需要放到代码层来实现。

宾语尽量是名词

宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。比如,/articles这个 URL 就是正确的,而下面这些:

  • /getAllCars
  • /createNewCar
  • /deleteAllRedCars

都不是名词,所以不是那么合适了就。 但是也有一些场合不是那么容易找到一个名词的宾语,例如在给一个query做预测的时候,接口一般会写成:

/predict

这样也是合理的。

复数还是单数

关于URL中的资源应该是复数还是单数,这个并没有明确的规定,个人建议用复数。例如:

  • /agents
  • /users

多级URL?

目前有2种API接口设计习惯,一种是使用多级URL来表示资源之间的关系,比如:

/agents/agent_id/bots

另外一种是避免使用多级URL,例如:

/bots?agent_id=<agent_id>

这2种方案各有利弊,建议使用第二种。 在第一种方案中,如果遇到多个级别的关系,那URL会写的很长,例如:

/agents/agent_id/bots/bot_id/intents/intent_id

读这个URL都会浪费不少时间。而在第二种方案中,我们可以将上级关系通过querystring来传,大部分情况下,只要将querystring当作查询条件就可以得到需要的结果了。

状态码

在restful中,HTTP状态码一定要精确。

客户端的每一次请求,服务器都必须给出回应。回应包括 HTTP 状态码和数据两部分。 HTTP 状态码就是一个三位数,分成五个类别。

  • 1xx:相关信息
  • 2xx:操作成功
  • 3xx:重定向
  • 4xx:客户端错误
  • 5xx:服务器错误

这5类基本上可以满足日常开发使用,并且每一个错误码都有一个标准(或者约定)的解释。客户端只要查看状态码,就可以知道本次请求的具体信息。

在日常的API设计中,1xx的状态码一般用不到。可以直接忽略。

2xx状态码

200是最常用的2xx状态码,但是更建议用不同的状态码来响应不同的请求,例如:

  • GET: 200 OK
  • POST: 201 Created
  • PUT: 200 OK
  • PATCH: 200 OK
  • DELETE: 204 No Content

其中,POST返回201,表示生成了新的资源(但是资源并没有随response返回给客户端)。DELETE返回204,表示资源已经被删除不存在了。 此外,还有一个202状态码,表示服务器已经收到请求,但还未进行处理,会在未来再处理,通常用于异步操作。例如:

HTTP/1.1 202 Accepted
{
    "task": {
        "jobId": "job_id",
     }
}

3xx状态码

API 用不到301状态码(永久重定向)和302状态码(暂时重定向,307也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况。 API 用到的3xx状态码,主要是303 See Other,表示参考另一个 URL。它与302和307的含义一样,也是"暂时重定向",区别在于302和307用于GET请求,而303用于POST、PUT和DELETE请求。收到303以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。下面是一个例子。

HTTP/1.1 303 See Other
Location: /api/orders/12345

4xx状态码

4xx状态码表示客户端错误,常见的有:

  • 400 Bad Request: 表示服务端不能理解客户端的请求,未做任何处理。
  • 401 Unauthorized: 用户未提供授权验证凭证,或者没有通过身份验证,比如第三方登录时,没有access_token,则应该返回401。
  • 403 Forbidden:403表示用户通过了身份验证,但是没有访问指定资源的权限。
  • 404 Not Found: 表示所请求的资源不存在,或者不可用。
  • 405 Method Not Allowed:表示请求的动词服务端不接受,比如服务端只接受GET请求,但是客户端发了一个POST请求。
  • 410 Gone: 所请求的资源已经从这个地址转移,不可用。
  • 415 Unsupported Media Type:客户端要求的返回格式不支持,比如服务端只支持JSON返回,但是客户端要求返回XML格式。
  • 429 Too Many Requests: 客户端请求次数超过限额(常见于登录次数受限的场景)

5xx状态码

5xx状态码表示服务端发生异常,一般来说API不会向客户端返回过多信息,因此常见的有2个状态码:

500 Internal Server Error: 客户端发送了请求,但是服务端处理时发生意外。 503 Service Unavaiable: 服务器无法处理请求

服务端响应

服务端响应只返回JSON格式的结果。并且header中要有: Content-Type: application/json这个header。 同时,客户端发请求时,也需要在header中添加Accept: application/json。表示客户端接收的是json格式的返回。

当发生错误时,不要返回200状态码。 经常会看到有些API在发生错误时,返回的也是200的状态码,错误信息在response的数据体里,例如:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }}

这是一个非常不友好的设计,正确的做法应该是,状态码反映发生的错误,具体的错误信息在response的数据题里,例如:

HTTP/1.1 400 Bad Request
Content-Type: application/json


{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }}