返回介绍

RESTful API 设计指南

发布于 2025-04-20 18:52:15 字数 7107 浏览 0 评论 0 收藏

API 一旦发布其结构将很难修改,因此设计和实现一个符合规范、灵活、友好的 API,是一件非常重要的事情。本节我们来聊聊 API 设计上一些被忽略的细节、使用时的误区和一些实践。

使用名词来表示资源

URI 不应该包含动词。动词应该通过不同的 HTTP 方法来体现,如下是几种常见的错误用法:

GET/getusers/1
POST/users/1/delete
POST/users/1/create

正确的用法为:

GET/users/1
DELETE/users/1
PUT/users/1

关注请求头

一定要看请求头信息,并给予正确的状态码。举个例子,假设服务器端只能返回 JSON 格式,如果客户端的头信息的 Accept 字段要求返回 application/xml,这个时候就不应该返回 application/json 类型的数据,而应该返回 406 错误。

合理使用请求方法和状态码

不能一味使用 GET 和 POST,返回 200,不合理的请求方法可能让未来的维护者或者合作方感到迷惑。关于方法语义的说明,可参考表 5.1。

表 5.1 方法语义的说明

方法语义
OPTIONS用于获取资源支持的所有 HTTP 方法
HEAD用于只获取请求某个资源返回的头信息
GET用于从服务器获取某个资源的信息:
1.完成请求后,返回状态码 200 OK
2.完成请求后,需要返回被请求的资源详细信息
POST用于创建新资源:
1.创建完成后,返回状态码 201 Created
2.完成请求后,需要返回被创建的资源详细信息
PUT用于完整的替换资源或者创建指定身份的资源,比如创建 id 为 123 的某个资源:
1.如果是创建了资源,则返回 201 Created
2.如果是替换了资源,则返回 200 OK
PATCH用于局部更新资源:
1.完成请求后,返回状态码 200 OK
2.完成请求后,需要返回被修改的资源详细信息
3.完成请求后,需要返回被修改的资源详细信息
DELETE用于删除某个资源,完成请求后返回状态码 204 No Content

正确地使用 REST

REST 服务器是无状态的。在有分页的时候,它并不能知道你当前访问到了什么位置,前一页和后一页的地址是什么,这个关系需要客户端来维护。但是“下一页资源”这样的业务逻辑是需要服务端来提供的。例如,下面例子的返回是不完整的:

Status:200 OK
[
    {
        "id":1,
        "url":"https://api.dongwm.com/users/1/",
    },
    {
        "id":2,
        "url":"https://api.dongwm.com/users/2/",
    }
]

返回的结果没有告诉我们是否有下一页,也没有告诉我们符合条件的记录总数。可以添加 Link 和 X-Total-Count 头来提供这样的功能:

Status:200 OK
X-Total-Count:210
Link:<https://api.dongwm.com/users?page=2>;rel="next",
    <https://api.dongwm.com/users?page=5>;rel="last"
[
    {
        "id":1,
        "url":"https://api.dongwm.com/users/1/",
    },
    {
        "id":2,
        "url":"https://api.dongwm.com/users/2/",
    }
]

rel 的值还可以是 first、self 和 prev,客户端只需要根据 Link 中提供的链接就可以找到全部的符合条件的条目。

还有两处容易出错的地方需要留意:

  • 使用“201 Created”响应时,应该带 Location,指向新建资源的地址。
  • 使用“405 Method Not Allowed”响应时,应该带有 Allow 头,告诉客户端对该资源有效的 HTTP 方法。

对输出的结果不再包装

body 中应该直接放数据,不要多层封装。下面有个不恰当的响应的例子:

HTTP/1.1 200 OK
{
    'success':true,
    'data':{'id':1, 'name':'xiaoming'},
}

直接返回 data 中的数据就好了:

HTTP/1.1 200 OK
{'id':1, 'name':'xiaoming'}

因为通过状态码“200 OK”就可以知道结果是正确的,也没有必要添加“success”字段。

如果 API 使用者确实由于某种原因无法访问返回头,或者 API 需要支持交叉域请求(例如通过 jsonp),这两种情况下还是需要包装的。

不要做出错误的提示

当访问出错或者响应的结果不符合预期时,不应该返回 200 作为状态码。哪怕返回的结果中也包含了错误原因,因为在没有充分的文档说明前提下,客户端可能会缓存成功的 HTTP 请求。

使用嵌套对象序列化

对象应该合理地嵌套,不应该都在一个层次上。如下的格式是不正确的:

{
    'id':1,
    'post_id':'10001',
    'post_name':'Post1',
    'post_content':'this is a post'
}

尽可能把相关联的资源信息内联在一起。应该把 post 作为一个键:

{
    'id':1,
    'post':{
        'id':'10001',
    'name':'Post1',
    'content':'this is a post'
    }
}

版本

常见的区分版本的方法有三种:

  • 保存在 URI 中。比如“https://api.dongwm.com/api/v2”。
  • 放在请求头中。比如 GitHub 的用法:“Accept:application/vnd.github.v3+json”。
  • 自定义请求头。比如,“X-Api-Version:1”。

第三种方式不推荐,推荐使用第一种。

URI 失效和迁移

随着业务发展,会出现一些 API 失效或者迁移。对失效的 API,应该返回“404 not found”或“410 gone”;对迁移的 API,返回 301 重定向。

信息过滤

URL 通常最好越简短越好,对结果过滤、排序和搜索相关的功能都应该通过参数实现。一些常见的参数用法如表 5.2 所示。

表 5.2 常见的参数及其用法

参数含义
offset=0&limit=10指定返回记录的数量,offset 也可以用 start 这个名字
offset=10指定返回记录的开始位置
page=2&per_page=100指定第几页,以及每页的记录数
sortby=name&order=asc指定返回结果按照哪个属性排序,以及排序的顺序
sort=age,desc多个排序条件组合

速度限制

为了避免请求泛滥,给 API 设置速度限制很重要。为此,RFC 6585(https://tools.ietf.org/html/rfc6585 )引入了 HTTP 状态码 429(too many requests)。加入速度限制功能之后,应该提示用户。可以参照 GitHub 的返回头,如下所述。

  • X-RateLimit-Limit:当前时间段允许的并发请求数。
  • X-RateLimit-Remaining:当前时间段保留的请求数。
  • X-RateLimit-Reset:当前时间段剩余的秒数。

我们看一个真实的例子:

> curl-i https://api.github.com/users/whatever
HTTP/1.1 200 OK
Date:Mon, 01 Jul 2013 17:27:06 GMT
Status:200 OK
X-RateLimit-Limit:60
X-RateLimit-Remaining:56
X-RateLimit-Reset:1372700873

缓存

数据内容在一段时间不会变动,这个时候我们就可以合理地减少 HTTP 响应内容。应该在响应头中携带 Last-Modified、ETag、Vary、Date 等信息,客户端可以在随后请求这些资源时,在请求头中使用 If-Modified-Since、If-None-Match 等来确认资源是否经过修改。如果资源没有做过修改,那么就可以响应“304 Not Modified”,并且不在响应实体中返回任何内容。

我们看看 GitHub 的用法(隐藏了无关的自定义头):

> http https://api.github.com/users/dongweiming--headers
HTTP/1.1 200 OK
Cache-Control:public, max-age=60, s-maxage=60
Content-Encoding:gzip
Content-Security-Policy:default-src 'none'
Content-Type:application/json;charset=utf-8
Date:Thu, 04 Feb 2016 14:05:03 GMT
ETag:W/"393f2b88fc9073927d2dda6f43318c8a"
Last-Modified:Sat, 16 Jan 2016 09:14:15 GMT
Server:GitHub.com
Status:200 OK
Transfer-Encoding:chunked
Vary:Accept
Vary:Accept-Encoding

我们可以通过 If-Modified-Since 实现缓存:

> http https://api.github.com/users/dongweiming 'If-Modified-Since:Thu, 04 Feb 2016
    14:05:03 GMT'--headers
HTTP/1.1 304 Not Modified
Cache-Control:public, max-age=60, s-maxage=60
Content-Security-Policy:default-src 'none'
Date:Thu, 04 Feb 2016 14:06:43 GMT
Last-Modified:Sat, 16 Jan 2016 09:14:15 GMT
Server:GitHub.com
Status:304 Not Modified
Vary:Accept-Encoding

当生成请求的时候,在 HTTP 头里面加入 ETag。其中包含请求的校验和与哈希值,这个值在输入变化的时候也应该变化。如果输入的 HTTP 请求包含 If-None-Match 头以及一个 ETag 值,那么 API 应该返回“304 Not Modified”状态码,而不是常规的输出结果:

> http https://api.github.com/users/dongweiming 'If-None-Match:"393
    f2b88fc9073927d2dda6f43318c8a"'--headers
HTTP/1.1 304 Not Modified
Cache-Control:public, max-age=60, s-maxage=60
Content-Security-Policy:default-src 'none'
Date:Thu, 04 Feb 2016 14:07:17 GMT
ETag:"393f2b88fc9073927d2dda6f43318c8a"
Last-Modified:Sat, 16 Jan 2016 09:14:15 GMT
Server:GitHub.com
Status:304 Not Modified
Vary:Accept-Encoding

并发控制

缺少并发控制的 PUT 和 PATCH 请求可能导致“更新丢失”。这个时候可以使用 Last-Modified 和 ETag 头来实现条件请求。具体原则如下:

  • 客户端发起的请求如果没有包含 If-Unmodified-Since 或者 If-Match 头,就返回状态码“403 Forbidden”,在响应正文中解释为何返回该状态码。
  • 客户端发起的请求所提供的 If-Unmodified-Since 或者 If-Match 头与服务器记录的实际修改时间或 ETag 值不匹配时,返回状态码“412 Precondition Failed”。
  • 客户端发起的请求所提供的 If-Unmodified-Since 或者 If-Match 头与服务器记录的实际修改时间或 ETag 的历史值匹配,但资源已经被修改过时,返回状态码“409 Conflict”。
  • 客户端发起的请求所提供的条件符合实际值,就更新资源,响应“200 OK”或者“204 No Content”,并且包含更新过的 Last-Modified 和/或 ETag 头,同时包含 Content-Location 头,其值为更新后的资源 URI。

本节参考了开源项目“HTTP 接口设计指北”(http://bit.ly/2azgIBP ),如果想了解更多内容,请阅读此项目。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。