ExpressJS-高级教程-一-

180 阅读37分钟

ExpressJS 高级教程(一)

原文:Pro Express.js

协议:CC BY-NC-SA 4.0

零、简介

如果你正在考虑是否要买这本书,这份介绍将帮助你确定它完全适合你的专业水平和需求。如果你已经买了这本书,那么恭喜你!现在,您已经做好了充分的准备,可以更深入地研究增长最快的平台的最流行的 web 框架。

随着初创公司和大公司逐渐意识到采用 Node.js 的好处,Node.js 和 Express.js 中的技能需求正处于快速增长的边缘。新技术的早期采用者和主流采用者之间总是有差距,Node.js 的主流采用者正在迅速接近。对于开发人员来说,这意味着现在是精通 Node.js 的最佳时机。您的技能将会大受欢迎!

为什么写这本书

几年前我开始写关于 Express.js 的文章,当时我写了 Express.js 指南。该书于 2013 年自助出版,是第一本专门介绍 Express.js 的书籍,express . js 是迄今为止最受欢迎的 Node.js web 框架(截至 2014 年 12 月撰写本文时)。在我写 Express.js 指南的时候,Express.js 官方网站(http://expressjs.com)只提供了一点点见解,而且是为高级 Node.js 程序员准备的。我遇到过很多人——包括通过 Hack Reactor 程序和我在 General Assembly 和 pariSOMA 上的 Node.js 课程——他们对一本权威手册感兴趣,这本手册将涵盖 Express.js 的所有不同组件在现实生活中如何协同工作。Express.js Guide 的目标就是成为这样的资源。

Express.js Guide 成为 Amazon.com 同类图书中的第一畅销书后,一家出版社联系我写这本书,以扩展该材料并接受专业技术编辑的正式审查。 Pro Express.js 远不止是 Express.js 指南的修订或更新。这是一个完整的翻拍,增加了评论,描述,例子和额外的东西。 Pro Express.js 拥有经过更好审核的代码和文本,以及最新版本的库(例如 Express.js v4.8.1)。

在写这两本书的过程中,很多事情都发生了变化。Node.js 在 io.js 处分叉,Express.js 的创建者 TJ Holowaychuk 不再积极参与 Node.js,现在 StrongLoop ( http:/strongloop.com)维护框架的知识库。Express.js 上的发展一如既往的迅猛。它更稳定,更安全。我只看到 Express.js 和 Node.js 更光明的未来!

谁应该拥有这本书

本书面向已经精通编程和前端 JavaScript 的软件工程师和 web 开发人员。为了从 Pro Express.js 中获得最大的好处,您应该熟悉 Node.js 的基本概念,例如进程和全局,还应该知道核心模块,包括流、集群和缓冲区。

如果您正在考虑开始一个 Node.js 项目或者重写一个现有的项目,并且您选择的武器是 Express.js,那么这个指南就是为您准备的!它将回答你的大多数“如何”和“为什么”的问题。

这本书是什么

Pro Express.js 是一本关于某个特定图书馆的详尽书籍。与覆盖许多库的 Practical Node.js (Apress,2014)不同,Pro Express.js 专注于单个模块 Express.js。当然,在有必要覆盖其他相关库的地方,如中间件,这本书也会涉及这些库,但不像框架本身那样广泛。

Pro Express.js 涵盖中间件、命令行界面和脚手架、渲染模板、从动态 URL 中提取参数、解析有效负载和 cookies、管理会话认证、错误处理以及为生产准备应用。

Pro Express.js 由四个不同的部分组成,各章节在目录中列出:

  1. 入门:让您体验框架的快速入门演练
  2. 深度 API 参考:作为 Express.js v4.8.1 API 参考,当您需要关于某些方法的信息时,您可以完整阅读或浏览
  3. 解决常见和抽象的问题:代码组织和模式的最佳实践——使用 Express.js 时需要了解的主题
  4. 教程和示例:现实世界的教程(精心描绘的编码练习)和示例(对更复杂应用的不太详细的解释)

这本书不是什么

这本书不是对 Node.js 的介绍,也不是一本非常详细地涵盖构建现代 web 应用的所有方面的书,例如 WebSockets、数据库和(当然)前端开发。你不会在这里找到学习编程或 JavaScript 基础知识的帮助,因为这不是一本初学者的书。

关于 Node.js、MongoDB 和 Backbone.js 前端开发的介绍,你可能想看看我的书用 js 快速原型化:敏捷 JavaScript 开发 ( http://rapidprototypingwithjs.com)或者考虑亲自或在线参与 Node 计划(http://nodeprogram.com)。

在现实世界中——尤其是在 Node.js 开发中,由于它的模块化理念——我们很少只使用一个框架。然而,在本书中,我试图坚持使用 Express.js,并尽可能地省略其他内容,而不影响示例的有用性。因此,我有意省略了 web 开发的一些重要部分——例如,数据库、认证和测试。虽然这些元素出现在教程和例子中,但没有详细解释。如果你想了解更多关于这些话题的内容,附录 A 列出了一些你可能想查阅的相关书籍。

例子

Pro Express.js 充满了代码片段和可运行的例子。一些例子是一步一步的,精心解释的教程,鼓励你在阅读本书时自己复制。其他的是一些简短的代码示例,目的是为了说明某一点。

大部分源代码可以在 GitHub 资源库的https://github.com/azat-co/proexpressjs文件夹ch1ch18下获得(对应于第一章–第十八章)。第四部分中的例子更加广泛,并且存在于它们自己的库中。可以在以下网址找到它们:

  • Instagram Gallery : https://github.com/azat-co/sfy-gallery
  • 全部应用:??]
  • REST API: https://github.com/azat-co/rest-api-express
  • 骇客厅:??

所提供的示例是仅用给定的特定版本的依赖项编写和测试的。因为 Node.js 及其模块生态系统正在快速开发中,请关注新版本是否有突破性变化。以下是我用过的版本列表:

  • Express.js v4.8.1
  • Node.js v0.10.12
  • NPM v1.2.32 版
  • mongodb v 2 . 6 . 3 版
  • Redis v2.6.7
  • 触控笔 v0.47.3
  • 杰德 v1.5.0
  • 领班 v0.75.0
  • 谷歌浏览器版本 39.0.2171.7

勘误表和联系人

如果你被困在一个练习中,一定要检查 GitHub 库。在 GitHub 问题版块(https://github.com/azat-co/proexpressjs/issues)中可能会有更新的代码和答案。此外,通过提交您自己的问题,您可以帮助您的程序员同事获得更好的体验。

至于令人讨厌的错别字,我敢肯定,无论我检查手稿多少次,其中一些仍然会存在,请将它们提交给出版社(通过www.apress.com/9781484200384的勘误表)或 GitHub Issues。

最后,让我们在网上做朋友吧!孤立地编码是孤独的。以下是联系我以及与其他开发人员交流的一些方法:

  • 写一篇 Amazon.com 评论:http://amzn.to/1D6qiqk
  • 加入 HackHall.com,一个面向程序员、黑客和开发者的社区
  • 在 Twitter 上发布你的 Node.js 问题:@azat_co
  • 跟随我上脸书:http://facebook.com/1640484994
  • 访问 Pro Express.js 网站:http://proexpressjs.com
  • 访问我的网站:http://azat.co
  • 启动 Pro Express.js GitHub 库:https://github.com/azat-co/proexpressjs
  • 直接给我发邮件:hi@azat.co
  • 注册博客时事通讯:http://webapplog.com

既然你已经到了介绍的结尾,让 Twitter 上的每个人都知道你即将通过 Pro Express.js : http://ctt.ec/91iHS开始学习 Express.js。在你有机会阅读这本书之后,请写一篇 Amazon.com 评论,让其他人知道你对这本书的看法。

一、从 Express.js 开始

Express.js 是基于核心 Node.js http模块 1 并连接 2 组件的 web 框架。这些组件被称为中间件。它们是框架理念的基石,即配置优于约定。一些熟悉 Ruby 的开发人员将 Express.js 与 Sinatra 相比较,后者与 Ruby on Rails 框架有着非常不同的方法,后者更倾向于*约定而不是配置。*换句话说,开发人员可以自由选择他们需要的特定项目的库。这种方法为他们提供了高度定制项目的灵活性和能力。

如果您只使用核心 Node.js 模块编写过任何严肃的应用,您很可能会发现自己在重复编写相同的代码来完成类似的任务,例如:

  • 解析 HTTP 请求正文
  • 解析 cookies
  • 管理会话
  • 根据请求的 URL 路径和 HTTP 方法,用一系列if条件组织路由
  • 基于数据类型确定正确的响应头
  • 处理错误
  • 提取 URL 参数(如/messages/3233)

后来,您可能创建了自己的库来重用代码,但是您的库不会像社区支持的最好的库那样经过彻底的测试。此外,维护将完全由您和您的团队承担。因此,我的建议是使用社区模块,如果它适合你的需要。这个建议同样适用于使用小型库和 web 框架。

Express.js 解决了这些和其他许多问题。它提供了优雅地重用代码的方法,并为您的 web 应用提供了类似于模型-视图-控制器(MVC) 的结构。模型(M)部分需要由一个额外的数据库驱动库提供(例如,mongose3)。这些应用可以是各种各样的,从准系统、仅后端 REST APIs 到成熟的、全栈的、实时的 web 应用,还有额外的库,如jade-browser ( https://npmjs.org/package/jade-browser)和socket.io ( http://socket.io)。

为了让你快速开始使用 Express.js,并且不要太深入地钻研它的 API,我们将在本章中讨论这些主题:

  • Express.js 如何工作
  • Express.js 安装
  • Express.js 发电机安装

Express.js 如何工作

Express.js 是一个 Node 包管理器(NPM 或npm)模块,它依赖于您的应用。这意味着每个用/on Express.js 构建的项目都需要在本地node_modules文件夹中有框架的源文件(不是全局的!).为此,您可以像安装任何其他 NPM 模块一样安装 Express.js,使用$ npm install,例如$ npm install express@4.2.0

现在,我们可以概述一个 Express.js 应用的典型结构。假设您的应用在一个server.js文件中,您计划用$ node server.js启动您的应用。在这种情况下,您需要在server.js文件中要求并配置 Express.js。该文件通常包含完成以下任务的语句:

  1. 包括第三方依赖项以及您自己的模块,如控制器、实用程序、助手和模型
  2. Express.js 对象和其他对象的实例化
  3. 连接到 MongoDB 4 、Redis 5 或 MySQL 6 等数据库
  4. 配置 Express.js 应用设置,例如模板引擎及其文件扩展名
  5. 定义中间件,如错误处理程序、静态文件夹、cookies 和其他解析器
  6. 定义路由及其请求处理程序
  7. 启动将在特定主机和端口上启动服务器的应用

当然,如果您的应用很大,您将有多个文件来配置您的 Express.js 应用,而不仅仅是单个的server.jsapp.js文件。原因是更好的代码组织。例如,在一个文件中,您将配置会话,在另一个身份验证中,在另一个路由中,等等。

Image 提示在应用开发的高级阶段(通常导致部署到生产环境中),您可能希望使用forever ( https://npmjs.org/package/forever )模块和 Upstart 来实现更好的应用正常运行时间。你也可以利用第十三章中概述的cluster模块来产生多个工人。

第三方依赖关系

定义第三方依赖关系很简单:

var name = require('name');

依赖关系通常包括 Express.js 库本身,以及必要的中间件,如body-parser。定义多个依赖项的另一种方法是在每个定义后面跟一个逗号:

var express = require('express'),
  compression = require('compression'),
  bodyParser = require('body-parser'),
  mongo = require('mongoskin');

实例化

要使用 Express.js,需要实例化它。同时,实例化任何其他对象也是一个很好的做法:

var app = express();
var db = mongo.db('mongodb://localhost:27017/integration_tests', {native_parser: true});

Image 提示你不必命名 Express.js 模块express或者命名 Express.js 应用app。变量名可以是任意的。然而,本书中的大多数例子都使用了expressapp来避免混淆。

连接到数据库

连接到数据库的语句不必在开头,只要它们在第 7 步“启动应用”之前(来自本节前面的列表)——除非我们将数据库用作会话存储。例如:

var session = require('express-session');
var RedisStore = require('connect-redis')(session);
app.use(session({
  store: new RedisStore(options),
  secret: 'Pro Express.js rocks!'
}));

大部分数据库驱动,如 Mongoskin 7 和 mongose8都支持查询的缓冲;这样,如果服务器在连接建立之前正在运行,数据库查询将被缓冲以供以后执行(当数据库连接建立时)。

配置 Express.js 应用设置

简单来说,配置 Express.js app 设置就是用app.set()给字符串键设置一些值。其中一些键由 Express.js 使用并增强其行为,而其他键是任意的。例如,如果您正在使用 Jade 模板引擎和*.jade文件,使用'view engine'让 Express.js 知道它需要寻找*.jade文件:

app.set('view engine', 'jade');

在接下来的章节中,你会发现更多关于配置设置的信息。

有时我们希望在服务器对象上存储一个自定义值,以供将来参考。例如,我们可以将port赋给环境变量PORT中的一个值,或者,如果没有定义,赋给3000,这样我们就可以在所有源代码中使用这个值:

app.set('port', process.env.PORT || 3000);

定义中间件

中间件是一个特殊的功能,允许更好的代码组织和重用。一些中间件被打包成第三方(NPM)模块,可以开箱即用。其他时候,我们可以编写自己的自定义中间件。在这两种情况下,语法都是app.use():

app.use(bodyParser.json());

定义路线

路由可以是好的旧网页,也可以是 REST API 端点。在这两种情况下,语法是相似的:我们使用app.VERB(),其中VERB()是一个 HTTP 方法,比如 GET、POST、DELETE、PUT、OPTIONS 或 PATCH。例如,我们可以将主页(根)路由定义为

app.get('/', renderHomePage);

启动应用

最后,在配置好一切之后,我们可以用server.listen(portNumber)启动服务器,其中server是用app对象创建的核心http server对象:

var server = http.createServer(app);
var boot = function () {
  server.listen(app.get('port'), function(){
    console.info('Express server listening on port ' + app.get('port'));
  });
};
var shutdown = function() {
  server.close();
};

如果这个文件包含在另一个文件中(例如,一个测试),我们可能想要导出服务器对象,而不是引导它。我们执行检查的方式是使用require.main === module;如果这是真的,那么这个文件没有被其他任何东西包含。测试将使用我们导出的方法boot()自动启动服务器。我们还出口shutdown()port:

if (require.main === module) {
  boot();
} else {
  console.info('Running app as a module');
  exports.boot = boot;
  exports.shutdown = shutdown;
  exports.port = app.get('port');
}

当 Express.js 应用运行时,它会监听请求。每个传入的请求都根据定义的中间件链和路由进行处理,从上到下进行处理。这一点很重要,因为它允许您控制执行流程。

例如,我们可以有多个函数来处理每个请求,其中一些函数位于中间(因此被称为中间件):

  1. 解析 cookie 信息,完成后进入下一步。
  2. 解析 URL 中的参数,完成后进入下一步。
  3. 如果用户被授权(cookie/session),则根据参数值从数据库中获取信息,如果匹配,则进入下一步。
  4. 显示数据并结束响应。

Express.js 安装

Express.js 是一个依赖模块,应该安装在本地(项目)node_modules文件夹:$ npm install express@4.8.1

Image 提示 NPM 查找node_modules文件夹或package.json文件。如果这是一个全新的文件夹,既没有文件夹也没有文件,你可以用$ mkdir node_modules创建node_modules,或者用$ npm init创建package.json

对于作为依赖项的本地 Express.js 模块安装,让我们创建一个新文件夹,$ mkdir proexpressjs。这将是本书的项目文件夹。现在,我们可以用$ cd proexpressjs打开它。

Image 提示为了方便起见,大部分示例都位于 GitHub 资源库 azat-co/proexpressjs ( http://github.com/azat-co/proexpressjs)中。但是,我强烈建议您键入书中的代码,并使用您的文本、名称和自定义逻辑对其进行修改。不要复制/粘贴代码,甚至更糟——只需运行我们的 GitHub 示例。使用书中提供的完整源代码,GitHub 仅在您遇到困难或在您阅读完这本书后需要为您的项目使用一些 reciepe 时作为参考。这个建议是基于大量的研究,这些研究表明写作或打字的人比那些只看内容的人记忆和学习更有效。还要做笔记!

一旦我们进入项目文件夹,我们可以在文本编辑器中手动创建package.json或者使用$ npm init终端命令。当你使用这个命令时,会要求你提供项目名称、描述等细节,如图图 1-1 所示。

9781484200384_Fig01-01.jpg

图 1-1 。运行$ npm init 的结果

这是图 1-1 中带有普通$ npm init选项的package.json文件示例:

{
  "name": "ch1",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

最后,要使用 NPM 安装模块:

$ npm install express

前面的命令将获取 Express.js 的最新版本。但是,对于本书,建议您使用指定的版本,因此改为运行以下命令:

$ npm install express@4.8.1 --save

--save标志是可选的。它在package.json中为这个模块创建一个条目。

如果您想为现有项目安装 Express.js 并将依赖关系保存到package.json文件中(聪明的做法!)—已经存在于该项目的文件夹中—运行:

 $ npm install express --save.

Image 注意如果你试图在没有package.json文件或node_modules文件夹的情况下运行前面提到的$ npm install express命令,智能 NPM 将遍历目录树找到有这两个文件的文件夹。这种行为有点模仿 Git 的逻辑。有关 NPM 安装算法的更多信息,请参考https://npmjs.org/doc/folders.html的官方文档。

如果你的项目是空白的,使用$ npm init然后$ npm install express@4.8.1 --save的方法是好的。因此,该命令将下载 express 及其依赖项,并列出它们(如图图 1-2 ):

pg9.jpg

9781484200384_Fig01-02.jpg

图 1-2 。运行 npm install express@4.8.1 的结果-保存

信息express@4.8.1 node_modules/express很重要,因为它告诉我们the express被放置在哪里(在node_modules)。

或者,如果项目不为空,并且package.json已经包含了所有有用的信息,我们可以在package.json文件中添加我们的新依赖项及其版本号或一些意外的组合(不推荐生产应用使用),例如"express": "4.8.x""express": "4.8.1", or "*",然后运行$ npm install

添加了 Express.js v4.8.1 依赖项的package.json文件如下所示:

{
  "name": "proexpress",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express": "4.8.1"
  },
  "author": "",
  "license": "BSD"
}

$ npm install

为了仔细检查 Express.js 的安装及其依赖项,我们可以运行一个$ npm ls命令(参见图 1-3 ,它列出了这个本地项目中的所有模块:

pg11.jpg

9781484200384_Fig01-03.jpg

图 1-3 。运行npmls npm ls 和 ls node_modules 的结果

命令$ npm ls列出了这个项目(当前文件夹)的本地依赖项。此时,我们至少应该看到express@4.8.1(如图图 1-3 )。另一种检查方法是使用$ ls node_modules(也显示在图 1-3 中),它列出了node_modules中的文件夹。

Express.js 发电机安装

Express.js 生成器(express-generator)是一个独立的模块(从 express . js 4 . x 版开始,之前都是捆绑的)。它允许快速创建应用,因为它的脚手架机制接受命令行选项并基于它们生成 Express.js 应用。

要安装 Express.js Generator,一个用于搭建的命令行工具,从 Mac/Linux 机器上的任何地方运行$ npm install -g express-generator。(对于 Windows 用户,路径会有所不同。检查NODE_PATH环境变量,如果您想要一个不同的全局位置,请更新它。)安装命令将下载并链接$ express终端命令到正确的路径,以便我们以后创建新应用时可以访问它的命令行界面(CLI)。

当然,我们可以更具体一点,告诉 NPM 使用$ npm install -g express@4.2.0 .版本安装 4.2.0 版本的expressexpress-generator模块不必(通常也不会)匹配,因为它们是独立的模块。因此,您需要根据变更日志和 GitHub 自述文件来了解什么是兼容的。

Image 提示当将–g标志与npm install一起使用时,一个好的经验法则是将其用于命令行工具(例如,node-devmochasupervisorgrunt),而不是用于项目的依赖项。Node.js 中的依赖项必须本地安装到项目的node_modules文件夹中。

在图 1-4 中,我们尝试了express命令没有成功,然后安装express-generator,进入express命令行工具。

9781484200384_Fig01-04.jpg

图 1-4 。用-g 和$ express -V 运行 NPM 的结果

Image 注意最有可能的情况是,您的系统需要 root/administrator 权限才能写入该文件夹。在这种情况下,您将需要$ sudo npm install -g express-generator

注意图 1-4 中的路径/usr/local/lib/node_modules/express-generator。这与本地安装的express路径截然不同。这是发现错误的方法之一,如果你试图安装某个东西,但它不可用——检查位置!

是的,对于一些精通 Node.js 的读者来说,这可能是微不足道的,但我见过太多次了,一些来自 Python、Ruby 和 Java 背景的人试图全局安装express(依赖项),以便他们可以在所有项目中使用它。请不要这样做。

摘要

本章为理解 Express.js 的工作原理奠定了基础。通过讲述一个典型的 Express.js 应用结构,您已经了解了配置、中间件和路由等概念,我们将在整本书中更深入地讨论这些概念。最重要的是,您学习了如何将 Express.js 本地安装到一个空白文件夹和现有项目中,以及如何安装 Express.js Generator。

在下一章中,我们将构建我们的第一个 Express.js 应用,典型的 Hello World,并探索生成器/脚手架选项。


1

2

3

4

5

6

7

8

二、Hello World 示例

在本章中,为了帮助您使用 Express.js,我们将构建一个典型的编程示例 Hello World 应用。如果你已经构建了这个 Express.js 应用的一些变体(也许通过遵循在线教程),请随意跳到下一章,或者去第三章获取 API 方法和对象,或者去第十九章 - 22 获取示例。本章涵盖的主题如下:

  • 入门:从头开始创建 minimal Express.js 应用
  • 生成器命令:Express.js 生成器的命令行选项
  • MVC 结构和模块:组织 Express.js 应用代码的常用方法
  • 监视文件更改:开发技巧

入门指南

我一直喜欢自下而上的教学方法,从最基本的概念开始,向更复杂的概念发展。此外,我注意到,当开发人员学会如何从头开始创建一些东西,而不仅仅是修改现有的项目或使用样板文件时,他们会获得更多的信心。

Image 注意我鼓励读者键入所有代码,因为这样可以提高学习效率。然而,作为参考,对于那些仍然喜欢复制和粘贴的人来说,本章(和其他章节)的代码在位于https://github.com/azat-co/proexpressjs的 GitHub 库中。

首先,您将编写一个在端口 3000 上本地运行的 web 服务器。所以,当你在浏览器中打开http://localhost:3000位置时,你应该会看到 Hello World。3000 是 Express.js 应用事实上的标准端口号。

Image 提示当你导航到某个页面时,浏览器会发出 GET 请求,所以至少你的服务器应该处理 GET 请求。GET 和其他类型的请求可以用 CURL (Mac 1 或 Windows 2 )或类似的工具来执行。

在文件夹proexpressjs/ch2中,创建一个hello.js文件。使用你喜欢的文本编辑器,比如 Vim ( www.vim.org)、Emacs ( www.gnu.org/software/emacs/)、Sublime Text 2 ( www.sublimetext.com)或者 TextMate ( http://macromates.com)。文件hello.js服务器将利用 Express.js 因此,让我们包括这个库:

var express = require('express');

现在我们可以创建一个应用(例如,实例化一个 Express.js 对象):

var app = express();

web 服务器将在本地端口 3000 上运行,所以让我们在这里定义它:

var port = 3000;

接下来,让我们用app.get()函数定义通配符路由 ( *):

app.get('*', function(request, response){
  response.end('Hello World');
});

app.get()函数接受字符串格式的 URL 模式的正则表达式 3 。在我们的例子中,我们通过指定通配符*来处理所有的 URL。

Image 注意正则表达式在许多编程语言中广泛使用,并且工作方式类似,所以如果您已经在 Perl、PHP、Java 等语言中使用过它们,那么您已经知道如何在 JavaScript/Node.js 中使用它们中的大多数。例如,这是电子邮件正则表达式之一:/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

对于 Express.js,您可以在 routes 中使用 RegExps 来动态定义复杂的 URL 模式。要了解更多关于正则表达式的信息,请查看位于https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions的文档。

使用请求处理程序

app.get()方法的第二个参数是一个请求处理程序 。典型的 Express.js 请求处理程序类似于我们作为回调传递给 native/core Node.js http.createServer()方法的处理程序。对于那些不熟悉核心http模块的人来说,请求处理器是一个每当服务器接收到一个特定请求时就会执行的功能,通常由 HTTP 方法(例如GET)和 URL 路径(即没有协议、主机和端口的 URL)定义。Express.js 请求处理程序至少需要两个参数— request,或简称为req,和response,或res(稍后在第九章中会有更多相关内容)。类似于它们的核心对应物,我们可以通过response.pipe()和/或response.on('data', function(chunk) {...})利用可读和可写的流接口(http://nodejs.org/api/stream.html)。

输出终端消息

最后,我们启动 Express.js web 服务器,并在回调中输出一条用户友好的终端消息:

app.listen(port, function(){
  console.log('The server is running, ' +
    ' please, open your browser at http://localhost:%s',
    port);
});

为了运行这个脚本,我们从项目文件夹中执行$ node hello.js。你会看到“服务器正在运行,请在http://localhost:3000”打开你的浏览器,如图图 2-1 。

9781484200384_Fig02-01.jpg

图 2-1 。运行$ node hello.js 的结果

现在,如果您在http://localhost:3000(与http://127.0.0.1:3000http://0.0.0.0:3000http://localhost:3000/)相同)打开浏览器,无论 URL 路径是什么,您都应该会看到 Hello World 消息(参见图 2-2 )。URL 路径是域和端口后的字符串,所以对于http://localhost:3000/,是/,对于http://localhost:3000/messages/,是/messages/。当您在路线定义中使用“*”时,此路径无关紧要。

9781484200384_Fig02-02.jpg

图 2-2 。浏览器在http://localhost:3000/ads打开

这里提供了hello.js文件的完整代码供您参考:

var express = require('express');
var port = 3000;
var app = express();

app.get('*', function(request, response){
  resquest.end('Hello World');
});

app.listen(port, function(){
  console.log('The server is running, ' +
    ' please open your browser at http://localhost:%s',
     port);
});

增强应用

我们可以通过回显提供给服务器的名称以及“Hello”短语,使我们的示例更具交互性。为此,我们可以使用$ cp hello.js hello-name.js复制hello.js文件,并在前面示例中的无所不包的路线(all.get('*', ...))之前添加以下路线:

app.get('/name/:user_name', function(req,res) {
  res.status(200);
  res.set('Content-type', 'text/html');
  res.send('<html><body>' +
    '<h1>Hello ' + req.params.user_name + '</h1>' +
    '</body></html>'
  );
});

/name/:name_route路由内部,我们设置适当的 HTTP 状态码 ( 200表示 OK)和 HTTP 响应头,并将动态文本包装在 HTML bodyh1标签中。

Image 注意 response.send() 是一个特殊的 Express.js 方法,它方便地超越了核心 http 模块response.end() 中我们的老朋友所做的事情。例如,前者会自动为我们添加一个Content-Length HTTP 头。它还根据提供给它的数据扩充了Content-Type—第八章中提供了更多详细信息。

下面提供了hello-name.js文件的完整源代码(也可以在本书的可下载源代码中找到):

var express = require('express');
var port = 3000;
var app = express();

app.get('/name/:user_name', function(request,response) {
  response.status(200);
  response.set('Content-Type', 'text/html');
  response.end('<html><body>' +
    '<h1>Hello ' + req.params.user_name + '</h1>' +
    '</body></html>'
  );
});

app.get('*', function(request, response){
  response.end('Hello World');
});

app.listen(port, function(){
  console.log('The server is running, ' +
    ' please open your browser at http://localhost:%s',
     port);
});

关闭之前的服务器并启动hello-name.js脚本后,您将能够看到动态响应;例如,在您的浏览器中输入http://localhost:3000/name/azat会产生如图图 2-3 所示的屏幕。

9781484200384_Fig02-03.jpg

图 2-3 。动态 Hello 用户示例

到目前为止,我们已经从头开始创建了两个 Express.js 应用。它们中的每一个都只有几行代码。这应该给你信心,并说明用 Express.js 和 Node.js 创建 web 服务器是多么容易!但是有一种更快的方法——express . js 生成器。让我们来看看它的命令和选项。

发电机命令

与 Ruby on Rails 和许多其他 web 框架相比,Express.js 附带了一个命令行界面(CLI ),用于启动您的开发过程。CLI 为最常见的情况生成了一个基本基础。与 Rails 或 Sails 不同,Express generator 不支持添加路线/模型(在撰写本文时)。

如果你遵循了第一章中的全球安装说明,你应该能看到版本号,如果你在你机器上的任何地方运行$ express -V表单的话。如果您键入$ express -h$ express --help,您将获得可用选项及其用法的列表。在本书中,我们使用的是最新(截至本文撰写时)版本 4.2.0,它与 Express.js 4.x 兼容(参见**图 2-4 )。

9781484200384_Fig02-04.jpg

图 2-4 。检查 Express.js 生成器版本

要生成 skeleton Express.js app,我们需要运行一个终端命令 : express [options] [dir|appname](如express cli-app),选项如下:

  • -e--ejs增加 EJS 发动机支持(www.embeddedjs.com)。默认使用 Jade ( http://jade-lang.com/tutorial/)。
  • -H--hogan增加 hogan.js 引擎支持。
  • -c <engine> or --css <engine>为 Less ( http://lesscss.org)、Stylus ( http://learnboost.github.io/stylus)或 Compass ( http://compass-style.org)添加样式表<engine>支持;默认情况下,使用普通 CSS。
  • -f--force强制在非空目录上生成应用。

当然,这些选项是可选的,所以你只需运行express cli-app就能得到一个默认设置的应用。

如果省略了dir / appname选项,Express.js 将使用当前文件夹作为项目的基础来创建文件。否则,应用将位于指定的目录下。

生成 Skeleton Express.js 应用

为了进行试验,我们运行这个命令:$ express -e -c less -f cli-app。生成器工具将输出创建的文件,并建议运行命令来启动服务器。图 2-5 显示了输出。

9781484200384_Fig02-05.jpg

图 2-5 。运行$ express -e -c less -f cli-app 的结果

如您所见,Express.js 提供了一个健壮的命令行工具来快速生成样板文件。缺点是 Express.js 生成器方法不太容易配置。例如,当你手动创建应用时,可以使用 Handlebars 模板引擎(以及许多其他工具,不仅仅是由 CLI 提供的 Hogan、Jade、JSHTML 或 EJS ),但是 Express.js Generator 没有这个选项(在撰写本文时)。你会在第五章中找到更多关于使用 Express.js 不同模板引擎的信息。

接下来,我们将检查生成器创建了什么——换句话说,脚手架使用了什么应用结构。

检查应用的结构

让我们简单地检查一下应用的结构。项目的根文件夹包括两个非常重要的文件,app.jspackage.js,如图 2-5 中所示:create: cli-app/package.jsoncreate: cli-app/app.jsapp.js文件是主文件(如前一章所述),它连接所有其他部分。package.json文件拥有所有需要的依赖项(至少是express)。然后,我们有三个文件夹:

  • public:静态资产,如图像、浏览器 JavaScript 和 CSS 文件
  • views:模板文件,如*.jade,或本例中的*.ejs
  • 请求处理程序被抽象成独立的文件/内部模块

public文件夹在express-generator生成项目时有三个自己的文件夹:

  • images:用于存储图像
  • javascripts:对于前端 JavaScript 文件
  • stylesheets:对于 CSS,或者在我们的例子中,对于更少的文件(-c less选项)

routes文件夹有两个文件:index.js,处理主页(root 或/),和users.js,处理/users路线。

public文件夹中的文件夹不是强制的,你可以创建任意的文件夹,这些文件夹将通过express.static()中间件暴露在你的服务器的/路径上。比如public/img的内容会在http://localhost:3000/img有。我个人更喜欢imgjscss而不是imagesjavascriptsstylesheets。当您重命名/public中的文件夹时,您不需要在 Express.js 配置中做任何额外的更改。

您也可以重命名viewspublic文件夹本身,但是您需要在配置语句中进行一些额外的更改,即更改设置。我们将在第三章的中介绍这些设置。

App.js

在您喜欢的文本编辑器中打开主 web 服务器文件app.js。我们将简要介绍一下自动生成的代码以及它的功能,然后再深入探讨每一种配置(第三章)。

我们包括以下模块依赖关系:

var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

接下来,我们创建 Express.js 应用对象:

var app = express();

然后,我们定义配置设置。现在,你大概可以根据它们的名字猜出它们的意思。即从哪里获取模板文件(views)以及使用什么模板引擎(view engine)。关于这些参数的更多细节在第三章中提供,所以现在让我们跟随app.js文件:

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

我们接下来定义中间件(在第四章中详细讨论)来服务 favicon、记录事件、解析请求体、支持旧浏览器的 HTTP 方法、解析 cookies 和利用路由:

app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());

以下中间件负责将较少的样式编译成 CSS 样式,并提供给浏览器:

app.use(require('less-middleware')(path.join(__dirname, 'public')));

我们将文件夹(public)作为参数传递,让它递归地扫描这个文件夹中的任何*.less文件。CSS 文件名和 Less 文件名需要匹配,所以如果我们用/css/style.css(e.g., in HTML or Jade),就需要有/public/css/style.less。对于每个请求,Express.js 将编译得更少。

该语句负责从public文件夹中提供静态资产:

app.use(express.static(path.join(__dirname, 'public')));

路由在一个单独的文件中被定义为一个模块,所以我们只是传递函数的表达式,而不是在这里将它们定义为匿名请求处理程序:

app.get('/', routes.index);
app.get('/users', user.list);

Express.js 从process.env.NODE_ENV获取它的环境变量,例如,当服务器启动时或者在机器的配置中,它被作为NODE_ENV=production传递。有了这个条件,我们为开发环境启用了一个更明确的错误处理程序。生成器为我们提供了一个404(未找到)中间件,和两个 500(内部服务器错误)错误处理程序,一个用于开发(更详细),一个用于生产:

app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

与 Hello World 示例不同,此服务器不会立即启动,而是导出:

module.exports = app;

/bin/www中,服务器从app.js文件导入:

#!/usr/bin/env node
var debug = require('debug')('cli-app');
var app = require('../app');

该语句设置了一个名为port的自定义设置,以便稍后在服务器启动时使用:

app.set('port', process.env.PORT || 3000);

最后,用熟悉的listen()启动服务器:

var server = app.listen(app.get('port'), function() {
  debug('Express server listening on port ' + server.address().port);
});

以下是app.js的完整代码,供您参考:

var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());
app.use(require('less-middleware')(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

/// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

/// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

module.exports = app;

如果您导航($ cd cli-app)到项目文件夹并运行$ npm install,您应该观察到基于package.json条目的 NPM 模块的安装。安装完成后,运行npm start$ ./bin/www。当您在浏览器中导航到http://localhost:3000时,您应该会看到如图图 2-6 所示的响应。

9781484200384_Fig02-06.jpg

图 2-6 。由生成器创建的样板 Express.js 应用

Express.js 生成器并不是一个非常强大的工具,因为它在样板文件创建之后并没有做太多事情。然而,它为开发人员提供了可用于快速原型制作的样板/框架。此外,初学者还可以了解推荐的结构和代码组织,以及学习如何包含 Less 和express.static()等一些中间件。

下一节总结了关于 Express.js 应用的文件夹结构的一些常见模式和约定。

MVC 结构和模块

Express.js 是一个高度可配置的框架,这意味着我们可以应用任何我们认为合适的结构。正如我们在上一节中观察到的,生成器工具立即为我们生成了几个文件夹:publicviewsroutes。为了坚持通用的 MVC 范式,所缺少的是模型。如果您使用类似 mongose, 4 的东西,您可能想要创建一个名为models的文件夹,并将模型和/或模式对象(mongose 模式)放在那里。更高级的应用可能有一个嵌套的文件夹结构,如下所示:

pg28.jpg

最佳实践是将静态资产放在一个特殊的文件夹下。这些资产也可以用 CoffeeScript 等可编译语言编写。

如果您更喜欢重命名文件夹,只需确保更新您的app.js文件(或者其他主脚本文件,如果您正在用不同的文件名从头开始创建应用)中的相应代码。例如,如果我想从文件夹controllers中提供与我的用户相关的路线,我会像这样更新我的app.js文件:

var user = require('./controllers/user');

模块的模式不必很复杂。在主文件中,我们包含了带有require()函数的对象,在该模块文件中,我们应用了一个exports全局关键字来附加一个我们想要公开的方法(稍后在主文件中使用):

exports.list = function(req, res){
  res.send("respond with a resource");
};

这里有一个警告——或者说一个特性,取决于你如何看待它——如果我们省略文件名并需要一个文件夹,比如我们前面例子中的var routes = require('./routes');, node . js 将从那个文件夹中获取index.js文件,如果存在的话。当声明一些您可能希望在该特定文件夹的文件间共享的助手或实用函数时,这可能会很方便。

在第十三章中,我们将讨论如何使我们的应用本身成为一个模块,这样我们就可以在一个生产环境中产生多个进程(例如,workers)。

类似的方法也适用于模板文件夹。如果我们决定用templates代替views,我们需要将设置行改为

app.set('views', path.join(__dirname, 'views'));

在这一行中,app.set()函数的第一个参数是设置的名称views,第二个参数是以全局__dirname变量为前缀的动态值。?? 5T7__dirname变量返回正在执行的模块的系统路径。

监视文件更改

这个主题超出了 Express.js 的范围,但是我相信它非常重要,值得一提。Node.js 应用存储在内存中,如果我们对源代码进行更改,我们需要重新启动该进程—正在运行的 Node.js 程序。在 Mac OS X 上,这是通过在终端中按下以下组合键来实现的:Ctrl+C、向上箭头、Enter。

以下出色的文件查看工具可以利用来自核心 Node.js fs模块的watch()方法 6 ,并在我们保存来自编辑器的更改时重启我们的服务器。出于本书及其示例的目的,这些工具几乎是相同的,因此没有给出任何偏好;随便挑一个。

  • forever:在生产服务器上使用(https://npmjs.org/package/forever)
  • node-dev : 描述(https://npmjs.org/package/node-dev;GitHub: https://github.com/fgnass/node-dev
  • nodemon:支持 coffee script(https://npmjs.org/package/nodemon);GitHub: https://github.com/remy/nodemon
  • supervisor:由《NPM》的创作者之一https://npmjs.org/package/supervisor撰写;GitHub: https://github.com/isaacs/node-supervisor
  • up:由编写 Express.js 的团队编写(https://npmjs.org/package/up;GitHub: https://github.com/LearnBoost/up

Image 提示因为默认情况下,Express.js 会为每个新请求重新加载一个模板文件,所以不需要重启服务器。但是,我们可以通过启用view cache设置来缓存模板。这个和其他设置将在本书第三章的中详细介绍。

摘要

到目前为止,我们已经从头开始创建了一些应用,使用了 Express.js Generator 并探索了它的选项。然后我们看了一种构建 Express.js 应用和组织代码的方法。Express.js 生成器不是一个非常通用的工具,但是通过使用它,您可以使用不同的 CSS 库和模板引擎快速生成一些样板代码。

该框架的可配置性是 Express.js 的主要卖点之一,也是其越来越受欢迎的一个因素。这意味着 Express.js 的方式是提供默认设置,所以最起码你的服务器在没有额外配置的情况下也能正常工作(ch2/hello.js只有 13 行)。但与此同时,Express.js 允许熟练的开发人员以合理的方式轻松配置设置。谁会反对知道这些设定呢,对吧?因此,在第三章中,我们将探索 Express.js 中最重要的配置设置


1

2 http://www.confusedbycode.com/curl/#downloadshttp://curl.haxx.se/download.html

3

4

5

6

三、配置、设置和环境

本章介绍了配置 Express.js 设置的不同方法。正如在第一章中提到的,Express.js 将自己定位为一个超越常规框架的配置。因此,当谈到 Express.js 中的配置时,您几乎可以配置任何东西!为此,您需要使用配置语句并了解设置。

有哪些设定?可以将设置视为通常以全局或应用范围的方式起作用的键值对。设置可以增强服务器的行为,将信息添加到响应中,或者在以后用作参考。

有两种类型的设置:框架在后台使用的 Express.js 系统设置,以及开发人员为自己的代码使用的任意设置。前者有默认值,所以如果你不配置它们,应用仍然可以正常运行!所以,如果你不知道或者不使用 Express.js 的一些设置,也没什么大不了的。出于这个原因,不要觉得你必须记住所有的设置才能构建快速应用。如果您对特定方法或系统设置有任何疑问,请随时参考本章内容。

为了从简单到复杂,本章组织如下:

  • 配置 :设定设定值和获取设定值的方法
  • 设置 :设置的名称、默认值、影响以及如何增加值的示例
  • 环境 :确定一个环境并将应用置于该模式是任何严肃应用的一个重要方面。

本章的例子可以在ch3/app.js项目中找到,该项目位于http://github.com/azat-co/proexpressjs的 GitHub 存储库中。

配置

在使用设置之前,您需要了解如何在 Express.js 应用上应用它们。最常见和最通用的方法是使用app.set定义一个值,并使用app.get根据设置的键/名称检索该值。

其他配置方法不太通用,因为它们仅适用于基于其类型(布尔值)的某些设置:app.enable()app.disable()

app.set()和 app.get()

方法app.set(name, value)接受两个参数:namevalue。正如您可能猜到的,它设置了名称的值。例如,我们经常想要存储我们计划启动服务器的端口的值:

app.set('port', 3000);

或者,对于一个更高级和更现实的用例,我们可以从系统环境变量PORT (process.env.PORT)中获取端口。如果PORT环境变量未定义,我们就回到硬编码的值3000:

app.set('port', process.env.PORT || 3000);

前面的代码更短,相当于使用了一个if else语句:

if (process.env.PORT) {
  app.set(process.env.PORT);
} else {
  app.set(3000);
}

name值可以是 Express.js 设置或任意字符串。要获得该值,我们可以使用带有单个参数的app.set(name),或者我们可以使用更显式的方法app.get(name),如下例所示:

console.log('Express server listening on port ' + app.get('port'));

app.set()方法还将变量暴露给应用范围内的模板;例如,

app.set('appName', 'HackHall');

将在所有模板中可用,这意味着该示例在 Jade 模板布局中有效:

doctype 5
html
  head
    title= appName
  body
    block content

app.enable()和 app.disable()

有些 system Express.js 设置的类型为布尔值 true 和 false,而不是字符串类型,它们只能设置为布尔值 false 或 true。对于这样的标志,有简写版本;例如,作为app.set(name, true)app.set(name, false)函数的替代,您可以相应地使用简洁的app.enable(name)app.disable(name)调用。我推荐使用app.set(),因为不管设置的类型是什么,它都能保持代码的一致性。

例如,etag Express.js 设置是一个布尔值。它为浏览器缓存打开和关闭 ETag 头(稍后将详细介绍etag)。要用app.disable()关闭缓存,写一个语句:

app.disable('etag');

app.enabled()和 app.disabled()

为了检查上述值是真还是假,我们可以调用方法app.enabled(name)app.disabled(name)。例如,

app.disable('etag');
console.log(app.disabled('etag'));

会在 Express.js app 的上下文中输出true

设置

有两类设置:

  • Express.js 系统设置 :这些设置被框架用来确定某些配置。它们中的大多数都有默认值,所以省略了配置这些设置的基本应用将会工作得很好。
  • 自定义设置 :您可以存储任意名称作为设置,以备后用。这些设置是为您的应用定制的,您首先需要定义它们来使用。

系统设置的范围是 Express.js 文档中最难理解的部分之一,因为有些设置根本没有文档记录(在撰写本文时)。Express.js 足够灵活,你不必为了写应用而知道所有的设置。但是当你了解了所有的设置并开始使用你需要的设置后,你会对配置你的服务器更有信心。您将更好地理解框架的内部工作方式。

在本节中,您将了解以下设置:

  • env
  • view cache
  • view engine
  • views
  • trust proxy
  • jsonp callback name
  • json replacerjson spaces
  • case sensitive routing
  • strict routing
  • x-powered-by
  • etag
  • query parser
  • subdomain offset

为了说明实际的设置,我们写了一个ch3/app.js例子。为了避免混淆,我们现在不展示整个文件,而是在本节末尾提供源代码以供参考。

包封/包围(动词 envelop 的简写)

这个变量用于存储这个特定 Node.js 进程的当前环境模式。该值由 Express.js 从process.env.NODE_ENV开始自动设置(通过执行机器上的环境变量提供给 Node.js ),如果没有设置,则设置为development值。

env设置的其他最常见值如下:

  • development
  • test
  • stage
  • preview
  • production

Express.js 使用“production”和“development”值作为某些设置的默认值(view cache就是其中之一)。其他值只是约定俗成的,意味着只要保持一致,你可以随意使用。例如,你可以用qa代替stage

我们可以通过在代码中添加app.set('env', 'preview');process.env.NODE_ENV=preview来增加env的设置。不过,更好的方法是用$ NODE_ENV=preview node app启动一个 app,或者在机器上设置NODE_ENV变量。

了解应用运行的模式非常重要,因为与错误处理、样式表编译和模板呈现相关的逻辑可能会有很大的不同。显然,数据库和主机名因环境而异。

app.get('env')设置在ch3/app.js示例中显示为

console.log(app.get('env'));

这一行输出

"development"

当我们用$ NODE_ENV=development node app.js启动过程时,如果NODE_ENV被设置为development,或者当NODE_ENV未被设置时,前面的行被打印。在后一种情况下,使用“development”值的原因是 Express.js 在未定义时默认设置为“development”。

查看缓存

这个标志,如果设置为false,允许无痛开发,因为每次服务器请求模板时都会读取它们。另一方面,如果view cache被设置为true,它有助于模板编译缓存,这是生产中期望的行为。如果env设置为production,则view cache 默认开启。否则设置为false

查看引擎

view engine 设置保存模板文件扩展名(例如,'ext''jade'),以便在文件扩展名未被传递给请求处理器内部的res.render()函数时使用。

例如,如图 3-1 中的所示,如果我们从上一章的ch2/cli-app/app.js示例中注释掉这一行:

// app.set('view engine', 'ejs');

9781484200384_Fig03-01.jpg

图 3-1 。没有合适的模板扩展集的结果

服务器将无法定位该文件,因为我们在cli-app/routes/index.js中的指令太不明确:

exports.index = function(req, res){
  res.render('index', { title: 'Express' });
};

我们可以通过给cli-app/routes/index.js文件添加扩展名来解决这个问题:

exports.index = function(req, res){
  res.render('index.ejs', { title: 'Express' });
};

有关如何应用不同模板引擎的更多信息,请参考第五章。

视图

views 设置有一个指向模板目录的绝对路径(在 Mac 和 Unix 上以/开头)。该设置默认为项目根目录下views文件夹的绝对路径(主应用文件,如app.js所在的位置)。

正如在第二章的的“MVC 结构和模块”一节中提到的,改变模板文件夹名是很简单的。通常,当我们在app.js中为views设置自定义值时,我们使用path.join()__dirname全局变量——这给了我们app.js所在文件夹的绝对路径。例如,如果你想使用文件夹templates使用这个配置语句:

app.set('views', path.join(__dirname, 'templates'));

信任代理

如果您的 Node.js 应用在 Varnish 或 Nginx 等反向代理后面工作,请将trust proxy 设置为true。这将允许信任X-Forwarded-*报头,例如X-Forwarded-Proto ( req.protocol)或X-Forwarder-For ( req.ips)。默认情况下,trust proxy设置被禁用。

如果您想打开它(当您有代理服务器时),您可以使用以下语句之一:

app.set('trust proxy', true);
app.enable('trust proxy');

jsonp 回调名称

如果您正在构建一个应用(REST API 服务器),为来自托管在不同域上的前端客户端的请求提供服务,那么在进行 XHR/AJAX 调用时,您可能会遇到跨域限制。换句话说,浏览器请求仅限于同一个域(和端口)。解决方法是在服务器上使用跨源资源共享(CORS) 头。

如果您不想将 CORS 头文件应用到您的服务器上,那么带前缀的 JavaScript 对象文字符号(JSONP)是一个不错的选择。Express.js 有一个res.jsonp()方法,使得使用 JSONP 变得轻而易举。

Image 提示要了解更多关于 CORS 的信息,请前往http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

默认的回调名称是 JSONP 响应的前缀,通常在请求的查询字符串中提供,名称为callback;比如?callback=updateView。但是,如果你想使用不同的东西,只需将设置jsonp callback name设置为该值即可;例如,对于带有查询字符串 param ?cb=updateView的请求,我们可以使用这个设置:

app.set('jsonp callback name', 'cb');

这样,我们的响应将被包装在updateView JavaScript 代码中(当然,带有适当的Content-Type头),如图图 3-2 所示。

9781484200384_Fig03-02.jpg

图 3-2 。使用 cb 作为回调的查询字符串名称

在大多数情况下,我们不想改变这个值,因为默认的callback值在某种程度上被 jQuery $标准化了。ajax JSONP 函数。

如果我们在 Express.js 设置配置中将jsonp callback name设置为cb,但是用不同的属性进行请求,比如callback,那么路由不会输出 JSONP。它会默认为 JSON 格式,如图图 3-3 所示,没有函数调用的前缀,我们在图 3-2 中看到。

9781484200384_Fig03-03.jpg

图 3-3 。如果没有合适的回调参数,JSONP 默认为 JSON

json 替换器和 json 空间

同样,当我们使用 Express.js 方法res.json()时,我们可以应用特殊的参数:replacerspaces。这些参数被传递给应用范围内的所有JSON.stringify()功能 1JSON.stringify()是一个广泛使用的函数,用于将原生 JavaScript/Node.js 对象转换成字符串。

replacer参数就像一个过滤器。这个函数有两个参数:键和值。如果返回undefined,那么该值被省略。为了让键值对成为最终的字符串,我们需要返回值。你可以在 Mozilla 开发者网络(MDN)上阅读更多关于replacer的内容。22

Express.js 使用null作为json replacer的默认值。当我需要打印漂亮的 JSON 时,我经常使用JSON.stringify(obj, null, 2)

spaces参数本质上是一个缩进尺寸。它的值在开发中默认为2,在生产中默认为0。在大多数情况下,我们不去管这些设置。

在我们的示例应用ch3/app.js中,我们有一个/json路由,它向我们发回一个包含一本书信息的对象。我们将一个replacer参数定义为一个从对象中省略折扣代码的函数(我们不想公开这个信息)。并且spaces参数被设置为4,这样我们可以看到 JSON 被很好地格式化,而不是一些混乱的代码。/json路线的最终响应如图 3-4 中的所示。

9781484200384_Fig03-04.jpg

图 3-4 。设置了替换符和空格的 JSON 输出

以下是示例应用中使用的语句:

app.set('json replacer', function(key, value){
  if (key === 'discount')
    return undefined;
  else
    return value;
});
app.set('json spaces', 4);

如果我们移除json spaces,应用将产生图 3-5 中所示的结果。

9781484200384_Fig03-05.jpg

图 3-5 。未设置空格的 JSON 输出

区分大小写的路由

case sensitive routing 标志应该是不言自明的。当它是默认值false时,我们不考虑 URL 路径的大小写,当该值设置为 true 时,我们不考虑大小写。比如我们有app.enable('case sensitive routing');,那么/users/Users就不一样了。为了避免混淆,最好禁用此选项。

严格路由

下一个设置(或者一个标志,因为它有布尔意义)严格路由处理 URL 中尾部斜杠的情况。在strict routing 使能的情况下,比如app.set('strict routing', true');,路径会被区别对待;例如,/users/users/将是完全独立的航线。在示例ch3/app.js中,我们有两条相同的路由,但其中一条有一个尾随斜杠。它们发回不同的字符串:

app.get('/users', function(request, response){
  response.send('users');
})
app.get('/users/', function(request, response){
  response.send('users/');
})

因此,浏览器对于/users/users/会有不同的消息,如图图 3-6 所示。

9781484200384_Fig03-06.jpg

图 3-6 。启用严格路由时,/users 和 users/是不同的路由

默认情况下,该参数设置为false,这意味着尾部斜杠被忽略,带有尾部斜杠的路由将被视为与不带尾部斜杠的路由相同。我的建议是保留默认值;也就是说,将带斜线的路由视为与不带斜线的路由相同。如果您的 API 架构要求区别对待它们,那么这个建议就不适用。

x 供电的

x-powered- by 选项将 HTTP 响应报头X-Powered-By设置为Express值。该选项默认启用,如图 3-7 中的所示。

9781484200384_Fig03-07.jpg

图 3-7 。X-Powered-By Express 已启用(默认)

如果你想禁用x-powered-by(从响应中删除它)——这是出于安全原因推荐的,因为如果你的平台未知,就更难找到漏洞——那么应用app.set('x-powered-by', false)app.disable('x-powered-by'),这将删除 X-Powered-By 响应头(如示例ch3/app.js和图 3-8 所示的)。

9781484200384_Fig03-08.jpg

图 3-8 。X-Powered-By Express 被禁用,没有响应头

电子标签

ETag 3 (或实体标签)是一个缓存工具。它的工作方式类似于给定 URL 上内容的唯一标识符。换句话说,如果特定 URL 上的内容没有变化,ETag 将保持不变,浏览器将使用缓存。图 3-7 和图 3-8 包括一个 ETag 响应头的例子。这个例子的代码可以在ch3/app.js中找到。

如果有人不知道 etag 是什么,也不知道如何使用它,那么最好让 Express.js 默认的 ETag 设置保持原样,即 on (boolean true)。否则,要禁用 ETag,请使用app.disable('etag');,这将消除 ETag HTTP 响应头。

默认情况下,Express.js 使用“弱”ETag。其他可能的值有false(无 ETag)true(弱 ETag)strong(强 ETag)。Express.js 提供的最后一个选项(对于高级开发人员)是使用您自己的 ETag 算法:

app.set('etag', function (body, encoding) {
  return customEtag(body, encoding); // you define the customEtag function
})

如果您不熟悉弱或强的含义,下面是这些类型的 ETag 之间的差异的简短解释:相同的强 ETag 保证响应是完全相同的,而相同的弱 ETag 表示响应在语义上是相同的。因此,对于弱 ETags 和强 ETags,您将获得不同级别的缓存。当然,这是一个非常简短和模糊的解释。如果这个主题对你的项目很重要,请自己做研究。

查询分析器

一个查询字符串是在 URL 中问号后面发送的数据(例如,?name=value&name2=value2)。这种格式需要解析成 JavaScript/Node.js 对象格式才能使用。为了方便起见,Express.js 自动包含了这个查询解析。这是通过启用query parser设置来实现的。

query parser 的默认值是extended,它使用了qs模块的功能。 4 其他可能值有

  • false:禁用解析
  • true:使用qs
  • simple:使用核心querystring模块的功能(http://nodejs.org/api/querystring.html)

可以将您自己的函数作为参数传递,在这种情况下,您的自定义函数将用于解析,而不是解析库。如果您传递自己的函数,那么您的自定义解析函数必须接受一个字符串参数,并返回一个 JavaScript/Node.js 对象,该对象类似于来自核心querystring模块的parse函数的签名。 5

以下是我们将query parser设置为使用querystring、无解析和自定义解析函数的示例:

app.set('query parser', 'simple');
app.set('query parser', false);
app.set('query parser', customQueryParsingFunction);

子域偏移

subdomain offset 设置控制req.subdomains属性返回的值。当应用部署在多个子域上时,如http://ncbi.nlm.nih.gov,此设置非常有用。

默认情况下,主机名/URL 中的最后两个“子域”(最右边的两个部分)被删除,其余的在req.subdomains中以相反的顺序返回;所以对于我们的http://ncbi.nlm.nih.gov的例子,得到的req.subdomains['nlm', 'ncbi']

但是,如果 app 已经通过app.set('subdomain offset', 3);subdomain offset设置为3req.subdomains的结果将只是['ncbi'],因为 Express.js 会从右边开始丢弃三(3)个部分(nlmnihgov)。

环境

正如你们许多人所知,大多数应用不能在单一环境中运行。这些环境通常至少包括开发、测试和生产。每种环境都对应用提出了不同的要求。例如,在开发中,应用的错误消息需要尽可能详细,而在生产中,它需要对用户友好,并且不会将任何系统或用户的个人身份信息(PII 6 )数据泄露给黑客。

代码需要适应不同的环境,而不需要我们这些开发人员在每次部署到不同的环境时都必须修改它。

当然,我们可以根据process.env.NODE_ENV值写一些if else语句;例如:

if ('development' === process.env.NODE_ENV) {

如果上面的那行对你来说很奇怪,请记住它与process.env.NODE_ENV === 'development'完全相同。或者,您可以使用process.env.NODE_ENV == 'development',它会在比较之前将NODE_ENV转换为字符串(如果由于某种原因它还不是字符串)。

  *// Connect to development database*
} else if ('production' === process.env.NODE_ENV) {
  *// Connect to production database*
 }; *// Continue for staging and preview environments*

或者使用 Express.js env param(参考本章前面的“env”部分):

*// Assuming that app is a reference to Express.js instance*
if ('development' === app.get('env')) {
  *// Connect to development database*
} else if ('production' === app.get('env')) {
  *// Connect to production database*
 }; *// Continue for staging and preview environments*

app.get('env')的另一个例子是 skeleton Express.js Generator 应用中的一个。与生产或任何其他环境相比,它为开发环境应用了更详细的错误处理程序(从err对象发送整个 stacktrace ):

if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

如果环境不是development,Express.js 将使用这个没有向用户泄露堆栈跟踪的错误处理程序,而不是上面的错误处理程序:

app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

APP。配置

允许更优雅的环境配置的app.configure()方法在 Express.js 4.x 中被弃用,但是,您应该仍然知道它是如何工作的,因为您可能会在旧项目中遇到它。

当用一个参数调用 app.configure()方法时,它会将回调应用到所有的环境。例如,如果您想要为任何环境设置一个作者电子邮件和应用名称,那么您可以编写:

app.configure(function(){ app.set('appName', 'Pro Express.js Demo App'); app.set('authorEmail', 'hi@azat.co');

但是,如果我们传递两个(或更多)参数,第一个是环境,最后一个仍然是函数,那么只有当应用处于这些环境模式(例如,开发、生产)时,才会调用代码。

例如,您可以为开发设置不同的dbUri值(数据库连接字符串),并使用这些回调进行准备:

app.configure('development', function() { app.set('dbUri', 'mongodb://localhost:27017/db'); }); app.configure('stage', 'production', function() { app.set('dbUri', process.env.MONGOHQ_URL);

Image 提示 Express.js 经常使用输入参数数量和类型的差异来指导函数的行为。因此,请密切注意如何调用方法。

现在您已经熟悉了设置,下面是演示厨房水槽应用。在其中,我们收集了所有上述设置来说明示例。在检查代码时,请注意文件中配置语句的顺序!它们必须在var app实例化之后,但在中间件和路由之前。下面是示例服务器ch3/app.js的完整源代码:

var book = {name: 'Practical Node.js',
  publisher: 'Apress',
  keywords: 'node.js express.js mongodb websocket oauth',
  discount: 'PNJS15'
}
var express = require('express'),
  path = require('path');

var app = express();

console.log(app.get('env'));

app.set('view cache', true);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.set('port', process.env.PORT || 3000);

app.set('trust proxy', true);
app.set('jsonp callback name', 'cb');
app.set('json replacer', function(key, value){
  if (key === 'discount')
    return undefined;
  else
    return value;
});
app.set('json spaces', 4);

app.set('case sensitive routing', true);
app.set('strict routing', true);
app.set('x-powered-by', false);
app.set('subdomain offset', 3);
// app.disable('etag')

app.get('/jsonp', function(request, response){
  response.jsonp(book);
})
app.get('/json', function(request, response){
  response.send(book);
})
app.get('/users', function(request, response){
  response.send('users');
})
app.get('/users/', function(request, response){
  response.send('users/');
})
app.get('*', function(request, response){
  response.send('Pro Express.js Configurations');
})

if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}
var server = app.listen(app.get('port'), function() {
  console.log('Express server listening on port ' + server.address().port);
});

摘要

在本章中,我们介绍了如何使用 app.set()、app.disable()和 app.enable()等方法配置 Express.js 系统设置。您学习了如何使用 app.get()和 app.enabled()和 app.disabled()获取设置值。然后,我们讨论了所有重要的 Express.js 设置,它们的意义和价值。您还看到,设置可以是任意的,并用于存储特定于应用的自定义信息(例如,端口号或应用名称)。

如果你还记得第一章中的应用结构,中间件在主 Express.js 应用文件的配置部分之后。第三方中间件和定制中间件都可以与 Express.js 一起使用。当你编写自己的中间件时,这是一种重用和组织代码的方法。

NPM 上有大量的第三方 Express.js 中间件模块。它们可以完成从解析到认证的许多任务。通过使用第三方中间件,您可以增强和定制应用的行为。所以中间件可以被认为是它自己的一种配置(类固醇上的配置!).继续阅读,掌握最常用的中间件!


1

2

3

4

5

6

四、使用中间件

中间件是一种非常有用的模式,它允许开发人员在他们的应用中重用代码,甚至以 NPM 模块的形式与他人共享。中间件的本质定义是一个有三个自变量的函数:request(或req)、response ( res)、next。如果您正在编写自己的中间件,您可以使用任意的名称作为参数,但是最好坚持通用的命名约定。下面是一个如何定义自己的中间件的例子:

var myMiddleware = function (req, res, next) {
  // Do something with req and/or res
  next();
};

自己写中间件的时候,别忘了调用next()回调函数。否则,请求将挂起并超时。对于后续的中间件,请求(req)和响应(res)对象是相同的,因此您可以向它们添加属性(例如req.user = 'Azat')以便以后访问它们。

在本章中,我们将讨论以下主题:

  • 应用中间件:如何在 Express.js 应用中使用中间件
  • 必备中间件:最常用的中间件,Connect.js 中间件,4.x 版本之前是 Express.js 一部分的中间件
  • 其他中间件:最有用最流行的第三方中间件

与描述如何构建一个大型项目的传统技术书籍章节不同,本章广泛描述了最流行和最常用的中间件模块。类似于第三章,这一章有点类似于参考。为了向您演示中间件的特性,在ch4文件夹中有一个厨房水槽,这意味着它有许多不同的东西。像往常一样,代码将在书中列出,并在https://github.com/azat-co/proexpressjs的 GitHub repo 中提供。

应用中间件

为了设置中间件,我们使用来自 Express.js API 的app.use()方法。这适用于第三方中间件和内部中间件。

方法app.use()有一个可选的字符串参数路径和一个强制的函数参数回调。例如,为了实现一个带有日期、时间、请求方法和 URL 的日志记录器,我们使用了console.log()方法:

*// Instantiate the Express.js app*
app.use(function(req, res, next) {
  console.log('%s %s — %s', (new Date).toString(), req.method, req.url);
  return next();
});
*// Implement server routes*

另一方面,如果我们想要给中间件加上前缀,也称为 mounting ,我们可以使用path参数,该参数将这个特定中间件的使用限制为只有具有这样前缀的路由。例如,为了将日志记录限制为仅管理仪表板路由/admin,我们可以编写

*// Instantiate the Express.js app*
app.use('/admin', function(req, res, next) {
  console.log('%s %s — %s', (new Date).toString(), req.method, req.url);
  return next();
});
*// Actually implement the /admin route*

从头开始编写所有的东西,甚至像静态文件的日志记录和服务这样琐碎的事情,显然不是很有趣。因此,我们可以利用express.static()morgan中间件功能,而不是实现我们自己的模块。这里有一个使用express.static()morgan中间件的例子:

var express = require('express');
var logger = require('morgan');
*// Instantiate and configure the app*
app.use(logger('combined'));
app.use(express.static(__dirname + '/public'));
*// Implement server routes*

Image 注意在 Express.js 3 . x 版及更早版本(即 4.x 版之前)中,logger 是 express . js 的一部分,可以用express.logger()调用。

Static 是唯一一个仍然捆绑在 Express.js version 4.x 中的中间件,它的 NPM 模块是serve-static。静态中间件支持对静态资产的直通请求。这些资产通常存储在public文件夹中(有关推荐文件夹结构的更多信息,请参考第二章)。

下面是一个更高级的静态中间件示例,它将资产限制在各自的文件夹中。这称为挂载,通过向app.use()提供两个参数来实现:路由路径和中间件功能;

app.use('/css', express.static(__dirname + '/public/css'));
app.use('/img', express.static(__dirname + '/public/images'));
app.use('/js', express.static(__dirname + '/public/javascripts'));

全局路径避免了歧义,这就是我们使用__dirname的原因。

当您编写自己的中间件时,静态中间件在幕后使用的模式是另一个很好的技巧。它是这样工作的:如果你仔细观察,express.static()接受一个文件夹名作为参数。这使得中间件能够动态地改变其行为或模式。这种模式被称为单子,尽管熟悉函数式编程的人可能会认为单子是不同的东西。总之,这里的主要思想是,我们有一个存储数据并返回另一个函数的函数。

这种模式在 JavaScript/Node.js 和类似于serve-static的模块中的实现方式是使用return关键字。这里有一个例子,一个定制的myMiddleware函数接受一个参数,根据参数 deep 是否等于(===)到 A,返回不同的中间件 A 或者默认的中间件:

var myMiddleware = function (param) {
  if (param === 'A') {
    return function(req, res, next) { // <---Middleware A
      // Do A stuff
      return next();
    }
  } else {
    return function(req, res, next) { // The default middleware
      // Do default stuff
      return next();
  }
}

接下来显示的ch4/app.js示例演示了如何应用(app.use())中间件staticmorgan和其他。示例中使用的每个中间件的参数和路由都包含在各自的章节中。

ch4/app.js的完整源代码,演示如何应用中间件(并为您提供其他中间件模块的工作内容):

// Import and instantiate dependencies
var express = require('express'),
  path = require('path'),
  fs = require('fs'),
  compression = require('compression'),
  logger = require('morgan'),
  timeout = require('connect-timeout'),
  methodOverride = require('method-override'),
  responseTime = require('response-time'),
  favicon = require('serve-favicon'),
  serveIndex = require('serve-index'),
  vhost = require('vhost'),
  busboy = require('connect-busboy'),
  errorhandler = require('errorhandler');

var app = express();
// Configure settings
app.set('view cache', true);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.set('port', process.env.PORT || 3000);
app.use(compression({threshold: 1}));
app.use(logger('combined'));
app.use(methodOverride('_method'));
app.use(responseTime(4));
app.use(favicon(path.join('public', 'favicon.ico')));
// Apply middleware
app.use('/shared', serveIndex(
  path.join('public','shared'),
  {'icons': true}
));
app.use(express.static('public'));
// Define routes
app.use('/upload', busboy({immediate: true}));
app.use('/upload', function(request, response) {
  request.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
    file.on('data', function(data){
      fs.writeFile('upload' + fieldname + filename, data);
    });
    file.on('end', function(){
      console.log('File' + filename + 'is ended');
    });

  });
 request.busboy.on('finish', function(){
    console.log('Busboy is finished');
   response.status(201).end();
 })
});

app.get(
  '/slow-request',
  timeout('1s'),
  function(request, response, next) {
    setTimeout(function(){
      if (request.timedout) return false;
      return next();
    }, 999 + Math.round(Math.random()));
  }, function(request, response, next) {
    response.send('ok');
  }
);

app.delete('/purchase-orders', function(request, response){
  console.log('The DELETE route has been triggered');
  response.status(204).end();
});

app.get('/response-time', function(request, response){
  setTimeout(function(){
    response.status(200).end();
  }, 513);
});

app.get('/', function(request, response){
  response.send('Pro Express.js Middleware');
});
app.get('/compression', function(request, response){
  response.render('index');
})
// Apply error handlers
app.use(errorhandler());
// Boot the server
var server = app.listen(app.get('port'), function() {
  console.log('Express server listening on port' + server.address().port);
});

既然您已经知道如何应用第三方和内部中间件,下一步就是确定哪一个第三方中间件是必不可少的。以及开发人员可以获得什么,并允许他们将自己和队友从实现、维护和测试 NPM 模块提供的功能的“乐趣”中解救出来。

基本中间件

正如您在上一节中看到的,中间件只不过是一个接受reqres对象的函数。express . js 4 . x 版只提供了一个现成的中间件功能:express.static()。大多数中间件需要安装和导入。本质中间件通常源于 Sencha 的 Connect 库:http://www.senchalabs.org/connect/(NPM:https://npmjs.org/package/connect;GitHub: https://github.com/senchalabs/connect)。

使用中间件时要记住的主要事情是,中间件函数应用于app.use()函数的顺序关系到*,因为这是它们执行的顺序*。换句话说,开发人员需要小心中间件语句的顺序(在app.js中),因为这个顺序将决定每个请求通过相应中间件功能的顺序。

你已经困惑了吗?看看这个例子:一个会话(express-session)必须跟随一个 cookie ( cookie-parser),因为任何 web 会话都依赖 cookie 来存储会话 ID(它是由cookie-parser提供的)。如果我们移动它们,会议将无法进行!另一个例子是需要express-session的跨站点请求伪造中间件csurf

为了使这一点完全清楚,出于完全相同的原因,中间件语句放在路由之前。如果您将静态(express.static()serve-static)中间件放在路由定义之后,那么框架将通过响应来完成请求流,静态资产(例如,来自/public)将不会被提供给客户端。

让我们更深入地了解以下中间件:

  • compression
  • morgan
  • body-parser
  • cookie-parser
  • express-session
  • csurf
  • express.staticserve-static
  • connect-timeout
  • errorhandler
  • method-override
  • response-time
  • serve-favicon
  • serve-index
  • vhost
  • connect-busboy

压缩

compression 中间件(NPM: http://npmjs.org/compression ) gzips 传输数据。Gzip 或 GNU zip 是一个压缩工具。要安装compression v1.0.11,在您的终端项目的根文件夹中运行这个命令:

$ npm install compression@1.0.11 --save

您还记得中间件语句的顺序很重要吗?这就是为什么compression中间件通常被放在 Express.js 应用配置的最开始,这样它就在其他中间件和路由之前。利用compression()方法进行压缩:

var compression = require('compression');
// ... Typical Express.js set up...
app.use(compression());

Image 提示您需要将压缩 NPM 模块安装在项目(即本地)node_modules文件夹中。你可以通过$ npm install compression@1.0.10 --save或者将行"compression": "1.0.10"放入package.json文件并运行$ npm install来实现。

compression()方法不需要任何额外的参数,但是如果您是一名高级 Node.js 程序员,您可能希望使用 gzip 选项进行压缩:

  • threshold:以千位为单位的开始压缩的大小(即可以解压缩的最小大小,以千位为单位)
  • filter:过滤出要压缩的内容的功能;默认过滤器是compressible,可在https://github.com/expressjs/compressible获得。

Gzip 使用核心 Node.js 模块 zlib ( http://nodejs.org/api/zlib.html#zlib_options)并将这些选项传递给它:

  • chunkSize:要使用的块的大小(默认:16*1024)
  • windowBits:窗口大小
  • level:压缩等级
  • memLevel:要分配多少内存
  • strategy:应用什么 gzip 压缩算法
  • filter:默认测试Content-Type表头为jsontextjavascript的功能

有关这些选项的更多信息,请参见位于http://zlib.net/manual.html#Advanced的 zlib 文档。

ch4项目中,我们用一些虚拟文本创建了一个index.jade文件,然后将以下内容添加到app.js file中:

var compression = require('compression');
// ... Configurations
app.use(compression({threshold: 1}));

views/index.jade文件将使用一些 Lorem Ipsum 文本呈现h1p HTML 元素,如下所示:

h1 hi
p Lorem Ipsum is simply dummy text of ...

Image 提示要获得完整的 Jade 模板引擎教程,请查阅 Practical Node.js (Apress,2014)。

作为应用compression的结果,在 Chrome 浏览器开发者工具控制台,可以看到Content-Encoding: gzip响应头,如图图 4-1 所示。

9781484200384_Fig04-01.jpg

图 4-1 。内容编码是 gzip,使用压缩中间件

摩根

morgan 中间件(https://www.npmjs.org/package/morgan)根据指定的输出格式跟踪所有请求和其他重要信息。要安装morgan1 . 2 . 2 版,请使用

$ npm install morgan@1.2.2 --save

Morgan 要么接受一个选项对象,要么接受一个格式字符串(commondev等)。);例如,

var logger = require('morgan');
// ... Configurations
app.use(logger('common'));

或者

var logger = require('morgan');
// ... Configurations
app.use(logger('dev'));

或者

var logger = require('morgan');
// ... Configurations
app.use(logger(':method :url :status :res[content-length] - :response-time ms'));

传递给 morgan 函数的支持选项(上例中的logger())如下:

  • format:有输出格式的字符串;查看即将发布的令牌字符串和预定义格式列表。
  • stream:要使用的输出流默认为stdout,但也可以是其他任何东西,比如一个文件或另一个流。
  • buffer:缓冲间隔的毫秒数;如果未设置或不是数字,则默认为 1000 毫秒。
  • immediate:布尔值,当设置为true时,使记录器(morgan)根据请求而不是响应写日志行。

以下是可用的格式字符串参数或标记:

  • :req[header](例如:req[Accept])
  • :res[header](例如:res[Content-Length])
  • :http-version
  • :response-time
  • :remote-addr
  • :date
  • :method
  • :url
  • :referrer
  • :user-agent
  • :status

以下是 Morgan 附带的预定义格式/标记:

  • combined:同:remote-addr - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"
  • common:同:remote-addr - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length]
  • short:同:remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms
  • tiny:同:method :url :status :res[content-length] - :response-time ms
  • dev:简短彩色开发输出,带响应状态,同:method :url :status :response-time ms - :res[content-length]

你也可以定义你自己的格式。有关更多信息,请参考位于https://www.npmjs.org/package/morganmorgan文档。

正文分析器

body-parser模块 ( https://www.npmjs.org/package/body-parser)可能是所有第三方中间件模块中最本质的。它允许开发人员将传入的数据(如主体有效负载)处理成可用的 JavaScript/Node.js 对象。要安装body-parser v1.6.1,运行以下命令:

$ npm install body-parser@1.6.1

body-parser模块有以下独特的中间件:

  • json():处理 JSON 数据;例如{"name": "value", "name2": "value"}
  • urlencoded():处理 URL 编码的数据;例如name=value&name2=value2
  • raw():返回主体作为缓冲类型
  • text():以字符串类型返回正文

如果请求的 MIME 类型为application/json,那么json()中间件将尝试将请求有效负载解析为 JSON。结果将被放入req.body对象中,并传递给下一个中间件和路由。

我们可以将以下选项作为属性传递:

  • strict:布尔型truefalse;如果是true(默认),那么当第一个字符不是``或{时,400 状态错误(错误请求)将被传递给next()回调。
  • reviver:转换输出的JSON.parse()函数的第二个参数;更多信息请访问 MDN。 [1
  • limit:最大字节大小;默认情况下禁用。
  • inflate:给瘪了的身体充气;默认为true
  • type:要解析的内容类型;默认为json
  • verify:验证身体的功能。

例如,如果您需要跳过私有方法/属性(按照惯例,它们以下划线符号_开头),应用非严格解析,并且限制为 5,000 个字节,您可以输入以下内容:

var bodyParser = require('body-parser');
// ... Express.js app set up
app.use(bodyParer.json({
  strict: false,
  reviver: function(key, value) {
    if (key.substr(0,1) === '_') {
      return undefined;
    } else {
      return value;
    }
  },
  limit: 5000
}));
// ...Boot-up

urlencoded()

这个body-parser模块的urlencoded()中间件只解析带有x-ww-form-urlencoded头的请求。它利用 qs 模块的(https://npmjs.org/package/qs ) querystring.parse())函数,并将结果 JS 对象放入req.body

除了limittypeverifyinflateurlencoded()带一个extended布尔选项。extended选项是一个强制字段。当设置为true(默认值)时,body-parser使用qs模块(https://www.npmjs.org/package/qs)解析查询字符串。

如果将extended设置为false,body-parser 将使用核心 Node.js 模块querystring解析 URL 编码的数据。我建议将extended设置为true(即使用qs),因为它允许从 URL 编码的字符串中解析对象和数组。

如果您忘记了 URL 编码的字符串是什么样子,它是 URL 中问号(?)后面的name=value&name2=value2字符串。

我们也可以将limit参数传递给urlencoded()limit选项的工作方式类似于bodyParser.json()中间件中的limit,您可以在前面的代码片段中看到。例如,要将limit设置为 10,000:

var bodyParser = require('body-parser');
// ... Express.js set up
app.use(bodyParser.urlencoded({limit: 10000});

Image 注意在旧版本中,众所周知,bodyParser.multipart()中间件在处理大文件上传时容易出现故障。安德鲁·凯利在文章《不要将 bodyParser 与 Express.js 一起使用》中描述了确切的问题 2 当前版本的 Express.js v 4.x 对 bodyParser.multipart()的非捆绑支持。而是 Express.js 团队推荐使用卫生员、 3 威猛、 4 或者多方。 5

cookie 分析器

cookie-parser 中间件(https://www.npmjs.org/package/cookie-parser)允许我们从请求处理程序中的req.cookie对象访问用户 cookie 值。该方法采用一个字符串,该字符串用于对 cookies 进行签名。通常是一些巧妙的伪随机序列(如very secret string)。要安装cookie-parser v1.3.2,运行以下命令:

$ npm install cookie-parser@1.3.2

像这样使用它:

var cookieParser = require('cookie-parser');
// ... Some Express.js set up
app.use(cookieParser());

或者用秘密字符串(任意的随机字符串,通常存储在环境变量中):

app.use(cookieParser('cats and dogs can learn JavaScript'));

Image 注意 避免在 cookies 中存储任何敏感信息,尤其是与用户相关的信息(个人身份信息),比如凭据,或者他们的偏好。在大多数情况下,仅使用 cookies 来存储与服务器上的值相匹配的唯一且难以猜测的密钥(会话 ID)。这使您能够在后续请求中检索用户会话。

除了 secret 之外,cookieParser()还将这些选项作为第二个参数:

  • path:一个 cookie 路径
  • expires:cookie 的绝对截止日期
  • maxAge:cookie 的相对最长时间
  • domain:cookie 的网站域
  • secure:布尔值,表示 cookie 是否安全
  • httpOnly:布尔值,表示是否只有 HTTP

cookie-parser 有一些额外的方法:

  • JSONCookie(string):将字符串解析成 JSON 数据格式
  • JSONCookies(cookies):与JSONCookie(string)相同,但对象不同
  • signedCookie(string, secret):将 cookie 值解析为签名的 cookie
  • signedCookies(cookies, secret):与signedCookie(string, secret)相同,但对象不同

快速会话

express-session 中间件(https://www.npmjs.org/package/express-session)允许服务器使用 web 会话。这个中间件必须在其定义之前启用 cookie-parser(在app.js文件中更高)。要安装express-session v1.7.6,运行以下命令:

$ npm install express-session@1.7.6 --save

1.7.6 版中间件采用了这些选项:

  • key : Cookie 名称,默认为connect.sid
  • store:会话存储实例,通常是一个 Redis 对象(在第十二章中有详细介绍)
  • secret:用于对会话 cookie 进行签名,防止篡改;通常只是一个随机字符串
  • cookie:会话 cookie 设置,默认为{ path: '/', httpOnly: true, maxAge: null }
  • proxy:布尔值,表示在设置安全 cookies 时(通过"X-Forwarded-Proto")是否信任反向代理
  • saveUninitialized:强制保存新会话的布尔值(默认为真)
  • unset:用可能的值keepdestroy(默认为keep)控制在取消设置会话后是否要将会话保留在存储中
  • resave:强制保存未修改会话的布尔值(默认值为 true)
  • rolling:布尔值,在每次请求时设置一个新的 cookie,以重置到期时间(默认值为 false)
  • genid:生成会话 ID 的函数(默认为uid2 : https://www.npmjs.org/package/uid2https://github.com/coreh/uid2)

默认情况下,会话存储在内存中。然而,我们可以使用 Redis 来实现持久性,并在多台机器之间共享会话。有关 Express.js 会话的更多信息,请参考第三部分,尤其是第十二章。

-是吗

跨站点请求伪造(CSRF) 当客户端仍然拥有来自受保护网站(如银行网站)的会话信息,并且恶意脚本代表客户端提交数据(甚至可能是资金转账)时,就会发生这种情况。攻击之所以成功,是因为银行的服务器无法区分客户来自银行网站的有效请求和来自一些受损或不可信网站的恶意请求。浏览器有正确的会话,但用户不在银行网站的页面上!!!

为了防止 CSRF,我们可以通过在每个请求中使用一个令牌并根据我们的记录验证该令牌来启用 CSRF 保护。通过这种方式,我们知道我们服务了页面或资源,带有提交数据的后续请求来自该页面或资源。更多信息,请参考维基百科 CSRF 条目http://en.wikipedia.org/wiki/Cross-site_request_forgery

Express.js 通过在会话(req.session._csrf)中放置一个_csrf令牌并对照req.bodyreq.queryX-CSRF-Token头中的值验证该值,来处理csurf模块(https://www.npmjs.org/package/csurf)的 CSRF 保护。如果值不匹配,则返回 403 禁止的 HTTP 状态代码,这意味着资源被禁止(例如,参见http://en.wikipedia.org/wiki/HTTP_403)。默认情况下,csurf中间件不检查 GET、HEAD 或 OPTIONS 方法。要安装csurf v1.6.0,运行以下命令:

$ npm install csurf@1.6.0 --save

使用csurf v1.6.0 最简单的例子如下:

var csrf = require('csurf');
// ... Instantiate Express.js application
app.use(csrf());

csurf v1.6.0 采用以下附加参数:

  • value:将 request ( req)作为参数的函数,检查令牌是否存在,并返回值 true(找到)或 false(未找到)。看看下面的例子。
  • cookie:指定使用基于 cookie 的存储,而不是默认的基于会话的存储(不推荐)
  • ignoreMethods:在检查请求中的 CSRF 令牌时要忽略的 HTTP 方法的数组(默认值为['GET', 'HEAD', 'OPTIONS'])

您可以通过在value属性中传递回调函数来覆盖检查标记值存在的默认函数;例如,要使用不同的名称并只检查请求体,您可以使用

var csrf = require('csurf');
// ... Instantiate Express.js application
app.use(express.csrf({
  value: function (req) {
    return (req.body && req.body.cross_site_request_forgery_value);
  }
}));

csrf 中间件必须在 express- 会话cookie 解析器之后,并且可选地(如果你计划在请求体中支持令牌)在体解析器之后中间件:

var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var csrf = require('csurf');
// ... Instantiate Express.js application
app.use(bodyParser.json());
app.use(cookieParser());
app.use(session());
app.use(csrf());

express.static()

作为独立模块(https://www.npmjs.org/package/serve-static)的express.static()serve-static是 express . js 4 . x 版唯一附带的中间件,所以你不必安装它。换句话说,在引擎盖下,express.static()是一个serve-static模块:https://github.com/expressjs/serve-static。我们已经介绍了express. static (path, options)方法,该方法从指定的根路径向文件夹提供文件,例如:

app.use(express.static(path.join(__dirname, 'public')));

或者(不推荐,因为这可能在 Windows 上不起作用):

app.use(express.static(__dirname + '/public'));

相对路径也是一个选项:

app.use(express.static('public'));

express.static(path, options) v1.5.0(对于 Express.js v4.8.1)方法采用这些选项:

  • maxAge:为浏览器缓存 maxAge 设置的毫秒数,默认为0
  • redirect:布尔型truefalse(默认为true)表示当 URL 路径名为目录时,是否允许重定向到结尾斜杠(/)
  • dotfiles:表示如何处理隐藏的系统文件夹/文件(例如gitignore);可能的值有ignore(默认)、allowdeny
  • etag:布尔值,表示是否使用 ETag 缓存(默认为true)
  • extensions:布尔值,表示是否使用默认文件扩展名(默认为false)
  • index:标识索引文件;默认为index.html;一个数组、一个字符串和false(禁用)都是可能的值
  • setHeaders:设置自定义响应头的功能

下面是 express.static()高级用法的一个示例,其中包含一些任意值:

app.use(express.static(__dirname + '/public', {
  maxAge: 86400000,
  redirect: false,
  hidden: true,
  'index': ['index.html', 'index.htm']
}));

连接超时

connect-timeout模块 ( https://www.npmjs.org/package/connect-timeout)设置超时。建议仅在您怀疑可能比一般路线慢的特定路线(如'/slow-route')上使用该中间件。要使用connect-timeout v1.2.2,请安装:

$ npm install connect-timeout@1.2.2 --save

在您的服务器文件中,编写这些语句,如示例ch4/app.js所示:

var timeout = require('connect-timeout');
// ... Instantiation and configuration
app.get(
  '/slow-request',
  timeout('1s'),
  function(request, response, next) {
    setTimeout(function(){
      if (request.timedout) return false;
      return next();
    }, 999 + Math.round(Math.random()));
  }, function(request, response, next) {
    response.send('ok');
  }
);
// ... Routes and boot-up

$ node app运行服务器。然后,从单独的终端,用 CURL 发送几个 GET 请求:

$ curl http://localhost:3000/slow-request -i

响应应该超时大约一半时间,并显示 503 服务不可用状态代码。好的响应返回状态代码 200。两者如图 4-2 所示。可以在错误处理程序中定制消息。

9781484200384_Fig04-02.jpg

图 4-2 。超时中间件运行和不运行时的响应

errorhandler(错误处理程序)

errorhandler 中间件(https://www.npmjs.org/package/errorhandler)可以用于基本的错误处理。这在开发和原型制作中特别有用。这个模块不会做任何您自己不能用定制的错误处理中间件做的事情。然而,它会节省你的时间。对于生产环境,请考虑根据您的需要定制错误处理。

使用以下 NPM 命令完成errorhandler v1.1.1 模块安装:

$ npm install errorhandler@1.1.1 --save

我们在服务器文件中应用它,如下所示:

var errorHandler = require('errorhandler');
// ... Configurations
app.use(errorHandler());

或者,仅适用于开发模式:

if (app.get('env') === 'development') {
  app.use(errorhandler());
}

编写自己的错误处理程序是微不足道的。事实上,你已经在第二章第一节和第三章第三节中看到过了,在第一章中我们检查了由生成器生成的代码。例如,这是一个基本处理程序,它呈现一个带有错误消息的错误模板:

app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

如您所见,方法签名类似于请求处理程序或中间件,但它有四个参数,而不是像中间件那样有三个参数,或者像 core Node.js 请求处理程序那样有两个参数。这就是 Express.js 如何确定这是一个错误处理程序而不是中间件——函数定义中的四个参数:error ( err)、request ( req)、response ( res)和next

通过用错误对象调用next()从另一个中间件内部触发该错误处理程序;比如next(new Error('something went wrong'))。如果我们不带参数地调用next(),Express.js 会认为没有错误,并继续处理链中的下一个中间件。

方法覆盖

方法覆盖中间件(https://www.npmjs.org/package/method-override)使您的服务器能够支持客户机可能不支持的 HTTP 方法——例如,请求被限制为 GET 和 POST 的系统(比如浏览器中的 HTML 表单)。要安装method-override v2.1.3,运行:

$ npm install method-override@2.1.3 --save

method-override模块可以使用来自传入请求的X-HTTP-Method-Override=VERB报头:

var methodOverride = require('method-override');
// ... Configuratoins
app.use(methodOverride('X-HTTP-Method-Override'));

除了头,我们可以使用一个查询字符串。例如,用?_method=VERB支持请求:

var methodOverride = require('method-override');
// ... Configuratoins
app.use(methodOverride('_method'));

ch4/app.js中,在我们使用查询字符串方法和_method名称安装、导入和应用方法覆盖中间件之后,我们可以像这样定义一个删除路径:

app.delete('/purchase-orders', function(request, response){
  console.log('The DELETE route has been triggered');
  response.status(204).end();
});

在我们用$ node app 启动应用后,我们在一个单独的终端窗口中用 CURL 提交POST请求。在 URL 中,我们将_method指定为DELETE:

$ curl http://localhost:3000/purchase-orders/?_method=DELETE -X POST

Express.js 将这个 CURL 请求视为删除 HTTP 方法请求,我们将在服务器上看到以下消息:

The DELETE route has been triggered

对于 Windows 用户,CURL 可以从http://curl.haxx.se/download.html开始安装。或者,你可以使用 Chrome 开发者工具中的 jQuery 的$.ajax()函数。

响应时间

response-time 中间件(https://www.npmjs.org/package/response-time)在X-Response-Time报头中添加了从请求进入该中间件开始的时间(以毫秒为单位)。

要安装response-time v2.0.1,运行

$ npm install response-time@2.0.1 --save

response-time() 方法在需要包含在结果中的点之后取一些数字( 3 是缺省值)。让我们要求 4 位数:

var responseTime = require('response-time');
// ... Middleware
app.use(responseTime(4));

为了演示这个中间件的运行,用$ node app 运行ch4/app.js。服务器有这些关于响应时间中间件的陈述:

app.use(responseTime(4));
// ... Middleware
app.get('/response-time', function(request, response){
  setTimeout(function(){
    response.status(200).end();
  }, 513);
});

preceding /response-time 路线背后的想法是将响应延迟 513 ms。然后,在一个单独的终端窗口中,运行带有-icurl命令,以发出 GET 请求并输出响应信息:

$ curl http://localhost:3000/response-time -i

如图 4-3 所示,该标题出现在响应中:

X-Response-Time: 514.3193ms

9781484200384_Fig04-03.jpg

图 4-3 。带有显示响应时间的 X-Response-Time 标头的 HTTP 响应

serve-favicon

serve-favicon 中间件(https://www.npmjs.org/package/serve-favicon)可以让你把浏览器中默认的收藏夹图标改成自定义图标。

要安装static-favicon v2.0.1 模块,运行:

$ npm install serve-favicon@2.0.1 --save

要包含和应用中间件,运行

var favicon = require('serve-favicon');
// ... Instantiations
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

serve-favicon v2.0.1 模块有两个参数:

  • path:收藏图标文件的路径,或者图标数据的缓冲区(缓冲区是 Node.js 二进制类型)
  • options : maxAge 以毫秒为单位——缓存收藏图标多长时间;默认值为 1 天

当你运行ch4/app.js时,你会在标签上看到 webapplog.com 的标志,如图图 4-4 所示。

9781484200384_Fig04-04.jpg

图 4-4 。使用 serve-favicon 中间件时最喜欢的图标

发球指数

serve-index 中间件(https://www.npmjs.org/package/serve-index)使您能够基于特定文件夹的内容创建目录列表。可以把它想象成一个终端$ ls命令(或者 Windows 上的dir)。您甚至可以用自己的模板和样式表定制外观(选项将在本节稍后讨论)。

要安装serve-index v1.1.6,运行:

$ npm install serve-index@1.1.6 --save

要应用中间件,请在您的服务器文件中写入以下几行:

var serveIndex = require('serve-index');
// ... Middleware
app.use('/shared', serveIndex(
  path.join('public','shared'),
  {'icons': true}
));
app.use(express.static('public'));

serveIndex语句中,指定'/shared'文件夹,并将path.join('public', 'shared');路径传递给项目目录中的public / shared文件夹。图标值true(icons: true)表示显示图标。需要静态中间件来显示实际的文件。

这几行代码取自ch4/app.js,如果你运行它并导航到http://localhost:3000/shared,你会看到一个带有文件夹名(shared)和文件名(abc.txt)的网络界面,如图图 4-5 所示。

9781484200384_Fig04-05.jpg

图 4-5 。带有文件夹和文件的默认服务器索引 web 界面

如果你把浏览器的尺寸调整到足够窄,界面应该会改变——响应性!此外,由于默认的serve-index界面,还有搜索栏。

点击文件名abc.txt应该会打开显示消息“秘密文本”的文件,如图图 4-6 所示。这是使用expsess.static()中间件而不是serve-index的结果。

9781484200384_Fig04-06.jpg

图 4-6 。由静态中间件服务的文本文件

serve-index中间件将 options 对象作为第二个参数(第一个是路径)。这些选项可以具有以下属性:

  • hidden : Boolean 表示是否显示隐藏(点)文件;默认为false
  • view:显示模式(tilesdetails);默认为tiles
  • icons : Boolean 表示是否在文件名/文件夹名旁边显示图标;默认为false
  • filter:过滤功能;默认为false
  • stylesheet:CSS 样式表的路径(可选);默认为内置样式表
  • template:HTML 模板的路径(可选);默认为内置模板

在模板中,您可以使用:{directory}作为目录名,{files}作为文件链接的无序列表(<ol>)的 HTML,{linked-path}作为目录链接的 HTML,以及{style}作为指定的样式表和嵌入的图像。

Image 注意不要在系统文件夹和机密文件上随意使用 serve-index。最好将其限制在某个子文件夹中,比如public

甚么东西

vhost 中间件(https://www.npmjs.org/package/vhost)使您能够基于域使用不同的路由逻辑。例如,我们可以有两个 Express.js 应用,apiweb,分别根据域、api.hackhall.com 或www.hackhall.com来组织不同路线的代码:

var app =express()
var api = express()
var web = express()
// ... Configurations, middleware and routes
app.use(vhost('www.hackhall.com', web))
app.use(vhost('api.hackhall.com', api))
app.listen(3000)

要安装vhost v2.0.0,运行:

$ npm install vhost@2.0.0 --save

vhost v2.0.0 中间件有两个参数(如前面的例子所示):

  • domain : String 或 RegExp 例如,*.webapplog.com
  • server:服务器对象(expressconnect);比如api或者web

连接-餐馆工

connect-busboy模块 ( https://www.npmjs.org/package/connect-busboy)是 connect.js/Express.js 中间件,被构建用于busboy表单解析器(https://www.npmjs.org/package/busboy)。busboy表单解析器基本上接受传入的 HTTP(S)请求的多部分主体,并允许我们使用它的字段、上传的文件等等。要安装已经包含 busboy 的中间件 v0.0.1,运行

$ npm install connect-busboy@0.0.1 --save

然后,在您的服务器文件(app.js)中,编写类似下面的内容,以在/upload路径上实现文件上传功能:

var busboy = require('connect-busboy');
// ... Configurations
app.use('/upload', busboy({immediate: true }));
app.use('/upload', function(request, response) {
  request.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
    file.on('data', function(data){
      fs.writeFile('upload' + fieldname + filename, data);
    });
    file.on('end', function(){
      console.log('File' + filename + 'is ended');
    });

  });
 request.busboy.on('finish', function(){
    console.log('Busboy is finished');
   response.status(201).end();
 })
});

前面的示例将文件写入磁盘,并在完成后向客户端输出 201。在终端中,我们应该会看到带有“ended”字样的文件名。

要模拟没有网页表单的文件上传,我们可以使用我们的老朋友 CURL(一行命令):

$ curl -X POST -i -F name=icon -F filedata=@./public/favicon.ico http://localhost:3000/upload

我们上传的文件在ch4/public/favicon.ico中。这是早期 serve-favicon 示例中最受欢迎的图标。因此,项目文件夹中应该有一个名为uploadfiledatafavicon.ico的文件。并且在您的终端服务器窗口上,您应该看到消息:

File favicon.ico is ended
Busboy is finished

在你的终端客户端上(即 curl 窗口),你会看到 201 被创建。

Image 除了ch4的例子,请参见第四部分的章节,了解更多关于中间件的高级例子。

其他中间件

还有很多其他值得注意的模块兼容 Connect.js 和 Express.js,下面只是简单列举了一些目前比较流行的模块;每个月都会有更多的作品问世,其中一些已经停产或被放弃,所以请定期查看 NPM 的更新。您可以在https://www.npmjs.org/package/ package 名称中找到这些模块中的每一个,其中包名称是下面列表中模块的名称。

  • cookieskegrip :替代cookie-parser ( https://www.npmjs.org/package/cookieshttps://www.npmjs.org/package/keygriphttps://www.npmjs.org/package/cookie-parser)
  • cookie-session :基于 Cookie 的会话存储(https://www.npmjs.org/package/cookie-session)
  • raw-body :针对作为缓冲器的请求(https://www.npmjs.org/package/raw-body)
  • connect-multiparty :使用mutliparty,替代connect-busboy ( https://www.npmjs.org/package/connect-multipartyhttps://www.npmjs.org/package/multipartyhttps://www.npmjs.org/package/connect-busboy)
  • qs:替代查询和查询字符串 ( https://www.npmjs.org/package/qshttps://www.nodejs.org/api/querystring.html)
  • stconnect-staticstatic-cache:静态资产缓存 ( https://www.npmjs.org/package/sthttps://www.npmjs.org/package/connect-statichttps://www.npmjs.org/package/static-cache)
  • express-validator :传入数据验证/清理(https://www.npmjs.org/package/express-validator)
  • everyauthpassport??:认证授权中间件(https://www.npmjs.org/package/everyauthhttps://www.npmjs.org/package/passport
  • oauth2-server : OAuth2 服务器中间件(https://www.npmjs.org/package/oauth2-server)
  • helmet :安全中间件集合(https://www.npmjs.org/package/helmet)
  • connect-cors :支持 Express.js 服务器的跨产地资源共享(CORS)(https://www.npmjs.org/package/connect-cors)
  • connect-redis:express . js 会话的 Redis 会话存储(https://www.npmjs.org/package/connect-redis)

摘要

本章讲述了如何创建和应用您自己的定制中间件,以及如何安装和应用来自 NPM 的第三方中间件。您了解了最基本的中间件是如何工作的,它们的功能需要哪些参数,以及它们的行为方式。您可能已经注意到,在ch4示例项目中,我们为错误页面和压缩页面使用了一个小模板。

下一章是配置和中间件主题的延续。这些几乎是任何 Express.js 应用的不同部分,正如我们在第一章(或server.jsindex.js,意思是主 Express.js 文件)中讨论的高级app.js结构。下一个主题是关于配置由模板支持的视图。第四章深入探讨了我们如何使用不同的模板引擎。我们探索如何利用 Express.js 中最流行的选项,比如 Jade 和 Handlebars 以及其他库。


1

2

3

4

5