Skip to main content

前备知识

在讨论 API 设计之前,我们总结了一些与之相关的知识点。为了后续内容更容易理解,请先阅读掌握以下内容。

URI/URL#

格式#

scheme://[username[:password]@]host[:port][/path][?query][#fragment]

部分是否必须说明
scheme传输协议,或代表处理请求的应用程序,如:httphttpsftpsmtpgitmongo
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)等同于说明
#bottomhttp://www.example.com/items/categories/8587.html?page=2#bottom页面不会刷新,跳转到同一页面的另一处
?page=3http://www.example.com/items/categories/8587.html?page=3仅更新查询参数
./8588.htmlhttp://www.example.com/items/categories/8588.html. 代表同级路径
../css/common.csshttp://www.example.com/items/css/common.css.. 代表返回上一级路径
/css/common.csshttp://www.example.com/css/common.css/(即虚拟主机的根路径)开始即相对于根路径的位置
//api.example.com/items/categories/8587?page=2http://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/htmltext/csstext/javascriptapplication/json
Content-Type发送数据的类型,如:application/x-www-form-urlencodedmultipart/form-dataapplication/xmlapplication/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(XXML)技术的出现被使用,请求报文的数据部分相应的为 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直接更改计算状态CC++JavaPythonRubyPHP
结构化(Structured逻辑化指令式编程CC++JavaPythonBASIC
过程化(Procedural派生自结构化编程,基于模块化编程存储过程调用的概念CC++PythonPHP
函数式(Functional将计算机运算视为数学上的函数计算,避免使用程序状态及易变对象
相关概念:Lambda 演算
C++ScalaKotlinPythonRubyJavaScript
事件驱动(Event-Driven流程由事件控制JavaScriptVisual Basic
面向对象(Object-Oriented抽象为对象之间的相互操作C++JavaScalaKotlinPythonRubyPHPJavaScriptLua
声明式(Declarative定义程序逻辑,但无控制流程SQLCSS、正则表达式

多种编程范型是并存的,很多语言是多范型编程,没有一种范型或范型组合能够解决所有问题(没有银弹),包括 OOP。

RPC vs SOAP vs REST#

概念解释说明备注
RPCRemote Procedure Call
远程过程调用
允许运行于一台计算机的程序调用另一台计算机的子程序而无需额外为这个交互作用编程的通信协议
RMIRemote Method Invoke
远程方法调用
OOP 的 RPC
XML-RPC基于 HTTP、使用 XML 封装的 RPC由微软发表于 1998 年,后逐步并入 SOAP
Web Service用以支持网络间不同计算机互动操作的软件系统
SOAPSimple Object Access Protocol
简单对象访问协议
应用于计算机网络 Web Service 中由微软及 IBM 发表于 1998 年
XMLHttpRequest用于在 Web 浏览器与服务器之间传输数据的 API 对象由微软于 1998 年开始应用于 Outlook Web App
AJAXAsynchronous JavaScript and XMLXMLHttpRequest 在浏览器端 JavaScript 中的应用1999 年应用于 IE5,后在 Mozilla、Safari、Opera 中均被实现
RESTRepresentational 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