RESTful
是目前最流行的 API 规范,适用于 Web 接口规范的设计。让接口易读,且含义清晰。本文将介绍如何设计易于理解和使用的 API,而且借助 Docker api 的实践说明。docker
它的核心思想就是客户端发出的数据操做指令都是「动词 + 宾语」的结构,好比 GET /articles这个命令,GET是动词,/articles是宾语。json
动词一般来讲就是五种 HTTP 方法,对应咱们业务接口的 CRUD 操做。而宾语就是咱们要操做的资源,能够理解成面向资源设计。咱们所关注的数据就是资源。ubuntu
正确的例子api
有些客户端只能使用GET和POST这两种方法。服务器必须接受 POST 模拟其余三个方法(PUT、PATCH、DELETE)。bash
这时,客户端发出的 HTTP 请求,要加上 X-HTTP-Method-Override
属性,告诉服务器应该使用哪个动词,覆盖 POST 方法。服务器
就是 API 的url ,是 HTTP 动词做用的对象,因此应该是名词。例如 /books 这个 URL 就是正确的,而下面的 URL 不是名词,都是错误的写法。restful
错误示范:app
GET /getAllUsers?name=jl
POST /createUser
POST /deleteUSer
复制代码
URL 是名词,那么是使用复数仍是单数?ide
没有统一的规定,可是咱们一般操做的数据多数是一个集合,好比 GET /books
,因此咱们就使用复数。网站
统一规范,建议都使用复数 URL, 好比 获取 id = 2 的书 GET /books/2
要好于 GET /book/2
。
有时候咱们要操做的资源多是有多个层级,所以很容易写多级 URL,好比获取某个做者某种分类的文章。
GET /authors/2/
categories/2 获取做者ID = 2 分类 = 2 的文章
这种 URL 不利于拓展,语义 也不清晰。
更好的方式就是 除了第一级,其余级别都是经过查询字符串表达。
正确方式:
GET /authors/12?categories=2
查询已发布的文章
错误 写法: GET /artichels/published
正确写法: GET /artichels?published=true
下面是一些常见的参数。
- ?limit=10:指定返回记录的数量
- ?offset=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪一个属性排序,以及排序顺序。
- ?animal_type_id=1:指定筛选条件
参数的设计容许存在冗余,即容许API路径和URL参数偶尔有重复。好比,GET /zoo/ID/animals 与 GET /animals?zoo-id=ID 的含义是相同的。推荐后者,避免出现多级URL。
客户端的请求,服务求都必须响应,包含 HTTP 状态码和数据。
HTTP 状态码就是一个三位数,分红五个类别。
200状态码表示操做成功,可是不一样的方法能够返回更精确的状态码。
4xx状态码表示客户端错误,主要有下面几种。
5xx状态码表示服务端错误。通常来讲,API 不会向用户透露服务器的详细信息,因此只要两个状态码就够了。
API 返回的数据格式,不该该是纯文本,而应该是一个 JSON 对象,由于这样才能返回标准的结构化数据。因此,服务器回应的 HTTP 头的 Content-Type 属性要设为 application/json 。
客户端请求时,也要明确告诉服务器,能够接受 JSON 格式,即请求的 HTTP 头的ACCEPT 属性也要设成 application/json。下面是一个例子。
有一种不恰当的作法是,即便发生错误,也返回200状态码,把错误信息放在数据体里面,就像下面这样。
错误例子:
HTTP/1.1 200 OK
ConteNTP-Type: application/json
{
"status": "fail",
"msg": "错误"
}
复制代码
上面代码中,解析数据体之后,才能得知操做失败。
这张作法实际上取消了状态码,这是彻底不可取的。正确的作法是,状态码反映发生的错误,具体的错误信息放在数据体里面返回。下面是一个例子。
正确方式:
HTTP/1.1 400 Bad Request
ConteNTP-Type: application/json
{
"status": "fail",
"msg": "错误"
}
复制代码
接下来咱们分析 docker api 对于 restful 的使用,助于咱们在实际工做中合理设计。
docker 文档 url :docs.docker.com/engine/api/…
GET /v1.19/containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1
经过 all=1&before=8dfafdbc3a40&size=1 过滤容器数据
GET /containers/(id or name)/json
GET /v1.19/containers/4fa6e0f0c678/json HTTP/1.1
GET /v1.19/containers/4fa6e0f0c678/top HTTP/1.1
假如想过滤进程等能够经过查询字符串实现
GET /v1.19/containers/4fa6e0f0c678/top?ps_args=aux HTTP/1.1
返回的数据
HTTP/1.1 200 OK
Content-Type: application/json
{
"Titles" : [
"USER","PID","%CPU","%MEM","VSZ","RSS","TTY","STAT","START","TIME","COMMAND"
]
"Processes" : [
[
"root","13642","0.0","0.1","18172","3184","pts/0","Ss","17:03","0:00","/bin/bash"
],
[
"root","13895","0.0","0.0","4348","692","pts/0","S+","17:15","0:00","sleep 10"
]
],
}
复制代码
POST /containers/create
POST /v1.19/containers/create HTTP/1.1
Content-Type: application/json
Content-Length: 12345
{
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"FOO=bar",
"BAZ=quux"
],
"Cmd": [
"date"
],
"Entrypoint": null,
"Image": "ubuntu",
"Labels": {
"com.example.vendor": "Acme",
"com.example.license": "GPL",
"com.example.version": "1.0"
},
"Volumes": {
"/volumes/data": {}
}
}
复制代码
根据容器 id 删除一个容器,v是请求是否删除 容器 volumes
DELETE /v1.19/containers/16253994b7c4?v=1 HTTP/1.1
Query parameters:
false
.false
.false
.