接下来的内容包括:
假设,现在程序需要在接受到 America/Los_Angeles 或 Europe/London 等代表时区的字符串后,返回该时区的当前时间信息(例如:2015-04-07T20:09:58-07:00 )。
该返回信息与现实中易懂的时间格式是不一样的,因为它是为计算机设计的。
通过类似下面格式的 URL 的 HTTP 请求来调用应用 API:
/timezone?tz=America+Los_Angeles
而服务端 API 返回的 JSON 的数据格式,如下:
{ "time": "2015-06-09T16:20:00+01:00", "zone": "America/Los_Angeles" }
只要能调用 API 并对 JSON 数据进行解析,你就可以在任意平台构建任意应用程序。
如下图,你可以通过 AJAX 请求该 API 实现一个展示时区信息的单页应用。
也可以利用该接口实现下图所示的移动应用。
你甚至可以利用该 API 实现下图一样的终端命令行工具:在终端中打印服务端 API 接口返回的数据。
我们可以利用这些 API 返回的冰冷数据构建更具表达力的 UI 。
下面我们动手实现一个 Express 驱动的 API 服务。
实现的原理非常简单:通过中间件和内置函数解析网络请求并将 JSON 数据和 HTTP 状态码封装到响应对象并返回给客户端。
从技术角度上说,API 服务除了使用 JSON 格式外,你还可以是使用 XML 或者纯文本。
但是 Express 和 JavaScript 对 JSON 的支持是最好的,同时它也是当前最流行的格式,所以后面会一直使用 JSON 作为默认数据格式。
下面我们编写一个为多平台提供随机数生成的服务,该 API 将拥有如下特性:
你可能认为这里完全可以使用纯文本来替换 JSON 格式。但是发送 JSON 数据是开发者的必备技能,而且 JSON 格式极易拓展。
该工程的构建步骤如下:
首先,在新建的 package.json 中,复制下面的内容并按照依赖项:
{ "name": "random-number-api", "private": true, "scripts": { "start": "node app" }, "dependencies": { "express": "^5.0.0" } }
接下来,将下面的代码复制到入口文件 app.js 中:
var express = require("express"); var app = express(); app.get("/random/:min/:max", function(req, res) { var min = parseInt(req.params.min); var max = parseInt(req.params.max); if (isNaN(min) || isNaN(max)) { res.status(400); res.json({ error: "Bad request." }); return; } var result = Math.round((Math.random() * (max - min)) + min); res.json({ result: result }); }); app.listen(3000, function() { console.log("App started on port 3000"); });
现在启动应用并访问 http://localhost:3000/random/10/100 的话,你将看到一个附带 10 ~ 100 范围内随机数的 JSON 数据。
接下来,我们来分析上面的代码。
与之前一样,前两行代码引入了 Express 并创建了一个 Express 应用实例。
然后,我们创建了一个路由中间件用于处理类似 /random/10/100 这样的 API 请求。
我们使用内置的 parseInt 解析范围参数,而该函数的返回值只可能是整形数字或者 NaN。如果传入的参数有一个为 NaN 的话就会给客户端返回一个错误信息。下面这部分代码对于整个程序来说是非常重要的:
if (isNaN(min) || isNaN(max)) { res.status(400); res.json({ error: "Bad request." }); return; }
如果上面的参数检查的结果是最少有一个为 NaN ,程序就会进行如下处理:
在代码的最后,我们会在合法的参数返回内生成随机数并将结果返回给客户端。
虽然示例很简单,但是它已经包含了使用 Express 构建 API 的基本流程:解析请求,设置 HTTP 状态码,返回响应数据。
你可以在这个基础之上构建更为复杂优雅的 API 。
CRUD 是对程序中 Create、Read、Update、Delete 四种业务动作的一个简称。
大多数的应用都会涉及到 CURD 操作。例如,对于一个图片分享应用来说,其中涉及图片的所有操作就是典型的 CRUD:
无论是分享照片的社交应用还是文件存储服务,你生活中的使用的很多服务中都使用了这种模式。不过在开始讨论构建 CRUD 功能的 API 之前,我们先来看看被称为 HTTP 方法的内容。
HTTP 的规范中是这样定义其方法的:
HTTP 方法明确了对请求 URI 所标识资源进行的操作,而且方法是区分大小写的。
一个更易理解的解释是:客户端在发送 HTTP 请求时需要指定一个 HTTP 方法,然后服务端回依据不同的 HTTP 方法做出不同的响应。
虽然,可用的 HTTP 方法有很多,但是常用的其实并不多。其中在 Web 应用中常用是下面 4 个:
var express = express("express"); var app = express(); app.get("/", function(req, res) { res.send("you just sent a GET request, friend"); }); app.post("/", function(req, res) { res.send("a POST request? nice"); }); app.put("/", function(req, res) { res.send("i don‘t see a lot of PUT requests anymore"); }); app.delete("/", function(req, res) { res.send("oh my, a DELETE??"); }); app.listen(3000, function() { console.log("App is listening on port 3000"); });
回想以下之前的照片分享应用,下面是其中可能的 CRUD 操作:
不难看出 CRUD 操作与之前四种 HTTP 方法存在对应关系:
因此通过这四个 HTTP 方法我们可以很好的实现最常见 CRUD 风格的 web 应用程序。
实际上对于更新和创建动作与 HTTP 方法的对应关系,一些人有着自己的看法。
它们认为 PUT 更应该对应创建动作而非 POST。另外,新的 PATCH 方法则对应更新操作。虽然本文将会使用上面那种更规范的对应关系,但是你完全可以按照自己的意愿选择。
为了应对未来可能的 API 更新,对 API 进行版本控制是一件非常高效的方法。
例如,前面获取指定时区当前时间的 API 在推出后就被很多的厂商和开发者使用。
但是,几年几后由于某些原因必须对该 API 进行更新而与此同时你又不能影响之前的使用者。此时,我们就可以通过添加新版本来解决这个问题。其中原有的 API 请求可以通过:
/v1/timezone
而新版本 API 请求则可以使用:
/v2/timezone
这样不仅在进行 API 更新时防止了代码的破坏性更改。而且接口使用者也有了更灵活的选择,他们可以在必要的时候进行 API 切换。
在 Express 中可以使用 Router 中间件来实现 API 版本管理。拷贝下面代码到文件 app1.js 中,并讲其作为第一个版本 API 的实现:
var express = require("express"); var api = express.Router(); api.get("/timezone", function(req, res) { res.send("Sample response for /timezone"); }); api.get("/all_timezones", function(req, res) { res.send("Sample response for /all_timezones"); }); module.exports = api;
请注意,上面的中间件代码在处理的 URL 并没有包含 /v1 。下面在入口文件中引入这个 Router 中间件并进行路由映射。
var express = require("express"); var apiVersion1 = require("./api1.js"); var app = express(); app.use("/v1", apiVersion1); app.listen(3000, function() { console.log("App started on port 3000"); });
将最新版本的 API 实现放在 api2.js 文件中:
var express = require("express"); var api = express.Router(); api.get("/timezone", function (req, res) { res.send("API 2: super cool new response for /timezone"); }); module.exports = api;
通过 Router 将这两个版本的 API 同时添加到主入口中:
var express = require("express"); var apiVersion1 = require("./api1.js"); var apiVersion2 = require("./api2.js"); var app = express(); app.use("/v1", apiVersion1); app.use("/v2", apiVersion2); app.listen(3000, function() { console.log("App started on port 3000"); });
你可以通过浏览器验证这些版本化后的 API 是否正确工作,也可以使用 cURL 命令进行测试。
Router 可以让你将不同的路由存放在不同文件中进行管理。而版本化 API 就是最典型的应用实例。
每一个 HTTP 响应都应该附带一个 HTTP 状态码,其中最有名的就是 404 Not Found 。
虽然 404 是最出名的,但是 200 状态码确是最常见的。与 404 不同的是,虽然当网页成功加载或 JSON 数据成功返回后都会包含状态码 200,但它并不会被展示出来。
当然,除了 404 和 200 之外,HTTP 中还定义了很多其他的状态码,包括 100、200、300、400 以及 500 系列。需要注意的是并不是每个系列中所有 100 个数字都有明确定义,例如,100 系列只有 100,101,102 三个有效码,紧跟其后就是 200 。
每个状态码系列其实都有特定的含义和主题,总结就是:
1xx: 成功接收到请求。
2xx: 成功
3xx: 重定向
4xx: 客户端错误
5xx: 服务端错误
规范中只定义的大约 60 个状态码。
你可以在此基础上拓展自己的状态码,但是通常并不会这么做。因为优秀的 API 的首要设计原则就是确保不会对使用者造成任何歧义,所以应该最大程度遵循官方规范的指导。
后面我们会对上面的每个区间的状态码进行讲解,但是在此之前先来看看如何在 Express 中设置状态码。
少部分应用还在使用 HTTP 1.0 版本的协议,而大部分以及切换到了 1.1 版本。
作为下一个版本的 HTTP 2.0 标准现在也逐渐在推广过程中。幸运的是,2.0 版本的协议大部分更新都在底层所以切换时并不会涉及太大的工作量。
另外,2.0 版本还新增了一个 421 的状态码。
默认情况下,HTTP 状态码是 200。如果用户访问的 URL 对应资源不存在的话,Express 会发送 404 错误。如果访问的服务器出现问题的话,Express 就会发送 500 错误。
但是这些都是 Express 的默认行为,某些情形下可能会需要自行设置状态码。为此,Express 的 response 对象提供了一个 status 方法,你需要在调用是传入对应状态码就能完成设置。
// ... res.status(404); // ...
该方法可以进行链式调用,所以你可以紧跟其后使用 json 设置返回的数据。
res.status(404).json({ error: "Resource not found!" }); // 它等价于: res.status(404); res.json({ error: "Resource not found!" });
虽然 Express 对原生 Node 的 response 对象进行了拓展,并且在使用 Express 时也应遵循 Express 风格,但是依旧可以使用原生方法来完成设置。
res.statusCode = 404;
100 区间的官方状态码只有两个:100(继续) 和 101 (切换协议),而且它们很少会被用到。如果你必须处理的话,可以去官网或者维基上查看。
200 区间状态码表示请求成功。虽然该区间状态码不少,但是常用的也就下面 4 个:
同样,在 300 区间,我们只介绍其中常用的三个,并且它们全都涉及重定向。
400 区间的状态码是最多的,而它通常都是表示由于客户端的错误导致请求失败。
另外,当你不确定应该使用哪种客户端错误状态码时,你可以直接使用 400 。
作为 HTTP 规范里的最后一个区间,500 区间状态码表示的是服务内部出现错误。
例如,请求过载或者数据库连接中断。另外,理论上该区间的错误只能有服务内部自己触发。
为了防止黑客窥探太多内部信息,你可以对所有的内部错误仅仅返回一个抽象的“内部服务器错误”这样的信息。
本章包含的内容有:
原文:https://www.cnblogs.com/magicg/p/12825102.html