RESETful API 设计规范

RESTful 是目前最流行的 API 设计规范,用于 Web 数据接口的设计nginx

关于「能愿动词」的使用

为了不歧义,文档大量使用了「能愿动词」,对应的解释以下:json

  • 必须 (MUST):绝对,严格遵循,请照作,无条件遵照;
  • 必定不可 (MUST NOT):禁令,严令禁止;
  • 应该 (SHOULD) :强烈建议这样作,可是不强求;
  • 不应 (SHOULD NOT):强烈不建议这样作,可是不强求;
  • 能够 (MAY)可选 (OPTIONAL) :选择性高一点,在这个文档内,此词语使用较少;
    参见:RFC 2119

域名

API 的根入口点应尽量保持足够简单,这里有两个常见的 URL 根例子:api

https://api.example.com/* 
    
    https://example.com/api/*

若是你的应用很庞大或者你预计它将会变的很庞大,那 应该 将 API 放到子域下(api.example.com)。这种作法能够保持某些规模化上的灵活性。bash

客户端请求

API 返回的数据格式,不该该是纯文本,而应该是一个 JSON 对象,由于这样才能返回标准的结构化数据。因此,客户端但愿服务器回应的 HTTP 头的Content-Type属性要设为application/json。服务器

GET /users/2 HTTP/1.1 
Accept: application/json
Content-Type: application/json

版本控制

全部的 API 必须保持向后兼容,你 必须 在引入新版本 API 的同时确保旧版本 API 仍然可用。因此 应该 为其提供版本支持。restful

目前比较常见的两种版本号形式:app

  • 在 URL 中嵌入版本编号debug

    https://api.example.com/v1/*
    https://api.example.com/v2/*
  • 将版本号放在 HTTP Header 头中 经过媒体类型来指定版本信息设计

    Accept: application/vnd.example.com.v1+json

HTTP 动词

HTTP 请求动词一般就是五种方法,对应 CRUD 操做。代理

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。

针对每个端点来讲,下面列出全部可行的 HTTP 动词和端点的组合

用 URL 定位资源

请求方法 URL 描述
GET /zoos 列出全部的动物园(ID和名称,不要太详细)
POST /zoos 新增一个新的动物园
GET /zoos/{zoo} 获取指定动物园详情
PUT /zoos/{zoo} 更新指定动物园(整个对象)
PATCH /zoos/{zoo} 更新动物园(部分对象)
DELETE /zoos/{zoo} 删除指定动物园
GET /zoos/{zoo}/animals 检索指定动物园下的动物列表(ID和名称,不要太详细)
GET /animals 列出全部动物(ID和名称)。
POST /animals 新增新的动物
GET /animals/{animal} 获取指定的动物详情
PUT /animals/{animal} 更新指定的动物(整个对象)
PATCH /animals/{animal} 更新指定的动物(部分对象)
GET /animal_types 获取全部动物类型(ID和名称,不要太详细)
GET /animal_types/{type} 获取指定的动物类型详情
GET /employees 检索整个雇员列表
GET /employees/{employee} 检索指定特定的员工
GET /zoos/{zoo}/employees 检索在这个动物园工做的雇员的名单(身份证和姓名)
POST /employees 新增指定新员工
POST /zoos/{zoo}/employees 在特定的动物园雇佣一名员工
DELETE /zoos/{zoo}/employees/{employee} 从某个动物园解雇一名员工
超出 Restful 端点的, 应该 模仿上表的方式来定义端点。

资源过滤

若是记录数量不少,服务器不可能都将它们返回给用户。API 应该 提供参数,过滤返回结果。下面是一些常见的参数。
  • ?page=10:指定返回记录的数量
  • ?per_page=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页,以及每页的记录数。
  • ?sortby=name&order=asc:指定返回结果按照哪一个属性排序,以及排序顺序。
  • ?status=closed:指定筛选条件

全部 URL 参数 必须 是全小写,必须 使用下划线类型的参数形式。

分页参数 必须 固定为 pageper_page

常用的、复杂的查询 应该 标签化,下降维护成本。如

GET /trades?status=closed&sort=sortby=name&order=asc

返回码

20x

200 OK

201 Created

对建立新资源的 POST 操做进行响应。应该带着指向新资源地址的 Location 头

202 Accepted

服务器接受了请求,可是还未处理,响应中应该包含相应的指示信息,告诉客户端该去哪里查询关于本次请求的信息

204 No Content

对不会返回响应体的成功请求进行响应(好比 DELETE 请求)

3xx 重定向

40x 客户端错误

400 Bad Request

请求异常,好比请求中的body没法解析

401 Unauthorized

没有进行认证或者认证非法或失效

403 Forbidden

服务器已经理解请求,可是拒绝执行它

404 Not Found

该状态码表示用户请求的资源不存在,如

  • 获取不存在的用户信息 (get /users/9999999)
  • 访问不存在的端点

必须 返回该状态码,若该资源已永久不存在,则 应该 返回 410 响应。

405 Method Not Allowed

所请求的 HTTP 方法不容许当前认证用户访问

409 Gonfilct

该状态码表示由于请求存在冲突没法处理。
如经过手机号码提供注册功能的 API,当用户提交的手机号已存在时,必须 返回此状态码。

410 Gone

表示当前请求的资源已永久不存在。当调用老版本 API 的时候颇有用

413 Request Entity Too Large

该状态码表示服务器拒绝处理当前请求,由于该请求提交的实体数据大小超过了服务器愿意或者可以处理的范围。

此种状况下,服务器能够关闭链接以避免客户端继续发送此请求。

若是这个情况是临时的,服务器 应该 返回一个 Retry-After 的响应头,以告知客户端能够在多少时间之后从新尝试。

414 Request-URI Too Long

该状态码表示请求的 URI 长度超过了服务器可以解释的长度,所以服务器拒绝对该请求提供服务。

415 Unsupported Media Type

一般表示服务器不支持客户端请求首部 Content-Type 指定的数据格式。如在只接受 JSON 格式的 API 中放入 XML 类型的数据并向服务器发送,都 应该 返回该状态码。

该状态码也可用于如:只容许上传图片格式的文件,可是客户端提交媒体文件非法或不是图片类型,这时 应该 返回该状态码:

HTTP/1.1 415 Unsupported Media Type
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 12:09:40 GMT
Connection: keep-alive

{"error_code":41500,"message":"不容许上传的图片格式"}

422 Unprocessable Entity

用来表示校验错误
{
    "message": "422 Unprocessable Entity",
    "errors": {
        "name": [
            "姓名 必须为字符串。"
        ]
    },
    "status_code": 422
}

429 Too Many Requests

该状态码表示用户请求次数超过容许范围。如 API 设定为 60次/分钟,当用户在一分钟内请求次数超过 60 次后,都 应该 返回该状态码。而且也 应该 在响应首部中加上下列头部:

X-RateLimit-Limit: 10 请求速率(由应用设定,其单位通常为小时/分钟等,这里是 10次/5分钟)
X-RateLimit-Remaining: 0 当前剩余的请求数量
X-RateLimit-Reset: 1529839462 重置时间
Retry-After: 120 下一次访问应该等待的时间(秒)

列子

必须 为全部的 API 设置 Rate Limit 支持。

50x 服务器错误

500 Internal Server Error

服务器遇到了一个不曾预料的情况,致使了它没法完成对请求的处理。通常来讲,这个问题都会在服务器端的源代码出现错误时出现。

501 Not Implemented

服务器不支持当前请求所须要的某个功能。当服务器没法识别请求的方法,而且没法支持其对任何资源的请求。

502 Bad Gateway

做为网关或者代理工做的服务器尝试执行请求时,从上游服务器接收到无效的响应。

503 Service Unavailable

因为临时的服务器维护或者过载,服务器当前没法处理请求。这个情况是临时的,而且将在一段时间之后恢复。若是可以预计延迟时间,那么响应中能够包含一个 Retry-After 头用以标明这个延迟时间。若是没有给出这个 Retry-After 信息,那么客户端应当以处理500响应的方式处理它。

注意:503状态码的存在并不意味着服务器在过载的时候必须使用它。某些服务器只不过是但愿拒绝客户端的链接。

504 Gateway timeout

做为网关或者代理工做的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。

注意:某些代理服务器在DNS查询超时时会返回400或者500错误

数据响应格式

错误格式

对于错误数据,默认使用以下结构:

'message' => ':message',          // 错误的具体描述
'errors' => ':errors',            // 参数的具体错误描述,422 等状态提供
'code' => ':code',                // 业务自定义的异常码
'status_code' => ':status_code',  // http状态码
'debug' => ':debug',              // debug 信息,非生产环境提供

422错误码显示

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
    "message": "422 Unprocessable Entity",
    "errors": {
        "username": [
            "姓名 必须为字符串。"
            "姓名 必须介于 4 - 18 个字符之间"
        ],
        "phone": [
            "手机号码 格式不正确。"
        ]
    },
    "status_code": 422
}

403错误码显示

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
    "message": "您无权访问该订单",
    "status_code":"403"
}

429错误码显示

HTTP/1.1 429 Too Many Requests
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1529839462
Retry-After: 290
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 11:19:32 GMT
Connection: keep-alive

{
    "message":"You have exceeded your rate limit.",
    "status_code":429
}

正确输出显示

分页显示

{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "therese28@example.com",
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com",
        }
    ],
    "links":{
        "first": "http://example.com/pagination?page=1",
        "last": "http://example.com/pagination?page=1",
        "prev": null,
        "next": null
    },
    "meta":{
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "http://example.com/pagination",
        "per_page": 15,
        "to": 10,
        "total": 10
    }
}
相关文章
相关标签/搜索