前备知识
在讨论 API 设计之前,我们总结了一些与之相关的知识点。为了后续内容更容易理解,请先阅读掌握以下内容。
URI/URL
格式
scheme://[username[:password]@]host[:port][/path][?query][#fragment]
部分 | 是否必须 | 说明 |
---|---|---|
scheme | 是 | 传输协议,或代表处理请求的应用程序,如:http 、https 、ftp 、smtp 、git 、mongo |
username | 否 | 用户名 |
password | 否 | 密码 |
host | 是 | 主机名(域名或 IP 地址) |
port | 否 | 端口 |
path | 否 | 访问路径 |
query | 否 | 查询参数,以 ? 开始,参数通过 & 分隔,参数名与参数值通过 = 分隔,如:?size=20&page=2 |
fragment | 否 | 数据片段的 Hash 值,在网页中是一个锚点的名称,以 # 开始,如:#bottom |
注意:fragment 部分不会传递给服务器
应用示例
- 网站的首页
http://www.example.com/index.html
在浏览器中使用 HTTP 协议时端口号默认为 80
- 网站的登录页面
https://www.example.com/login.html
在浏览器中使用 HTTPS 协议时端口号默认为 443
- 调用订单列表 Web 服务
https://api.example.com/users/jinhy/orders
- 连接到 MongoDB
mongo://admin:123456@192.168.1.1:27017/dbname?replicaSet=rs01&wtimeoutMS=0&readPreference=secondaryPreferred
- 在移动端调起应用的【我的订单】页面
my-app://my-app/my-orders
片段与相对路径
前提:浏览器当前访问 URL 为 http://www.example.com/items/categories/8587.html?page=2#top
超链接(href) | 等同于 | 说明 |
---|---|---|
#bottom |
http://www.example.com/items/categories/8587.html?page=2#bottom |
页面不会刷新,跳转到同一页面的另一处 |
?page=3 |
http://www.example.com/items/categories/8587.html?page=3 |
仅更新查询参数 |
./8588.html |
http://www.example.com/items/categories/8588.html |
. 代表同级路径 |
../css/common.css |
http://www.example.com/items/css/common.css |
.. 代表返回上一级路径 |
/css/common.css |
http://www.example.com/css/common.css |
以 / (即虚拟主机的根路径)开始即相对于根路径的位置 |
//api.example.com/items/categories/8587?page=2 |
http://api.example.com/items/categories/8587?page=2 |
以 // 开始代表使用的 scheme 相同 |
HTTP 请求与响应
请求(Request)报文格式
行 | 格式 |
---|---|
第1行 | <请求方法> <访问路径及查询参数> <报文协议版本> |
第2行~第一个空行 | <请求头字段名>: <请求头字段值> |
空行 | <空行> |
第一个空行~最后 | <请求数据> |
HTTP 报文中各部分之间的换行必须为
\r\n
(即回车(return)
并换行(newline)
)
示例:查询用户头像缩略图列表
GET /users/jinhy/photos?category=avatar&type=thumbnail HTTP/1.1
Host: api.example.com
Origin: http://www.example.com
Referer: http://www.example.com/my-profile.html
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36
Accept: application/json
这是一个 GET 请求,不包含数据部分。Stackoverflow: HTTP GET with request body
请求方法
Wikipedia: Hypertext Transfer Protocol
GET
:取得POST
:创建PUT
:整体更新PATCH
:部分更新DELETE
:删除OPTIONS
:用于获取允许使用的方法、请求头,测试服务器响应速度HEAD
:仅取得头信息
常用请求头
请求头 | 说明 |
---|---|
Host |
访问的主机名或 IP 地址,如:api.example.com |
Origin |
发起请求的页面所在的域,如:http://www.example.com |
Referer |
发起请求的页面的 URL,如:http://www.example.com/my-profile.html |
User-Agent |
发起请求的客户端(即用户代理软件)的描述字符串 |
Accept |
能够接受的返回数据的类型,如:text/html 、text/css 、text/javascript 、application/json |
Content-Type |
发送数据的类型,如:application/x-www-form-urlencoded 、multipart/form-data 、application/xml 、application/json |
Content-Length |
数据内容的长度(字节),必须与实际匹配 |
Referer
正确的拼写应该是Referrer
,一些 HTTP 服务器实现支持使用Referrer
作为关键字从请求里取得Referer
的值
常用的请求数据类型(Content Type)
application/x-www-form-urlencoded
数据被编码为 Query 参数形式(因此键和值会被转义),是 HTML 的 FORM 元素的默认编码类型,如点击以下页面中的【登录】按钮后
<form method="post" action="/authorizations">
<input type="text" name="username" value="jinhy"><br>
<input type="password" name="password" value="123456"><br>
<input type="submit" value="登录">
</form>
请求的报文如下
POST /authorizations HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Referer: https://www.example.com/login.html
User-Agent: Mozilla/5.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
username=jinhy&password=123456
multipart/form-data
请求的数据部分被分为若干部分,各部分通过指定的分隔符分隔,每一个部分又包含头(即元数据)、空行及数据,通过 HTML 的 FORM 上传文件时必须将编码类型设置为此类型,如点击以下页面中的【登录】按钮后
<form method="post" action="/authorizations" enctype="multipart/form-data">
<input type="text" name="username" value="jinhy"><br>
<input type="password" name="password" value="123456"><br>
<input type="file" name="avatar" value="photo.png"><br>
<input type="submit" value="登录">
</form>
请求的报文如下
POST /authorizations HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Referer: https://www.example.com/login.html
User-Agent: Mozilla/5.0
Content-Type: multipart/form-data; boundary=RANDOM_BOUNDARY_STRING_GENERATE_BY_USER_AGENT
Content-Length: 30
--RANDOM_BOUNDARY_STRING_GENERATE_BY_USER_AGENT
Content-Disposition: form-data; name="username"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
jinhy
--RANDOM_BOUNDARY_STRING_GENERATE_BY_USER_AGENT
Content-Disposition: form-data; name="username"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
123456
--RANDOM_BOUNDARY_STRING_GENERATE_BY_USER_AGENT
Content-Disposition: form-data; name="avatar"; filename="photo.png"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
<文件的字节流数据>
--RANDOM_BOUNDARY_STRING_GENERATE_BY_USER_AGENT--
注意
- HTTP 请求是以流的形式传输给 HTTP 服务器进程的;
- Node.js 在接收到请求后会构造一个 request 对象,并解析请求头,数据部分会通过触发 request 对象的
data
事件以buffer
的形式传递给data
事件的监听器; buffer
的大小通常为 32KB,由于文件通常会比较大,所以可能会触发多次data
事件,数据接收完毕后 request 的end
事件将被触发;- 取得文件的完整数据后再做处理是不可取的,因为这可能会占用服务器大量的内存空间,甚至导致服务器崩溃(如上传一个 4GB 的 DVD 文件),正确的处理方式是在磁盘上建立一个写入流,并将
buffer
写入到文件。
application/xml、application/json
在浏览器中,这两种类型数据随着 AJAX(X
即 XML
)技术的出现被使用,请求报文的数据部分相应的为 XML 或 JSON 字符串。
Node.js 的 Express 模块对 Request 对象的封装
路由定义
POST /users/:ownerId/following
请求报文
POST /users/jinhy/following?foo=1&bar[]=hello&bar[]=world HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Referer: https://www.example.com/login.html
User-Agent: Mozilla/5.0
Content-Type: application/json; charset=UTF-8
Content-Length: 40,
Cookies: language=zh-CN; page-size=50
{"userId":"jiangxg"}
Request 对象(IncomingMessage 类实例)
IncomingMessage {
method: 'POST',
url: '/users/jinhy/following?foo=1&bar[]=hello&bar[]=world',
path: '/users/jinhy/following',
headers: {
host: 'api.example.com',
origin: 'https://www.example.com',
referer: 'https://www.example.com/login.html',
'user-agent': 'Mozilla/5.0',
'content-type': 'application/json; charset=UTF-8',
'content-length': 40
},
cookies: { // 由 cookie-parser 中间件解析
language: 'zh-CN',
'page-size': '50'
},
params: { // 由 express.Router 解析
ownerId: 'jinhy'
},
query: { // 由 express.Router 解析
foo: '1',
bar: [ 'hello', 'world' ]
},
body: { // 由 body-parser 中间件解析
userId: 'jiangxg'
},
...
}
响应(Response)报文格式
行 | 格式 |
---|---|
第1行 | <报文协议版本> <状态码> <状态描述> |
第2行~第一个空行 | <请求头字段名>: <请求头字段值> |
空行 | <空行> |
第一个空行~最后 | <响应数据> |
HTTP 状态码
W3: Status Code Definitions
Wikipedia: List of HTTP status codes
2XX
:成功3XX
:重定向4XX
:客户端错误5XX
:服务器错误
示例:返回请求的头像缩略图列表
HTTP/1.1 200 OK
Date: Sat, 11 Nov 2017 10:35:01 GMT
Content-Type: application/json; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 114
Last-Modified: Sat, 10 Nov 2017 11:12:13 GMT
Server: nginx/1.6.3
ETag: W/"156-Ks3xxpBRyr3JQG+QTteaFQ"
X-Powered-By: Express
{"success":true,"data":["/users/jinhy/photos/avatars/001.thumb.png","/users/jinhy/photos/avatars/002.thumb.png"]}
编程范型
编程范型(Programming Paradigm)
Wikipedia: Programming Paradigm Wikipedia: Comparison of Programming Paradigms
范型 | 说明 | 代表语言 |
---|---|---|
指令式(Imperative) | 直接更改计算状态 | C 、C++ 、Java 、Python 、Ruby 、PHP |
结构化(Structured) | 逻辑化指令式编程 | C 、C++ 、Java 、Python 、BASIC |
过程化(Procedural) | 派生自结构化编程,基于模块化编程 及存储过程调用 的概念 |
C 、C++ 、Python 、PHP |
函数式(Functional) | 将计算机运算视为数学上的函数计算,避免使用程序状态及易变对象 相关概念:Lambda 演算 |
C++ 、Scala 、Kotlin 、Python 、Ruby 、JavaScript |
事件驱动(Event-Driven) | 流程由事件控制 | JavaScript 、Visual Basic |
面向对象(Object-Oriented) | 抽象为对象之间的相互操作 | C++ 、Java 、Scala 、Kotlin 、Python 、Ruby 、PHP 、JavaScript 、Lua |
声明式(Declarative) | 定义程序逻辑,但无控制流程 | SQL 、CSS 、正则表达式 |
多种编程范型是并存的,很多语言是
多范型编程
,没有一种范型或范型组合能够解决所有问题(没有银弹
),包括 OOP。
RPC vs SOAP vs REST
概念 | 解释 | 说明 | 备注 |
---|---|---|---|
RPC | Remote Procedure Call 远程过程调用 |
允许运行于一台计算机的程序调用另一台计算机的子程序而无需额外为这个交互作用编程的通信协议 | |
RMI | Remote Method Invoke 远程方法调用 |
OOP 的 RPC | |
XML-RPC | 基于 HTTP、使用 XML 封装的 RPC | 由微软发表于 1998 年,后逐步并入 SOAP | |
Web Service | 用以支持网络间不同计算机互动操作的软件系统 | ||
SOAP | Simple Object Access Protocol 简单对象访问协议 |
应用于计算机网络 Web Service 中 | 由微软及 IBM 发表于 1998 年 |
XMLHttpRequest | 用于在 Web 浏览器与服务器之间传输数据的 API 对象 | 由微软于 1998 年开始应用于 Outlook Web App | |
AJAX | Asynchronous JavaScript and XML | XMLHttpRequest 在浏览器端 JavaScript 中的应用 | 1999 年应用于 IE5,后在 Mozilla、Safari、Opera 中均被实现 |
REST | Representational State Transfer 表象状态转换 |
由 Roy Fielding 发表于 2000 年 |
REST 网络是 WWW(基于 HTTP)的子集。
……
我们可以定义两种主要类型的 Web Service:
• 遵循 REST 的 Web Service:主要目的是使用一系列统一的无状态操作对 Web 资源的 XML 表现层进行处理;
• 随意的 Web Service(原文:arbitrary web services
):提供一系列随意的操作。
—— W3: Web Services Architecture 3.1.3