在《T4》第 10 章中,你学习了如何使用net模块创建低级 TCP 应用。然后,在第 11 章中,使用http模块抽象出 TCP 的底层细节。向更高抽象层次的转移允许我们做更多的事情,同时编写更少的代码。第 11 章还通过连接库介绍了中间件的概念。中间件促进代码重用,并使您能够以流水线的方式请求处理。然而,使用http和connect模块创建复杂的应用仍然有点乏味。
TJ Holowaychuk 创建的 Express 框架,在http和connect之上提供了另一个抽象层次。Express 基于 Ruby 的 Sinatra 框架,并标榜自己是“一个最小且灵活的 Node.js web 应用框架,为构建单页面、多页面和混合 web 应用提供了一组强大的功能。”Express 为许多常见的任务提供了方便的方法和语法糖,否则这些任务将是乏味的或多余的。本章详细分析了 Express 框架。而且记住,因为 Express 是建立在http和connect之上的,所以你在第 11 章中学到的一切都是适用的。
快速路线
在看 Express 提供了什么之前,让我们先确定一下http和connect的一些缺点。清单 12-1 包括了一个支持三个唯一的GETURL 的例子,并为其他的所有内容返回一个404。注意,每个新支持的动词/URL 组合在if语句中都需要一个额外的分支。还有相当数量的重复代码。通过更好地优化代码,可以消除一些重复,但这需要牺牲代码的可读性和一致性。
清单 12-1 。使用http模块支持多种资源
var http = require("http");
http.createServer(function(request, response) {
if (request.url === "/" && request.method === "GET") {
response.writeHead(200, {
"Content-Type": "text/html"
});
response.end("Hello <strong>home page</strong>");
} else if (request.url === "/foo" && request.method === "GET") {
response.writeHead(200, {
"Content-Type": "text/html"
});
response.end("Hello <strong>foo</strong>");
} else if (request.url === "/bar" && request.method === "GET") {
response.writeHead(200, {
"Content-Type": "text/html"
});
response.end("Hello <strong>bar</strong>");
} else {
response.writeHead(404, {
"Content-Type": "text/html"
});
response.end("404 Not Found");
}
}).listen(8000);HTTP 动词和 URL 的组合被称为路由,Express 拥有处理它们的高效语法。清单 12-2 显示了来自清单 12-1 的路线是如何使用 Express 的语法编写的。首先,express模块必须安装(npm install express)并导入到应用中。http模块也必须导入。在清单 12-2 的第三行,通过调用express()函数创建了一个 Express app。这个应用的行为类似于一个连接应用,并被传递给清单 12-2 最后一行的http.createServer()方法。
清单 12-2 。使用 Express 重写清单 12-1 中的服务器
var express = require("express");
var http = require("http");
var app = express();
app.get("/", function(req, res, next) {
res.send("Hello <strong>home page</strong>");
});
app.get("/foo", function(req, res, next) {
res.send("Hello <strong>foo</strong>");
});
app.get("/bar", function(req, res, next) {
res.send("Hello <strong>bar</strong>");
});
http.createServer(app).listen(8000);对应用的get()方法的三次调用用于定义路线。get()方法定义了处理GET请求的路径。Express 还为其他 HTTP 动词定义了类似的方法(put()、post()、delete()等等)。所有这些方法都将 URL 路径和一系列中间件作为参数。路径是表示路由响应的 URL 的字符串或正则表达式。请注意,查询字符串不被视为路径 URL 的一部分。还要注意,我们还没有定义一个404路由,因为这是当一个请求与任何已定义的路由都不匹配时 Express 的默认行为。
注意 Express 中间件遵循与 Connect 相同的request - response - next签名。Express 还用其他方法增加了请求和响应对象。这方面的一个例子是response.send()方法,如清单 12-2 所示,因为res.send(). send()用于将响应状态代码和/或主体发送回客户端。如果send()的第一个参数是一个数字,那么它将被视为状态代码。如果没有提供状态代码,Express 将发回一个200。响应体可以在第一个或第二个参数中指定,可以是字符串、Buffer、数组或对象。send()也设置Content-Type标题,除非你明确这样做。如果响应体是一个Buffer,那么Content-Type头也被设置为application/octet-stream。如果主体是字符串,Express 会将Content-Type头设置为text/html。如果主体是数组或对象,那么 Express 会发回 JSON。最后,如果没有提供主体,则使用状态代码的原因短语。
Route Parameters
假设您正在创建一个销售数百或数千种不同产品的电子商务网站,每种产品都有自己唯一的产品 ID。您肯定不希望手动指定数百条唯一的路线。一种方法是创建一条路线,并将产品 ID 指定为查询字符串参数。尽管这是一个非常有效的选择,但它会导致不吸引人的 URL。如果毛衣的网址看起来像/products/sweater而不是/products?productId=sweater不是更好吗?
事实证明,可以定义为正则表达式的 Express routes 非常适合支持这种场景。清单 12-3 展示了如何使用正则表达式来参数化一条路线。在本例中,产品 ID 可以是除正斜杠以外的任何字符。在路由的中间件内部,任何匹配的参数都可以通过req.params对象访问。
清单 12-3 。使用正则表达式参数化快速路径
var express = require("express");
var http = require("http");
var app = express();
app.get(/\/products\/([^\/]+)\/?$/, function(req, res, next) {
res.send("Requested " + req.params[0]);
});
http.createServer(app).listen(8000);为了更加方便,即使 URL 是用字符串描述的,路由也可以参数化。清单 12-4 展示了这是如何完成的。在本例中,使用冒号(:)字符创建了一个命名参数productId。在路由的中间件内部,使用req.params对象按名称访问这个参数。
清单 12-4 。带有命名参数的路线
var express = require("express");
var http = require("http");
var app = express();
app.get("/products/:productId", function(req, res, next) {
res.send("Requested " + req.params.productId);
});
http.createServer(app).listen(8000);您甚至可以从字符串中为参数定义一个正则表达式。假设productId参数现在只能由数字组成,清单 12-5 展示了正则表达式是如何定义的。请注意\d字符类上的附加反斜杠。因为正则表达式是在字符串常量中定义的,所以需要一个额外的反斜杠作为转义字符。
清单 12-5 。在路由字符串中定义正则表达式
var express = require("express");
var http = require("http");
var app = express();
app.get("/products/:productId(\\d+)", function(req, res, next) {
res.send("Requested " + req.params.productId);
});
http.createServer(app).listen(8000);
注意可选的命名参数后面都是问号。例如,在前面的例子中,如果productId是可选的,它将被写成:productId?。
创建快速应用
Express 包含一个名为express(1)的可执行脚本,用于生成 skeleton Express 应用。运行express(1)的首选方式是使用清单 12-6 中的命令全局安装express模块。要复习全局安装模块的含义,请参见第 2 章。
清单 12-6 。全局安装模块express
npm install -g express在全局安装 Express 之后,你可以通过发出清单 12-7 所示的命令在你机器的任何地方创建一个框架应用。这个清单还包括命令的输出,其中详细列出了创建的文件以及配置和运行应用的指令。注意,在这个例子中你实际输入的唯一东西是express testapp。
清单 12-7 。使用express(1)创建应用框架
$ express testapp
create : testapp
create : testapp/package.json
create : testapp/app.js
create : testapp/public
create : testapp/public/stylesheets
create : testapp/public/stylesheets/style.css
create : testapp/routes
create : testapp/routes/index.js
create : testapp/routes/user.js
create : testapp/public/javascripts
create : testapp/views
create : testapp/views/layout.jade
create : testapp/views/index.jade
create : testapp/public/images
install dependencies:
$ cd testapp && npm install
run the app:
$ node app将在新文件夹中创建 skeleton Express 应用。在这种情况下,文件夹将被命名为testapp。接下来,使用清单 12-8 中的命令安装应用的依赖项。
清单 12-8 。安装框架应用的依赖项
$ cd testapp && npm install在npm安装完依赖项之后,我们就可以运行框架程序了。快速应用的入口点位于文件app.js中。因此,要运行testapp,从项目的根目录发出命令node app。你可以通过连接到localhost的 3000 端口进入测试程序。框架应用定义了两条路线——/和/users——它们都响应GET请求。图 12-1 显示了使用 Chrome 连接到/路线的结果。
图 12-1 。骷髅 app 返回的索引页
检查骨架应用
app.js是快递 app 的心脏。在清单 12-7 中生成的app.js文件的内容如清单 12-9 所示。该文件首先导入express、http和path模块,以及两个项目文件/routes/index.js和/routes/user.js。从routes目录导入的两个文件包含框架应用的路由所使用的中间件。在require()语句之后,使用express()函数创建一个快速应用。
清单 12-9 。app.js的生成内容
/**
* Module dependencies.
*/
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
注意如果传递给require()的模块路径解析为一个目录,Node 将在该目录中寻找一个index文件。这就是为什么表达require("./routes")解析为/routes/index.js。
接下来,您将看到对应用的set()方法的三次调用,该方法用于定义应用设置。第一个调用定义了一个名为port的设置,它定义了服务器将绑定到的端口号。端口号默认为 3000,但是这个值可以通过定义一个名为PORT的环境变量来覆盖。接下来的两个设置,views和view engine,由快速模板系统使用。模板系统将在本章后面被重新讨论。现在,只需要知道这些设置使用 Jade 模板语言来呈现存储在views目录中的视图。
在设置定义之后是对use()的几个调用,这些调用定义了用于处理所有请求的中间件。表 12-1 包含了对 skeleton 应用中包含的各种中间件的简短描述。这些功能中有许多只是使用了同名的 Connect 中间件。
表 12-1 。app.js 中使用的中间件 T3
|
中间件
|
描述
|
| --- | --- |
| favicon | 如果您一直在使用浏览器测试您的 web 服务器,那么您可能已经注意到了对文件favicon.ico的请求。这个中间件通过为您的favicon.ico文件提供服务来处理这样的请求,或者如果您没有提供文件,则使用连接默认值。 |
| logger | 这个中间件记录它收到的每个请求的信息。在框架应用中使用的dev模式中,logger显示请求动词和 URL,以及响应代码、处理请求所用的时间和返回数据的大小。 |
| bodyParser | 这个中间件在第 11 章中有解释。它将请求体字符串解析成一个对象,并将其作为request.body附加到请求对象上。 |
| methodOverride | 有些浏览器只允许 HTML 表单发出GET和POST请求。要发出其他类型的请求(PUT、DELETE等等),表单可以包含一个名为X-HTTP-Method-Override的输入,其值是所需的请求类型。这个中间件检测到这种情况,并相应地设置request.method属性。 |
| app.router | 这是用于将传入请求映射到定义的路由的快速路由。如果没有明确使用它,Express 将在第一次遇到路由时装载它。然而,手动安装路由将确保它在中间件序列中的位置。 |
| static | 这个中间件接受一个目录路径作为输入。该目录被视为静态文件服务器的根目录。这对于提供图像、样式表和其他静态资源等内容非常有用。在骷髅 app 中,静态目录是public。 |
| errorHandler | 顾名思义,errorHandler是处理错误的中间件。与其他中间件不同,errorHandler接受四个参数— error、request、response和next。在 skeleton app 中,这个中间件只在开发模式下使用(见development only注释)。 |
在set()和use()呼叫之后,使用get()方法定义两条GET路线。如前所述,这些路线的网址是/和/users。 /users路由使用存储在user.list变量中的一个中间件。回头看看require()语句,user变量来自文件/routes/user,其内容如清单 12-10 所示。正如您所看到的,这个路由只是返回字符串"respond with a resource"。
清单 12-10 。/routes/user.js生成的内容
/*
* GET users listing.
*/
exports.list = function(req, res){
res.send("respond with a resource");
};/路线比较有意思。在/routes/index.js 中定义,如清单 12-11 所示。这里显示的代码看起来不像能创建如图 12-1 所示的页面。关键是render()方法,它与 Express 模板系统联系在一起。这可能是一个探索模板化,以及如何在 Express 中处理它的好时机。
清单 12-11 。/routes/index.js生成的内容
/*
* GET home page.
*/
exports.index = function(req, res){
res.render('index', { title: 'Express' });
};模板
创建动态 web 内容通常涉及构建长的 HTML 字符串。手动完成这项工作既繁琐又容易出错。例如,很容易忘记对长字符串中的字符进行适当的转义。模板引擎是一种替代方法,它通过提供一个框架文档(模板)来大大简化这个过程,您可以在这个框架文档中嵌入动态数据。现在有许多兼容 JavaScript 的模板引擎,其中一些比较流行的选项是 Mustache、Handlebars、嵌入式 JavaScript (EJS)和 Jade。Express 支持所有这些模板引擎,但是默认情况下,Jade 是和 Express 一起打包的。这一节解释了如何使用玉。其他模板引擎可以很容易地安装和配置,以便与 Express 一起工作,但这里不讨论。
配置 Jade 就像在app.js文件中定义两个设置一样简单。这些设置是views和view engine。views设置指定了 Express 可以定位模板的目录,也称为视图。如果没有提供,那么view engine指定要使用的视图文件扩展名。清单 12-12 显示了如何应用这些设置。在本例中,模板位于名为views的子目录中。这个目录应该包括一些 Jade 模板文件,文件扩展名是.jade。
清单 12-12 。用于在 Express 中配置 Jade 的设置
app.set("views", __dirname + "/views");
app.set("view engine", "jade");一旦 Express 被配置为使用您最喜欢的模板引擎,您就可以开始呈现视图了。这是通过response对象的render()方法完成的。render()的第一个参数是您的views目录中视图的名称。如果您的views目录包含子目录,该名称可以包含正斜杠。render()的下一个参数是传递数据的可选参数。这用于在静态模板中嵌入动态数据。render()的最后一个参数是一个可选的回调函数,一旦模板完成渲染,这个函数就会被调用。如果省略回调,Express 将自动用呈现的页面响应客户端。如果包含回调,Express 将不会自动响应,调用函数时会出现一个可能的错误,并以呈现的字符串作为参数。
假设您正在为一个用户的帐户页面创建一个视图。用户登录后,您需要称呼他们的名字。清单 12-13 显示了一个使用render()处理这种情况的例子。这个例子假设模板文件被命名为home.jade,并且位于views文件夹中的一个名为account的目录中。假设用户的名字是 Bob。在实际的应用中,这些信息可能来自某种类型的数据存储。这里还包含了可选的回调函数。在回调中,我们检查错误。如果出现错误,将返回一个500内部服务器错误。否则,返回呈现的 HTML。
清单 12-13 。render()的使用示例
res.render("account/home", {
name: "Bob"
}, function(error, html) {
if (error) {
return res.send(500);
}
res.send(200, html);
});当然,为了呈现视图,我们需要实际创建视图。因此,在您的views目录中,创建一个名为account/home.jade 的文件,包含如清单 12-14 所示的代码。这是一个 Jade 模板,虽然对 Jade 语法的解释超出了本书的范围,但我们将介绍绝对的基础知识。第一行用于指定 HTML5 文档类型。第二行创建了开始的<html>标记。请注意,Jade 不包含任何尖括号或结束标记。相反,Jade 根据代码缩进来推断这些事情。
清单 12-14 。一个翡翠模板的例子
doctype 5
html
head
title Account Home
link(rel='stylesheet', href='/stylesheets/style.css')
body
h1 Welcome back #{name}接下来是文档的<head>标签。标题包括页面标题和一个样式表链接。link旁边的括号用于指定标签属性。样式表链接到一个静态文件,Express 可以使用static中间件找到该文件。
清单 12-14 的最后两行定义了文档的<body>。在这种情况下,主体由欢迎用户的单个<h1>标记组成。#{name}的值取自传递给render()的 JSON 对象。在花括号内,可以使用 JavaScript 的标准点和下标符号访问嵌套的对象和数组。
产生的 HTML 字符串显示在清单 12-15 中。请注意,为了可读性,该字符串已被格式化。实际上,Express 呈现的模板没有额外的缩进和换行符。有关 Jade 语法的更多信息,请参见 Jade 主页上的http``:``//``www``.``jade``-``lang``.``com。
清单 12-15 。从清单 12-14 中的模板呈现的 HTML 示例
<!DOCTYPE html>
<html>
<head>
<title>Account Home</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<h1>Welcome back Bob</h1>
</body>
</html>express-validator
express-validator是一个有用的第三方模块,用于确保用户输入以预期的格式提供。express-validator创建中间件,将数据检查方法附加到request对象上。清单 12-16 中的显示了一个使用express-validator验证产品 ID 的例子。在示例的第二行导入了express-validator模块,然后用use()将其添加为中间件。中间件将assert()和validationErrors()方法附加到req上,在路由中使用。
assert()方法将参数名和错误消息作为参数。该参数可以是命名的 URL 参数、查询字符串参数或请求正文参数。由assert()返回的对象用于验证参数的数据类型和/或值。清单 12-16 展示了三种验证方法,notEmpty()、isAlpha()和len()。这些方法验证了productId参数存在,并且长度在 2 到 10 个字母之间。为了方便起见,这些方法可以链接在一起,如第二个assert()所示。当然,如果您完全省略了productId参数,路由将不会被匹配,验证器将永远不会运行。notEmpty()在验证查询字符串参数和表单体数据时更有用。
清单 12-16 。express-validator的一个例子
var express = require("express");
var validator = require("express-validator");
var http = require("http");
var app = express();
app.use(express.bodyParser());
app.use(validator());
app.get("/products/:productId", function(req, res, next) {
var errors;
req.assert("productId", "Missing product ID").notEmpty();
req.assert("productId", "Invalid product ID").isAlpha().len(2, 10);
errors = req.validationErrors();
if (errors) {
return res.send(errors);
}
res.send("Requested " + req.params.productId);
});
http.createServer(app).listen(8000);在做出所有断言后,使用validationErrors()方法来检索任何错误。如果没有错误,将返回null。但是,如果检测到错误,将返回一组验证错误。在这个例子中,错误数组只是作为响应被发送回来。
还有许多其他有用的验证方法没有在清单 12-16 中显示。其中一些是isInt()、isEmail()、isNull()、is()和contains()。前三种方法验证输入是整数、电子邮件地址还是null。is()方法接受一个正则表达式参数,并验证该参数是否与之匹配。contains()也接受一个参数,并检查参数是否包含它。
express-validator还为req附加了一个sanitize()方法,用于清理输入。清单 12-17 显示了sanitize()的几个例子。前两个示例分别将参数值转换为布尔值和整数。第三个示例删除了参数开头和结尾多余的空白。最后一个例子用相应的字符(<和>)替换字符实体(比如<和>)。
清单 12-17 。express-validator sanitize()方法的例子
req.sanitize("parameter").toBoolean()
req.sanitize("parameter").toInt()
req.sanitize("parameter").trim()
req.sanitize("parameter").entityDecode()REST
代表性状态转移 或 REST,是一种越来越常见的创建 API 的软件架构。由 Roy Fielding 在 2000 年提出的 REST 本身并不是一项技术,而是一套用于创建服务的原则。RESTful APIs 几乎总是使用 HTTP 实现,但这不是严格的要求。下面的列表列举了 RESTful 设计背后的一些原则。
- RESTful 设计应该有一个单一的基本 URL,和一个类似目录的 URL 结构。例如,一个博客 API 可以有一个基本 URL
/blog。某一天的个人博客条目可以使用类似于/blog/posts/2013/03/17/的 URL 结构进行访问。 - 作为应用状态引擎的超媒体(HATEOAS) 。客户端应该能够只使用服务器提供的超链接来导航整个 API 。例如,在访问一个 API 的入口点之后,服务器应该提供链接,客户端可以使用这些链接来导航 API。
- 服务器不应该维护任何客户端状态,例如会话。相反,每个客户端请求都应该包含定义状态所需的所有信息。这一原则通过简化服务器来提高可伸缩性。
- 服务器响应应该声明它们是否可以被缓存。这种声明可以是显式的,也可以是隐式的。如果可能,响应应该是可缓存的,因为它可以提高性能和可伸缩性。
- RESTful 设计应该尽可能地利用底层协议的词汇。例如,CRUD(创建、读取、更新和删除)操作分别使用 HTTP 的
POST、GET、PUT和DELETE动词来实现。此外,服务器应该尽可能使用适当的状态代码进行响应。
RESTful API 示例
Express 使得 RESTful 应用的实现变得非常简单。在接下来的几个例子中,我们将创建一个 RESTful API 来操作服务器上的文件。API 更常用于操作数据库条目,但是我们还没有涉及数据库。我们的示例应用也被分成许多文件。这使得示例更具可读性,同时也使得应用更加模块化。
首先,我们从app.js 开始,如清单 12-18 所示。这其中的大部分应该看起来很熟悉。然而,增加了一个额外的中间件来定义req.store。这是包含应用将使用的文件的目录。路线声明也被删除了,取而代之的是对文件routes.js中定义的自定义函数routes.mount(). mount()的调用,该函数将 Express app 作为其唯一的参数。
清单 12-18 。app.js的内容
var express = require("express");
var routes = require("./routes");
var http = require("http");
var path = require("path");
var app = express();
var port = process.env.PORT || 8000;
app.use(express.favicon());
app.use(express.logger("dev"));
app.use(express.bodyParser());
app.use(express.methodOverride());
// define the storage area
app.use(function(req, res, next) {
req.store = __dirname + "/store";
next();
});
app.use(app.router);
// development only
if ("development" === app.get("env")) {
app.use(express.errorHandler());
}
routes.mount(app);
http.createServer(app).listen(port, function() {
console.log("Express server listening on port " + port);
});routes.js 的内容如清单 12-19 所示。测试应用接受四个路径,每个 CRUD 操作一个路径。每个路由的中间件都在自己的文件中定义(create.js、read.js、update.js和delete.js)。需要指出的一点是,delete既是 HTTP 动词又是 JavaScript 保留字,所以在某些地方将delete操作简称为del。
清单 12-19 。routes.js的内容
var create = require("./create");
var read = require("./read");
var update = require("./update");
var del = require("./delete");
module.exports.mount = function(app) {
app.post("/:fileName", create);
app.get("/:fileName", read);
app.put("/:fileName", update);
app.delete("/:fileName", del);
};由POST进路处理的create操作在create.js中找到,如清单 12-20 所示。因为我们正在执行文件系统操作,所以我们从导入fs模块开始。在路由中间件内部,计算文件路径及其内容。该路径由req.store值和fileName参数组成。要写入文件的数据来自名为data的POST主体参数。然后使用fs.writeFile()方法创建新文件。文件是使用wx标志创建的,如果文件已经存在,这会导致操作失败。在writeFile()回调中,我们返回一个400状态码来表明请求不能被满足,或者返回一个201来表明一个新文件被创建。
清单 12-20 。create.js的内容
var fs = require("fs");
module.exports = function(req, res, next) {
var path = req.store + "/" + req.params.fileName;
var data = req.body.data || "";
fs.writeFile(path, data, {
flag: "wx"
}, function(error) {
if (error) {
return res.send(400);
}
res.send(201);
});
};下一个 CRUD 操作是读取,由GET路径处理。read.js的内容如清单 12-21 所示。这一次,fs.readFile()方法用于检索在fileName参数中指定的文件内容。如果读取因任何原因失败,将返回一个404状态代码。否则,将返回一个200状态代码,以及包含文件数据的 JSON 主体。值得指出的是,在设置响应代码时,可以更彻底地检查error参数。例如,如果error.code等于"ENOENT",那么文件确实不存在,状态代码应该是404。所有其他错误都可以简单地返回一个400。
清单 12-21 。read .js的内容
var fs = require("fs");
module.exports = function(req, res, next) {
var path = req.store + "/" + req.params.fileName;
fs.readFile(path, {
encoding: "utf8"
}, function(error, data) {
if (error) {
return res.send(404);
}
res.send(200, {
data: data
});
});
};接下来是PUT路线,它实现了update操作,如清单 12-22 所示。这非常类似于create操作,有两个小的不同。首先,在成功更新时返回一个200状态代码,而不是一个201。第二,用r+标志而不是wx打开文件。如果文件不存在,这会导致update操作失败。
清单 12-22 。update.js的内容
var fs = require("fs");
module.exports = function(req, res, next) {
var path = req.store + "/" + req.params.fileName;
var data = req.body.data || "";
fs.writeFile(path, data, {
flag: "r+"
}, function(error) {
if (error) {
return res.send(400);
}
res.send(200);
});
};最终的 CRUD 操作是delete ,如清单 12-23 中的所示。方法删除由参数fileName指定的文件。这条路由失败时返回一个400,成功时返回一个200。
清单 12-23 。delete.js的内容
var fs = require("fs");
module.exports = function(req, res, next) {
var path = req.store + "/" + req.params.fileName;
fs.unlink(path, function(error) {
if (error) {
return res.send(400);
}
res.send(200);
});
};测试 API
我们可以创建一个简单的测试脚本,如清单 12-24 所示,用于测试 API。该脚本使用request模块至少访问一次所有的 API 路径。async模块也用于避免回调地狱。通过查看对async.waterfall()的调用,您可以看到脚本是从创建一个文件并读回内容开始的。然后,文件被更新并再次被读取。最后,我们删除文件并尝试再次读取它。所有的请求都处理同一个文件,foo。每个请求完成后,将显示操作名称和响应代码。对于成功的GET请求,也会显示文件内容。
清单 12-24 。RESTful API 的测试脚本
var async = require("async");
var request = require("request");
var base = "http://localhost:8000";
var file = "foo";
function create(callback) {
request({
uri: base + "/" + file,
method: "POST",
form: {
data: "This is a test file!"
}
}, function(error, response, body) {
console.log("create: " + response.statusCode);
callback(error);
});
}
function read(callback) {
request({
uri: base + "/" + file,
json: true // get the response as a JSON object
}, function(error, response, body) {
console.log("read: " + response.statusCode);
if (response.statusCode === 200) {
console.log(response.body.data);
}
callback(error);
});
}
function update(callback) {
request({
uri: base + "/" + file,
method: "PUT",
form: {
data: "This file has been updated!"
}
}, function(error, response, body) {
console.log("update: " + response.statusCode);
callback(error);
});
}
function del(callback) {
request({
uri: base + "/" + file,
method: "DELETE"
}, function(error, response, body) {
console.log("delete: " + response.statusCode);
callback(error);
});
}
async.waterfall([
create,
read,
update,
read,
del,
read
]);测试脚本的输出显示在清单 12-25 中。在运行脚本之前,请确保创建了store目录。创建操作返回一个201,表示在服务器上成功创建了foo。当文件被读取时,返回一个200,并显示文件的正确内容。接下来,文件被成功更新并再次读取。然后,文件被成功删除。随后的read操作返回一个404,因为文件不再存在。
清单 12-25 。清单 12-24 中测试脚本的输出
$ node rest-test.js
create: 201
read: 200
This is a test file!
update: 200
read: 200
This file has been updated!
delete: 200
read: 404摘要
本章介绍了 Express 框架的基础知识。Express 在 Connect 和 HTTP 之上提供了一个层,这大大简化了 web 应用的设计。在撰写本文时,Express 是npm注册表中第五大依赖模块,已经被用于构建超过 26,000 个 web 应用。这使得 Express 对于全面发展的 Node 开发人员来说极其重要。尽管 Express 可能是一整本书的主题,但本章已经触及了框架和相关技术的最重要的方面。为了更好地理解这个框架,我们鼓励你浏览位于http://www.expressjs.com的 Express 文档,以及位于https://github.com/visionmedia/express的源代码。
