express
# Express
https://www.expressjs.com.cn/ (opens new window)
https://www.expressjs.com.cn/4x/api.html (opens new window)
# 快速入门
# 安装
mkdir myapp
cd myapp
npm init -y
#将 package.json 中的入口 main 改为 app.js 或其他自定义入口文件
npm install express --save
#如果只是临时安装 Express,不想将它添加到依赖列表中,可执行如下命令:
npm install express --no-save
# Hello world 示例
# app.js
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.post('/', function (req, res) {
res.send('POST request to the homepage')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
# 运行
node app.js
# Express 项目生成器
https://www.expressjs.com.cn/starter/generator.html
通过应用生成器工具 express-generator
可以快速创建一个应用的骨架。
你可以通过 npx
(包含在 Node.js 8.2.0 及更高版本中)命令来运行 Express 应用程序生成器。
npx express-generator
对于较老的 Node 版本,请通过 npm 将 Express 应用程序生成器安装到全局环境中并使用:
$ npm install -g express-generator
$ express
例如,如下命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 Pug (opens new window) 模板引擎(view engine):
$ express --view=pug myapp
create : myapp
create : myapp/package.json
create : myapp/app.js
create : myapp/public
create : myapp/public/javascripts
create : myapp/public/images
create : myapp/routes
create : myapp/routes/index.js
create : myapp/routes/users.js
create : myapp/public/stylesheets
create : myapp/public/stylesheets/style.css
create : myapp/views
create : myapp/views/index.pug
create : myapp/views/layout.pug
create : myapp/views/error.pug
create : myapp/bin
create : myapp/bin/www
然后安装所有依赖包:
$ cd myapp
$ npm install
在 MacOS 或 Linux 中,通过如下命令启动此应用:
$ DEBUG=myapp:* npm start
在 Windows 命令行中,使用如下命令:
> set DEBUG=myapp:* & npm start
在 Windows 的 PowerShell 中,使用如下命令:
PS> $env:DEBUG='myapp:*'; npm start
然后在浏览器中打开 http://localhost:3000/
网址就可以看到这个应用了。
通过生成器创建的应用一般都有如下目录结构:
.
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.pug
├── index.pug
└── layout.pug
7 directories, 9 files
通过 Express 应用生成器创建应用只是众多方法中的一种。你可以不使用它,也可以修改它让它符合你的需求。
# 基本路由
路由是指确定应用程序如何响应客户端对特定端点的请求,该端点是 URI(或路径)和特定的 HTTP 请求方法(GET、POST 等)。
每个路由可以有一个或多个处理函数,当路由被匹配时执行。
路由定义采用以下结构:
app.METHOD(PATH, HANDLER)
app
是 的实例express
。METHOD
是一个HTTP 请求方法 (opens new window),小写。PATH
是服务器上的路径。HANDLER
是路由匹配时执行的函数。
以下示例说明了定义简单路由。
Hello World!
在首页回复:
app.get('/', function (req, res) {
res.send('Hello World!')
})
响应/
应用程序主页根路由 ( ) 上的 POST 请求:
app.post('/', function (req, res) {
res.send('Got a POST request')
})
响应对路由的 PUT 请求/user
:
app.put('/user', function (req, res) {
res.send('Got a PUT request at /user')
})
响应对/user
路由的 DELETE 请求:
app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user')
})
# 处理请求参数
在 Express 中,可以使用 req.params
、req.query
和 req.body
属性来获取不同类型的请求参数。
使用
req.params
获取路由参数,例如/users/:userId
这样的路由,:userId
就是一个路由参数,可以通过req.params.userId
来获取它的值。使用
req.query
获取查询参数,例如/?name=John&age=30
这样的 URL,可以通过req.query.name
和req.query.age
来获取它们的值。使用
req.body
获取请求体参数,例如 POST 请求提交的表单数据或 JSON 数据,需要使用中间件(如body-parser
)解析后才能获取。例如:const express = require('express'); const bodyParser = require('body-parser'); const app = express(); // 解析 application/json app.use(bodyParser.json()); app.post('/users', (req, res) => { const { name, age } = req.body; // 处理表单数据或 JSON 数据 });
除了这些方法外,还可以使用 req.header()
方法获取请求头信息,例如 req.header('User-Agent')
可以获取浏览器的 User-Agent 信息。
# 设置访问 / 时跳转到index.html
可以使用 Express 的 res.sendFile()
方法来实现在访问根路径时跳转到 index.html
页面。这个方法会将指定的文件发送到客户端,让客户端可以在浏览器中渲染该文件。
首先需要创建一个 index.html
文件,放在项目的根目录下或者其他指定的静态资源目录中。例如,在项目根目录下创建一个 public
文件夹,并将 index.html
文件放在其中。
然后,在 Express 应用程序中设置路由,将根路径 /
的访问请求重定向到 index.html
文件:
const express = require('express');
const app = express();
// 设置静态资源目录为 public
app.use(express.static('public'));
// 将访问根路径的请求重定向到 index.html
app.get('/', (req, res) => {
res.sendFile(__dirname + '/public/index.html'); // 这里也可以直接使用 /index.html
});
// 启动服务器
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
上面的代码中,我们使用了 express.static()
中间件来设置静态资源目录为 public
文件夹。这样,当客户端访问 /index.html
路径时,Express 会自动查找并返回该文件。
接着,我们使用 app.get()
方法设置路由,将根路径 /
的访问请求重定向到 index.html
文件。在回调函数中,使用 res.sendFile()
方法将文件发送给客户端。注意,需要使用 __dirname
变量来指定文件的绝对路径。
最后,我们启动服务器并监听 3000 端口,这样当客户端访问 http://localhost:3000/ 时,就会重定向到 index.html
文件。
# 托管静态文件
为了提供诸如图像、CSS 文件和 JavaScript 文件之类的静态文件,请使用 Express 中的 express.static
内置中间件函数。
此函数特征如下:
express.static(root, [options])
该root
参数指定从中提供静态资产的根目录。有关options
参数的更多信息,请参阅express.static (opens new window)。
例如,通过如下代码就可以将 public
目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:
app.use(express.static('public'))
现在,你就可以访问 public
目录中的所有文件了:
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html
Express 在静态目录查找文件,因此,存放静态文件的目录名不会出现在 URL 中。
如果要使用多个静态资源目录,请多次调用 express.static
中间件函数:
app.use(express.static('public'))
app.use(express.static('files'))
访问静态资源文件时,express.static
中间件函数会根据目录的添加顺序查找所需的文件。
注意:为获得最佳效果,请使用反向代理 (opens new window)缓存来提高服务静态资产的性能。
要为函数服务的文件创建虚拟路径前缀(路径实际上不存在于文件系统中)express.static
,请为静态目录指定挂载路径,如下所示: (opens new window)
app.use('/static', express.static('public'))
现在,你就可以通过带有 /static
前缀地址来访问 public
目录中的文件了。
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html
然而,您提供给函数的路径express.static
是相对于您启动node
进程的目录的。如果您从另一个目录运行 Express 应用程序,使用您要提供服务的目录的绝对路径会更安全:
const path = require('path')
app.use('/static', express.static(path.join(__dirname, 'public')))
欲了解更多关于 serve-static
函数及其参数的知识,请参考 serve-static (opens new window)。
# 示例
https://www.expressjs.com.cn/starter/examples.html
- auth (opens new window) - 使用登录名和密码进行身份验证
- 内容协商 (opens new window)- HTTP 内容协商
- cookie-sessions (opens new window) - 使用基于 cookie 的会话
- cookies (opens new window) - 使用 cookies
- 下载 (opens new window)- 将文件传输到客户端
- ejs (opens new window) - 使用嵌入式 JavaScript 模板 (ejs)
- error-pages (opens new window) - 创建错误页面
- error (opens new window) - 使用错误中间件
- hello-world (opens new window) - 简单的请求处理程序
- markdown (opens new window) - Markdown 作为模板引擎
- 多路由器 (opens new window)- 使用多个 Express 路由器
- multipart (opens new window) - 接受多部分编码的形式
- mvc (opens new window) - MVC 风格的控制器
- online (opens new window) - 使用
online
和redis
包跟踪在线用户活动 - params (opens new window) - 使用路由参数
- resource (opens new window) - 对同一资源的多个 HTTP 操作
- route-map (opens new window) - 使用地图组织路线
- 路由中间件 (opens new window)- 使用路由中间件
- route-separation (opens new window) - 根据每个资源组织路线
- 搜索 (opens new window)- 搜索 API
- session (opens new window) - 用户会话
- static-files (opens new window) - 提供静态文件
- vhost (opens new window) - 使用虚拟主机
- view-constructor (opens new window) - 动态渲染视图
- view-locals (opens new window) - 在中间件调用之间将数据保存在请求对象中
- 网络服务 (opens new window)- 简单的 API 服务
# 指南
# 路由
路由路径与请求方法相结合,定义了可以发出请求的端点。路由路径可以是字符串、字符串模式或正则表达式。
字符?
、+
、*
和()
是它们对应的正则表达式的子集。连字符 ( -
) 和点 ( .
) 由基于字符串的路径逐字解释。
$
如果您需要在路径字符串中使用美元字符 ( ),请将其转义包含在([
和中])
。例如,“ /data/$book
”处请求的路径字符串将为“ /data/([\$])book
”。
下面是一些基于字符串的路由路径示例。
此路由路径将匹配对根路由的请求,/
.
app.get('/', function (req, res) {
res.send('root')
})
此路由路径将匹配请求到/about
.
app.get('/about', function (req, res) {
res.send('about')
})
此路由路径将匹配请求到/random.text
.
app.get('/random.text', function (req, res) {
res.send('random.text')
})
以下是一些基于字符串模式的路由路径示例。
此路由路径将匹配acd
和abcd
。
app.get('/ab?cd', function (req, res) {
res.send('ab?cd')
})
此路由路径将匹配abcd
、abbcd
、abbbcd
等。
app.get('/ab+cd', function (req, res) {
res.send('ab+cd')
})
此路由路径将匹配abcd
, abxcd
, abRANDOMcd
, ab123cd
, 等等。
app.get('/ab*cd', function (req, res) {
res.send('ab*cd')
})
此路由路径将匹配/abe
和/abcde
。
app.get('/ab(cd)?e', function (req, res) {
res.send('ab(cd)?e')
})
基于正则表达式的路由路径示例:
此路由路径将匹配其中带有“a”的任何内容。
app.get(/a/, function (req, res) {
res.send('/a/')
})
此路由路径将匹配butterfly
and dragonfly
,但不匹配butterflyman
, dragonflyman
, 等等。
app.get(/.*fly$/, function (req, res) {
res.send('/.*fly$/')
})
# 路由参数
路由参数是命名的 URL 段,用于捕获在 URL 中它们的位置指定的值。捕获的值填充到对象中req.params
,路径中指定的路由参数的名称作为它们各自的键。
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }
要使用路由参数定义路由,只需在路由的路径中指定路由参数,如下所示。
app.get('/users/:userId/books/:bookId', function (req, res) {
res.send(req.params)
})
路由参数的名称必须由“单词字符”([A-Za-z0-9_])组成。
由于连字符 ( -
) 和点 ( .
) 按字面解释,因此可以将它们与路由参数一起用于有用的目的。
Route path: /flights/:from-:to
Request URL: http://localhost:3000/flights/LAX-SFO
req.params: { "from": "LAX", "to": "SFO" }
Route path: /plantae/:genus.:species
Request URL: http://localhost:3000/plantae/Prunus.persica
req.params: { "genus": "Prunus", "species": "persica" }
为了更好地控制路由参数可以匹配的确切字符串,您可以在括号 ( ()
) 中附加一个正则表达式:
Route path: /user/:userId(\d+)
Request URL: http://localhost:3000/user/42
req.params: {"userId": "42"}
因为正则表达式通常是文字字符串的一部分,所以一定要用
\
额外的反斜杠转义任何字符,例如\\d+
.
在 Express 4.x 中,正则表达式中的字符
*
不以通常的方式解释 (opens new window)。作为解决方法,使用{0,}
而不是*
。这可能会在 Express 5 中得到修复。
# 路由处理程序
您可以提供多个回调函数,它们的行为类似于中间件 (opens new window)来处理请求。唯一的例外是这些回调可能会调用next('route')
以绕过剩余的路由回调。您可以使用此机制在路由上施加先决条件,然后在没有理由继续当前路由的情况下将控制权传递给后续路由。
路由处理程序可以采用函数、函数数组或两者组合的形式,如以下示例所示。
单个回调函数可以处理路由。例如:
app.get('/example/a', function (req, res) {
res.send('Hello from A!')
})
不止一个回调函数可以处理一个路由(确保指定对象next
)。例如:
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})
一组回调函数可以处理一个路由。例如:
var cb0 = function (req, res, next) {
console.log('CB0')
next()
}
var cb1 = function (req, res, next) {
console.log('CB1')
next()
}
var cb2 = function (req, res) {
res.send('Hello from C!')
}
app.get('/example/c', [cb0, cb1, cb2])
独立函数和函数数组的组合可以处理路由。例如:
var cb0 = function (req, res, next) {
console.log('CB0')
next()
}
var cb1 = function (req, res, next) {
console.log('CB1')
next()
}
app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from D!')
})
# Response methods
下表中响应对象 (res ) 上的方法res
可以向客户端发送响应,并终止请求-响应循环。如果没有从路由处理程序调用这些方法,客户端请求将被挂起。
Method | Description |
---|---|
res.download() (opens new window) | 提示要下载的文件。 |
res.end() (opens new window) | 结束响应过程。 |
res.json() (opens new window) | 发送 JSON 响应。 |
res.jsonp() (opens new window) | 发送支持 JSONP 的 JSON 响应。 |
res.redirect() (opens new window) | 重定向请求。 |
res.render() (opens new window) | 渲染视图模板。 |
res.send() (opens new window) | 发送各种类型的响应。 |
res.sendFile() (opens new window) | 将文件作为八位字节流发送。 |
res.sendStatus() (opens new window) | 设置响应状态代码并将其字符串表示形式作为响应主体发送。 |
# 开发中间件
中间件函数是可以访问请求对象 (opens new window) ( req
)、响应对象 (opens new window)( res
) 和next
应用程序请求-响应周期中的函数的函数。该next
函数是 Express 路由器中的一个函数,当被调用时,它会执行当前中间件之后的中间件。
中间件函数可以执行以下任务:
- 执行任何代码。
- 更改请求和响应对象。
- 结束请求-响应循环。
- 调用堆栈中的下一个中间件。
如果当前中间件函数没有结束请求-响应循环,它必须调用next()
将控制权传递给下一个中间件函数。否则,请求将被挂起。
下图显示了中间件函数调用的元素:
从 Express 5 开始,返回 Promise 的中间件函数将
next(value)
在拒绝或抛出错误时调用。next
将被拒绝的值或抛出的错误调用。
# 例子
这是一个简单的“Hello World”Express 应用程序示例。本文的其余部分将定义三个中间件函数并将其添加到应用程序:一个调用myLogger
来打印简单的日志消息,一个调用requestTime
来显示 HTTP 请求的时间戳,另一个调用validateCookies
来验证传入的 cookie。
var express = require('express')
var app = express()
app.get('/', function (req, res) {
res.send('Hello World!')
})
app.listen(3000)
# 中间件函数 myLogger
这是一个名为“myLogger”的中间件函数的简单示例。当对应用程序的请求通过时,此函数仅打印“LOGGED”。中间件函数被分配给一个名为 的变量myLogger
。
var myLogger = function (req, res, next) {
console.log('LOGGED')
next()
}
注意上面对next()
. 调用此函数会调用应用程序中的下一个中间件函数。该next()
函数不是 Node.js 或 Express API 的一部分,而是传递给中间件函数的第三个参数。该next()
函数可以命名为任何名称,但按照惯例,它总是命名为“next”。为避免混淆,请始终使用此约定。
要加载中间件功能,请调用app.use()
,指定中间件功能。例如,下面的代码myLogger
在路由到根路径(/)之前加载中间件函数。
var express = require('express')
var app = express()
var myLogger = function (req, res, next) {
console.log('LOGGED')
next()
}
app.use(myLogger)
app.get('/', function (req, res) {
res.send('Hello World!')
})
app.listen(3000)
每次应用程序收到请求时,它都会向终端打印消息“LOGGED”。
中间件加载的顺序很重要:最先加载的中间件函数也最先执行。
如果myLogger
在到根路径的路由之后加载,请求永远不会到达它并且应用程序不会打印“LOGGED”,因为根路径的路由处理程序终止了请求-响应周期。
中间件函数myLogger
简单地打印一条消息,然后通过调用该函数将请求传递给堆栈中的下一个中间件函数next()
。
# 中间件函数请求时间
接下来,我们将创建一个名为“requestTime”的中间件函数,并添加一个名为requestTime
request 对象的属性。
var requestTime = function (req, res, next) {
req.requestTime = Date.now()
next()
}
该应用程序现在使用requestTime
中间件功能。此外,根路径路由的回调函数使用中间件函数添加到的属性req
(请求对象)。
var express = require('express')
var app = express()
var requestTime = function (req, res, next) {
req.requestTime = Date.now()
next()
}
app.use(requestTime)
app.get('/', function (req, res) {
var responseText = 'Hello World!<br>'
responseText += '<small>Requested at: ' + req.requestTime + '</small>'
res.send(responseText)
})
app.listen(3000)
当您向应用程序的根目录发出请求时,应用程序现在会在浏览器中显示您请求的时间戳。
# 中间件函数 validateCookies
最后,我们将创建一个中间件函数来验证传入的 cookie 并在 cookie 无效时发送 400 响应。
下面是一个使用外部异步服务验证 cookie 的示例函数。
async function cookieValidator (cookies) {
try {
await externallyValidateCookie(cookies.testCookie)
} catch {
throw new Error('Invalid cookies')
}
}
在这里,我们使用cookie-parser
(opens new window)中间件从对象中解析传入的 cookie req
,并将它们传递给我们的cookieValidator
函数。中间件validateCookies
返回一个 Promise,在拒绝时将自动触发我们的错误处理程序。
var express = require('express')
var cookieParser = require('cookie-parser')
var cookieValidator = require('./cookieValidator')
var app = express()
async function validateCookies (req, res, next) {
await cookieValidator(req.cookies)
next()
}
app.use(cookieParser())
app.use(validateCookies)
// error handler
app.use(function (err, req, res, next) {
res.status(400).send(err.message)
})
app.listen(3000)
注意next()
after 是如何调用的await cookieValidator(req.cookies)
。这确保如果cookieValidator
解决,堆栈中的下一个中间件将被调用。如果您向next()
函数传递任何内容(字符串'route'
或除外'router'
),Express 会将当前请求视为错误,并将跳过任何剩余的非错误处理路由和中间件函数。
因为您可以访问请求对象、响应对象、堆栈中的下一个中间件函数以及整个 Node.js API,所以中间件函数的可能性是无穷无尽的。
有关 Express 中间件的更多信息,请参阅:使用 Express 中间件 (opens new window)。
# 可配置的中间件
如果你需要你的中间件是可配置的,导出一个接受选项对象或其他参数的函数,然后返回基于输入参数的中间件实现。
文件:my-middleware.js
module.exports = function (options) {
return function (req, res, next) {
// Implement the middleware function based on the options object
next()
}
}
现在可以使用中间件,如下所示。
var mw = require('./my-middleware.js')
app.use(mw({ option1: '1', option2: '2' }))
有关可配置中间件的示例,请参阅cookie-session (opens new window)和压缩 (opens new window)。
# 使用中间件
# 覆盖 Express API
# 使用模板引擎
# 错误处理
错误处理是指 Express 如何捕获和处理同步和异步发生的错误。Express 带有默认的错误处理程序,因此您无需编写自己的错误处理程序即可开始使用。
# 捕获错误
确保 Express 捕获运行路由处理程序和中间件时发生的所有错误非常重要。
路由处理程序和中间件内的同步代码中发生的错误不需要额外的工作。如果同步代码抛出错误,Express 将捕获并处理它。例如:
app.get('/', function (req, res) {
throw new Error('BROKEN') // Express will catch this on its own.
})
对于由路由处理程序和中间件调用的异步函数返回的错误,您必须将它们传递给函数next()
,Express 将在该函数中捕获并处理它们。例如:
app.get('/', function (req, res, next) {
fs.readFile('/file-does-not-exist', function (err, data) {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
从 Express 5 开始,返回 Promise 的路由处理程序和中间件将next(value)
在拒绝或抛出错误时自动调用。例如:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
如果getUserById
抛出错误或拒绝,next
将使用抛出的错误或拒绝的值调用。如果没有提供拒绝值,next
将使用 Express 路由器提供的默认错误对象调用。
如果你向next()
函数传递任何东西(字符串除外'route'
),Express 会将当前请求视为错误,并将跳过任何剩余的非错误处理路由和中间件函数。
如果序列中的回调不提供数据,只提供错误,您可以简化此代码,如下所示:
app.get('/', [
function (req, res, next) {
fs.writeFile('/inaccessible-path', 'data', next)
},
function (req, res) {
res.send('OK')
}
])
在上面的示例中,next
作为回调提供fs.writeFile
,调用时有或没有错误。如果没有错误,则执行第二个处理程序,否则 Express 会捕获并处理错误。
您必须捕获路由处理程序或中间件调用的异步代码中发生的错误,并将它们传递给 Express 进行处理。例如:
app.get('/', function (req, res, next) {
setTimeout(function () {
try {
throw new Error('BROKEN')
} catch (err) {
next(err)
}
}, 100)
})
上面的示例使用一个try...catch
块来捕获异步代码中的错误并将它们传递给 Express。如果try...catch
省略该块,Express 将不会捕获错误,因为它不是同步处理程序代码的一部分。
使用 promises 来避免块的开销try...catch
,或者在使用返回 promise 的函数时。例如:
app.get('/', function (req, res, next) {
Promise.resolve().then(function () {
throw new Error('BROKEN')
}).catch(next) // Errors will be passed to Express.
})
由于 promises 会自动捕获同步错误和被拒绝的 promises,您可以简单地提供next
作为最终捕获处理程序,Express 将捕获错误,因为捕获处理程序将错误作为第一个参数。
您还可以使用一系列处理程序来依赖同步错误捕获,方法是将异步代码减少到一些微不足道的地方。例如:
app.get('/', [
function (req, res, next) {
fs.readFile('/maybe-valid-file', 'utf-8', function (err, data) {
res.locals.data = data
next(err)
})
},
function (req, res) {
res.locals.data = res.locals.data.split(',')[1]
res.send(res.locals.data)
}
])
上面的例子有几个来自readFile
调用的简单语句。如果readFile
导致错误,则它将错误传递给 Express,否则您将快速返回到链中下一个处理程序中的同步错误处理世界。然后,上面的示例尝试处理数据。如果失败,则同步错误处理程序将捕获它。如果你在回调中完成了这个处理,readFile
那么应用程序可能会退出并且 Express 错误处理程序将不会运行。
无论您使用哪种方法,如果您希望 Express 错误处理程序被调用并且应用程序继续运行,您必须确保 Express 接收到错误。
# 默认错误处理程序
Express 带有一个内置的错误处理程序,可以处理应用程序中可能遇到的任何错误。这个默认的错误处理中间件函数被添加到中间件函数栈的末尾。
如果您将错误传递给next()
并且您没有在自定义错误处理程序中处理它,它将由内置错误处理程序处理;该错误将与堆栈跟踪一起写入客户端。堆栈跟踪不包含在生产环境中。
将环境变量设置
NODE_ENV
为production
, 以在生产模式下运行应用程序。
写入错误时,将以下信息添加到响应中:
- 是
res.statusCode
从err.status
( 或err.statusCode
) 设置的。如果此值超出 4xx 或 5xx 范围,它将被设置为 500。 - 是
res.statusMessage
根据状态码设置的。 - 在生产环境中,body 将是状态码消息的 HTML,否则将是
err.stack
. - 对象中指定的任何标头
err.headers
。
如果您next()
在开始编写响应后调用错误(例如,如果您在将响应流式传输到客户端时遇到错误),Express 默认错误处理程序将关闭连接并使请求失败。
因此,当您添加自定义错误处理程序时,当标头已发送到客户端时,您必须委托给默认的 Express 错误处理程序:
function errorHandler (err, req, res, next) {
if (res.headersSent) {
return next(err)
}
res.status(500)
res.render('error', { error: err })
}
请注意,如果您next()
多次调用代码中的错误,则可能会触发默认错误处理程序,即使自定义错误处理中间件已就位。
# 编写错误处理程序
以与其他中间件函数相同的方式定义错误处理中间件函数,除了错误处理函数有四个参数而不是三个: (err, req, res, next)
。例如:
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
您最后定义错误处理中间件,在其他app.use()
和路由调用之后;例如:
var bodyParser = require('body-parser')
var methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(function (err, req, res, next) {
// logic
})
中间件函数内的响应可以采用任何格式,例如 HTML 错误页面、简单消息或 JSON 字符串。
出于组织(和更高级别的框架)的目的,您可以定义几个错误处理中间件函数,就像您使用常规中间件函数一样。例如,为使用XHR
和不使用的请求定义错误处理程序:
var bodyParser = require('body-parser')
var methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)
在此示例中,泛型logErrors
可能会将请求和错误信息写入stderr
,例如:
function logErrors (err, req, res, next) {
console.error(err.stack)
next(err)
}
同样在此示例中,clientErrorHandler
定义如下;在这种情况下,错误会明确传递给下一个错误。
请注意,当不在错误处理函数中调用“next”时,您负责编写(和结束)响应。否则这些请求将“挂起”并且不符合垃圾收集的条件。
function clientErrorHandler (err, req, res, next) {
if (req.xhr) {
res.status(500).send({ error: 'Something failed!' })
} else {
next(err)
}
}
实现“catch-all”errorHandler
功能如下(举例):
function errorHandler (err, req, res, next) {
res.status(500)
res.render('error', { error: err })
}
如果你有一个带有多个回调函数的路由处理程序,你可以使用route
参数跳到下一个路由处理程序。例如:
app.get('/a_route_behind_paywall',
function checkIfPaidSubscriber (req, res, next) {
if (!req.user.hasPaid) {
// continue handling this request
next('route')
} else {
next()
}
}, function getPaidContent (req, res, next) {
PaidContent.find(function (err, doc) {
if (err) return next(err)
res.json(doc)
})
})
在此示例中,getPaidContent
处理程序将被跳过,但app
for中的任何剩余处理程序/a_route_behind_paywall
将继续执行。
调用
next()
并next(err)
指示当前处理程序已完成以及处于什么状态。next(err)
将跳过链中所有剩余的处理程序,除了那些设置为如上所述处理错误的处理程序。
# 调试
# 为 Express 设置代理
在反向代理后面运行 Express 应用程序时,某些 Express API 可能会返回与预期不同的值。为了对此进行调整,trust proxy
应用程序设置可用于公开 Express API 中反向代理提供的信息。最常见的问题是公开客户端 IP 地址的快速 API 可能会显示反向代理的内部 IP 地址。
配置设置时
trust proxy
,了解反向代理的确切设置很重要。由于此设置将信任请求中提供的值,因此 Express 中的设置组合必须与反向代理的操作方式相匹配。
应用程序设置trust proxy
可以设置为下表中列出的值之一。
类型 | 价值 |
---|---|
布尔值 | 如果true ,则客户端的 IP 地址被理解为标头中最左侧的条目X-Forwarded-For 。如果false ,应用程序被理解为直接面向客户端,客户端的 IP 地址来自req.socket.remoteAddress 。这是默认设置。设置为 时true ,请务必确保最后一个受信任的反向代理正在删除/覆盖以下所有 HTTP 标头:X-Forwarded-For 、X-Forwarded-Host ,X-Forwarded-Proto 否则客户端可能会提供任何值。 |
IP地址 | 一个 IP 地址、子网或一组 IP 地址和子网,可作为反向代理信任。以下列表显示了预配置的子网名称:环回 - 127.0.0.1/8 ,::1/128 本地链接 - 169.254.0.0/16 ,fe80::/10 独特的地方 - 10.0.0.0/8 , 172.16.0.0/12 , 192.168.0.0/16 ,fc00::/7 您可以通过以下任一方式设置 IP 地址:app.set('trust proxy', 'loopback') // specify a single subnet app.set('trust proxy', 'loopback, 123.123.123.123') // specify a subnet and an address app.set('trust proxy', 'loopback, linklocal, uniquelocal') // specify multiple subnets as CSV app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']) // specify multiple subnets as an array 当指定时,IP 地址或子网被排除在地址确定过程之外,并且最接近应用程序服务器的不受信任的 IP 地址被确定为客户端的 IP 地址。这通过检查是否req.socket.remoteAddress 可信来工作。X-Forwarded-For 如果是,则从右到左检查每个地址,直到第一个不受信任的地址。 |
数字 | n 使用距离 Express 应用最多跳数的地址。是第一跳,其余在header中从右到左req.socket.remoteAddress 查找。X-Forwarded-For 值0 表示第一个不受信任的地址是req.socket.remoteAddress ,即没有反向代理。使用此设置时,重要的是要确保没有多个不同长度的路径到 Express 应用程序,以便客户端可以少于配置的跳数,否则客户端可能会提供任何值. |
功能 | 自定义信任实现。app.set('trust proxy', function (ip) { if (ip === '127.0.0.1' || ip === '123.123.123.123') return true // trusted IPs else return false }) |
启用trust proxy
会产生以下影响:
- req.hostname (opens new window)的值源自
X-Forwarded-Host
标头中设置的值,该值可以由客户端设置,也可以由代理设置。 X-Forwarded-Proto
可以由反向代理设置以告知应用程序它是否https
是http
无效名称。该值由req.protocol (opens new window)反映。- req.ip和req.ips (opens new window)值根据套接字地址和标头填充,从 (opens new window)第一个不受信任的地址开始。
X-Forwarded-For
该trust proxy
设置是使用proxy-addr (opens new window)包实现的。有关详细信息,请参阅其文档。
# 集成数据库
# MySQL
https://www.runoob.com/nodejs/nodejs-mysql.html (opens new window)
$ npm install mysql
var mysql = require('mysql')
var connection = mysql.createConnection({
host: 'localhost',
user: 'dbuser',
password: 's3kreee7',
database: 'my_db'
})
connection.connect()
connection.query('SELECT 1 + 1 AS solution', function (err, rows, fields) {
if (err) throw err
console.log('The solution is: ', rows[0].solution)
})
connection.end()
注意:
实际开发只用一次 connection.connect(),不需要 connection.end(),否则运行会报错。
查询数据
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
port: '3306',
database: 'test'
});
connection.connect();
var sql = 'SELECT * FROM websites';
//查
connection.query(sql,function (err, result) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
return;
}
console.log('--------------------------SELECT----------------------------');
console.log(result);
console.log('------------------------------------------------------------\n\n');
});
connection.end();
插入数据
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
port: '3306',
database: 'test'
});
connection.connect();
var addSql = 'INSERT INTO websites(Id,name,url,alexa,country) VALUES(0,?,?,?,?)';
var addSqlParams = ['菜鸟工具', 'https://c.runoob.com','23453', 'CN'];
//增
connection.query(addSql,addSqlParams,function (err, result) {
if(err){
console.log('[INSERT ERROR] - ',err.message);
return;
}
console.log('--------------------------INSERT----------------------------');
//console.log('INSERT ID:',result.insertId);
console.log('INSERT ID:',result);
console.log('-----------------------------------------------------------------\n\n');
});
connection.end();
更新数据
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
port: '3306',
database: 'test'
});
connection.connect();
var modSql = 'UPDATE websites SET name = ?,url = ? WHERE Id = ?';
var modSqlParams = ['菜鸟移动站', 'https://m.runoob.com',6];
//改
connection.query(modSql,modSqlParams,function (err, result) {
if(err){
console.log('[UPDATE ERROR] - ',err.message);
return;
}
console.log('--------------------------UPDATE----------------------------');
console.log('UPDATE affectedRows',result.affectedRows);
console.log('-----------------------------------------------------------------\n\n');
});
connection.end();
删除数据
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '123456',
port: '3306',
database: 'test'
});
connection.connect();
var delSql = 'DELETE FROM websites where id=6';
//删
connection.query(delSql,function (err, result) {
if(err){
console.log('[DELETE ERROR] - ',err.message);
return;
}
console.log('--------------------------DELETE----------------------------');
console.log('DELETE affectedRows',result.affectedRows);
console.log('-----------------------------------------------------------------\n\n');
});
connection.end();