什么是REST API?
什么是REST API?REST是Representational State Transfer的缩写--对最常用的网络服务技术的描述几乎毫无意义REST API是两个计算机系统使用网络浏览器和服务器中的HTTP技术进行通信的一种方式。
在两个或多个系统之间共享数据一直是软件开发的一个基本要求。例如,考虑购买汽车保险。你的保险公司必须获得关于你和你的车辆的信息,所以他们要求从汽车登记机构、信贷机构、银行和其他系统获得数据。所有这些都是实时透明地发生的,以确定保险公司是否能提供一个有竞争力的政策。
API(应用编程接口)通过为系统之间的对话提供一个接口来帮助这种类型的通信。REST只是一种被广泛采用的API风格,我们用它来与内部和外部各方以一致和可预测的方式进行沟通。它可以比作我们以前寄信时,以一定的方式贴上邮票、地址和信封,以确保信件被送达和阅读。
REST是人们在网络系统中常用的交互方式。例如,在一个社交媒体应用程序中检索和更新账户信息。
一个REST API例子
在你的浏览器中打开以下链接,请求从开放的琐事数据库中随机抽取一道电脑题。
这是一个作为RESTful网络服务实现的公共API(它遵循REST惯例)。你的浏览器将显示一个单一的JSON格式的问答问题,并附有答案,如。
{
"response_code": 0,
"results": [
{
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "What does GHz stand for?",
"correct_answer": "Gigahertz",
"incorrect_answers": [
"Gigahotz",
"Gigahetz",
"Gigahatz"
]
}
]
}
你可以使用任何HTTP客户端,如curl,请求相同的URL并获得响应。
curl "https://opentdb.com/api.php?amount=1&category=18"
所有流行的语言和运行时都有HTTP客户端库,包括JavaScript的Fetch、Node.js和Deno以及PHP的file_get_contents()。JSON响应是机器可读的,因此它可以在输出HTML或其他格式之前被解析和使用。
REST APIs和其他
多年来,各种数据通信标准已经发展起来。你可能遇到过包括CORBA、SOAP或XML-RPC的选择。大多数建立了严格的信息传递规则。
REST在2000年由Roy Fielding定义,比其他的要简单得多。它不是一个标准,而是一套针对RESTful网络服务的建议和约束。这些包括:
- 客户端-服务器:系统A向系统B托管的URL发出HTTP请求,系统B返回响应。这与浏览器的工作方式相同。浏览器对一个特定的URL发出请求。该请求被转到一个网络服务器上,通常会返回一个HTML页面。该页面可能包含对图像、样式表和JavaScript的引用,从而产生进一步的请求和响应。
- 无状态:REST是无状态的:客户端请求应该包含所有必要的响应信息。换句话说,应该可以按照任何顺序提出两个或更多的HTTP请求,并且会收到相同的响应(......除非API被设计为返回随机响应,如上面的测验例子)。
- 可缓存:响应应该被定义为可缓存或不可缓存。缓存可以提高性能,因为没有必要为同一个URL重新生成一个响应。通常情况下,某个用户在某个时间的私人数据是不会被缓存的。
- 分层:请求的客户端不需要知道它是否在与实际的服务器、代理或任何其他中间人进行通信。
创建一个RESTful网络服务
一个RESTful网络服务请求包含:
-
一个端点URL:实现RESTful API的应用程序将定义一个或多个带有域名、端口、路径和/或查询字符串的URL端点,例如,
https://mydomain/user/123?format=json。 -
HTTP方法:在任何端点上都可以使用不同的HTTP方法,这些方法映射到应用程序的创建、读取、更新和删除(CRUD)操作。
HTTP方法 CRUD 行动 GET 读取 返回请求的数据 POST 创建 创建一个新的记录 PUT或PATCH 更新 更新一个现有的记录 DELETE 删除 删除一条现有的记录 例子:
- 对
/user/的GET请求返回一个系统中的注册用户列表 - 对
/user/的POST请求使用正文数据创建一个ID为123的用户(见下面4.)响应会返回该ID。 - 向
/user/123发出PUT请求,用正文数据更新用户123(见下文第4条)。 - 向
/user/123发出 GET 请求,返回用户的详细信息。123 - 向
/user/123发出DELETE请求,删除用户。123
- 对
-
HTTP头信息:认证令牌或cookies等信息可以包含在HTTP请求头中。
-
主体数据:数据通常以与HTML
<form>提交相同的方式在HTTP正文中传输,或者通过发送单一的JSON编码的数据字符串。

REST API响应
响应的有效载荷可以是任何实用的东西:数据、HTML、图像、音频文件等等。数据响应通常是JSON编码的,但也可以使用XML、CSV、简单字符串或任何其他格式。你可以允许在请求中指定返回格式--例如,/user/123?format=json 或/user/123?format=xml 。
适当的HTTP状态代码也应该在响应头中设置。200 OK 用于成功的请求,尽管201 Created 也可以在创建一个记录时返回。错误应该返回一个适当的代码,如400 Bad Request,404 Not Found,401 Unauthorized, 等等。
可以设置其他的HTTP头,包括Cache-Control或Expires指令,以指定一个响应在被认为是陈旧的之前可以被缓存多长时间。
然而,并没有严格的规则。端点URL、HTTP方法、正文数据和响应类型可以随心所欲地实现。例如,POST,PUT, 和PATCH 通常可以互换使用,所以任何会根据需要创建或更新一个记录。
REST API "Hello World "示例
下面的Node.js代码使用Express框架创建了一个RESTful网络服务。一个单一的/hello/ 端点对HTTP GET请求做出响应。
确保你已经安装了Node.js,然后创建一个名为restapi 的新文件夹。在该文件夹中创建一个新的package.json 文件,内容如下:
{
"name": "restapi",
"version": "1.0.0",
"description": "REST test",
"scripts": {
"start": "node ./index.js"
},
"dependencies": {
"express": "4.18.1"
}
}
从命令行运行npm install ,以获取依赖性,然后用以下代码创建一个index.js 文件:
// simple Express.js RESTful API
'use strict';
// initialize
const
port = 8888,
express = require('express'),
app = express();
// /hello/ GET request
app.get('/hello/:name?', (req, res) =>
res.json(
{ message: `Hello ${req.params.name || 'world'}!` }
)
);
// start server
app.listen(port, () =>
console.log(`Server started on port ${port}`);
);
使用npm start ,从命令行启动应用程序,在浏览器中打开http://localhost:8888/hello/ 。响应GET请求时,会显示以下JSON:
{
"message": "Hello world!"
}
API也允许自定义名称,所以http://localhost:8888/hello/everyone/ 返回:
{
"message": "Hello everyone!"
}
客户端REST请求和CORS
考虑在浏览器中启动以下HTML页面,网址是http://localhost:8888/ 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>REST test</title>
</head>
<body>
<script>
fetch('http://localhost:8888/hello/')
.then((response) => {
return response.json();
})
.then((json) => {
console.log(json);
});
</script>
</body>
</html>
fetch 调用了相同的API请求,浏览器控制台显示Object { message: "Hello world!" } ,正如你所期望的那样。
然而,假设你的RESTful网络服务现在被放在网络上,域名为http://mydomain.com/hello/ 。页面的JavaScriptfetch() URL也相应地改变了,但是在浏览器中打开http://localhost:8888/ ,现在会返回控制台错误Cross-Origin Request Blocked。
为了安全起见,浏览器只允许客户端的XMLHttpRequest和Fetch API调用到调用页面所在的同一域。
幸运的是,跨源资源共享(CORS)使我们能够规避这一安全限制。设置一个Access-Control-Allow-Origin HTTP响应头,告诉浏览器允许该请求。它可以被设置为一个特定的域或* ,用于所有的域(由上面的Quiz API实现)。
网络服务API代码可以被修改,以允许从任何域上运行的任何客户端脚本访问。
// /hello/ GET request
app.get('/hello/:name?', (req, res) =>
res
.append('Access-Control-Allow-Origin', '*')
.json(
{ message: `Hello ${req.params.name || 'world'}!` }
)
);
或者,一个Express.js中间件函数可以把头附加到每个端点请求中。
// enable CORS
app.use((req, res, next) => {
res.append('Access-Control-Allow-Origin', '*');
next();
});
// /hello/ GET request
// ...
请注意,浏览器向REST API发出两个请求。
- 对同一URL的HTTP
OPTIONS请求确定Access-Control-Allow-OriginHTTP响应头是否有效 - 实际的REST调用
当你的服务器收到一个OPTIONS 的请求方法时,它可以设置Access-Control-Allow-Origin HTTP响应头返回一个假的空响应,以确保工作不被重复。
REST API的挑战
REST的成功很大程度上归功于它的简单性。开发人员可以随意实现RESTful API,但这可能会导致进一步的挑战。要深入了解实施策略,请看我们的13个构建RESTful API的最佳实践。
端点共识
考虑一下下面的端点:
/user/123/user/id/123/user/?id=123
都是为用户获取数据的有效选项123 。当你有更复杂的操作时,组合的数量会进一步增加。例如,当按出生日期倒序排列时,返回10个姓氏以'A'开头且在X公司工作的用户,从记录51开始。
归根结底,你如何格式化URL并不重要,但整个API的一致性很重要。这对有许多开发人员的大型代码库来说是个挑战。
REST API的版本管理
API的变化是不可避免的,但终端的URL不应该失效,否则会破坏使用它们的应用程序。
API通常会有版本,以避免兼容性问题。例如,/2.0/user/123 取代了/user/123 。新的和旧的端点都可以保持活跃。不幸的是,这样就有必要维护多个历史的API。旧的版本最终可以被废除,但这个过程需要仔细规划。
REST API认证
上面显示的测验API是开放的:任何系统都可以在没有授权的情况下获取一个笑话。这对于访问私人数据或允许更新和删除请求的API来说是不可行的。
与RESTful API处于同一域的客户端应用程序将像其他HTTP请求一样发送和接收cookies。(请注意,Fetch() 在旧的浏览器中需要设置credentials init选项。)因此,API请求可以被验证,以确保用户已经登录并有适当的权限。
第三方应用程序必须使用其他的授权方法。常见的验证选项包括:
- HTTP基本认证:在请求头中传递一个包含base64编码的用户名:密码字符串的HTTP
Authorization头。 - API密钥:第三方应用程序通过发布一个密钥来获得使用API的许可,这个密钥可能有特定的权利或被限制在一个特定的领域。密钥在每个请求中的HTTP头或查询字符串中传递。
- OAuth:在提出任何请求之前,通过向OAuth服务器发送一个客户ID和可能的客户秘密,获得一个令牌。然后,OAuth令牌与每个API请求一起发送,直到它过期。
- JSON网络令牌(JWT):数字签名的认证令牌在请求和响应头中安全地传输。JWT允许服务器对访问权限进行编码,因此不需要调用数据库或其他授权系统。
API认证将根据使用环境的不同而不同:
- 在某些情况下,第三方应用程序被视为具有特定权利和权限的任何其他登录用户。例如,一个地图API可以返回两点之间的方向给一个调用的应用程序。它必须确认该应用程序是一个有效的客户端,但不需要检查用户的证书。
- 在其他情况下,第三方应用程序正在请求个人用户的私有数据,如电子邮件内容。REST API必须识别用户和他们的权限,但它可能不关心哪个应用程序在调用API。
REST API的安全性
RESTful API提供了另一种访问和操作你的应用程序的途径。即使它不是一个高调的黑客攻击目标,一个行为不良的客户端也可能每秒发送成千上万的请求,并使你的服务器崩溃。
安全问题超出了本文的范围,但常见的最佳做法包括:
- 使用HTTPS
- 使用一个强大的认证方法
- 使用CORS来限制客户端对特定域的调用
- 提供最低限度的功能--也就是说,不要创建不需要的DELETE选项
- 验证所有端点的URL和主体数据
- 避免在客户端的JavaScript中暴露API令牌
- 阻止来自未知领域或IP地址的访问
- 阻止意外的大的有效载荷
- 考虑速率限制--即使用同一API令牌或IP地址的请求限制在每分钟N个以内
- 用适当的HTTP状态代码和缓存头进行响应
- 记录请求并调查失败情况
多重请求和不必要的数据
RESTful APIs受到其实现的限制。一个响应可能包含比你需要的更多的数据,或者需要进一步的请求来访问所有的数据。
考虑一个RESTful API,它提供对作者和书籍数据的访问。为了显示销量最高的10本书的数据,客户端可以:
- 请求前10本
/book/,按销售数量排序的细节(销量最高的在前)。响应包含有每个作者ID的书籍列表。 - 最多提出10个
/author/{id},以获取每个作者的详细资料。
这被称为N+1问题;必须为父请求中的每个结果提出N个API请求。
如果这是一个常见的用例,可以改变RESTful API,使每个返回的书都包含完整的作者细节,如他们的名字、年龄、国家、传记等。它甚至可以提供他们其他书籍的全部细节--尽管这可能会大大增加响应的有效载荷
为了避免不必要的大的响应,可以调整API,使作者的细节是可选的--例如,?author_details=full 。API作者需要满足的选项的数量可能会变得令人困惑。
GraphQL是否修复了REST APIs?
REST的难题导致Facebook创建了GraphQL--一种网络服务查询语言。把它看作是网络服务的SQL:一个单一的请求定义了你所需要的数据以及你希望它如何返回。
GraphQL解决了RESTful APIs带来的一些挑战,尽管它引入了其他挑战。例如,对GraphQL响应进行缓存变得很困难。
你的客户不太可能有与Facebook类似的问题,所以一旦RESTful API发展到超出其实际限制时,可能值得考虑GraphQL。
REST API链接和开发工具
有许多工具可以帮助所有语言的RESTful API开发。显著的选择包括:
- Swagger:有多种工具可以帮助设计、记录、模拟、测试和监控REST API。
- Postman:一个RESTful API测试应用程序
- Hoppscotch:一个开源的、基于网络的Postman的替代品。
还有很多公共的REST API,迎合了笑话、货币转换、地理编码、政府数据以及你能想到的所有主题。许多是免费的,尽管有些需要你注册一个API密钥或使用其他认证方法。分类列表包括:
在实现你自己的网络服务之前,尝试在你自己的项目中消费一些RESTful API。或者考虑跟随Facebook、GitHub、Google和其他许多巨头的脚步,建立一个属于自己的RESTful API。