一、覆盖ExpressAPI
1、简述
Express API由请求和响应对象上的各种方法和属性组成。这些由原型继承。Express API有两个扩展点:
express.request 和 express.response 中的全局属性和方法
app.request 和 app.response 中的属性和方法
本小白还清楚在js中有没有覆盖、重载、隐藏的概念,这里就用官方的字眼“覆盖”
2、示例:覆盖方法
示例展示:覆盖res.sendStatus方法
app.response.sendStatus = function (statusCode, type, message) {
return this.contentType(type)
.status(statusCode)
.send(message)
}
3、示例:覆盖属性
Express API中的属性包括:
指定的属性(例如:req.baseUrl, req.originalUrl)
定义的属性(例如:req.secure, req.ip)
由于“指定的属性”是在当前请求-响应周期的上下文中对请求和响应对象动态分配的,因此它们的行为不能被覆盖。
“定义的属性”可以覆盖。
示例展示:修改req.ip属性
Object.defineProperty(app.request, 'ip', {
configurable: true,
enumerable: true,
get: function () { return this.get('Client-IP') }
})
二、使用模板引擎
模板引擎能够在应用程序中使用静态模板文件。在运行时,模板引擎用实际值替换模板文件中的变量,并将模板转换为发送给客户机的HTML文件。这种方法使设计HTML页面更容易。
常用于Express中的模板引擎有Pug、Mustache、EJS、Jade。Express生成的默认项目使用的是Jade
要使用模板文件,在生成器创建的默认应用程序的app.js中设置,设置以下属性:
views:模板文件所在的目录。如:app.set('views', './views')。默认情况是应用程序根目录的views目录。
view engine:要使用的模板引擎。例如,要使用Pug模板引擎:app.set('view engine', 'pug')。
然后安装相应的模板引擎npm包;例如安装Pug:
npm install pug --save
与express兼容的模板引擎(如Jade和Pug)导出一个名为__express(filePath, options, callback)的函数,res.render()函数调用该函数来呈现模板代码。
视图引擎设置好后,不需要指定引擎或在应用中加载模板引擎模块;Express在内部加载模块
app.set('view engine', 'pug')
创建一个名为index.pug模板文件。Pug在views目录中,包含以下内容:
html
head
title= title
body
h1= message
然后创建一个路由来呈现index.pug文件。如果未设置视图引擎属性,则必须指定视图文件的扩展名。否则,您可以省略它。
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!' })
})
当您向主页发出请求时,index.pug 文件将呈现为HTML。
注意:视图引擎缓存不缓存模板输出的内容,只缓存底层模板本身。即使在缓存打开的情况下,每个请求仍然会重新呈现视图。
三、错误处理
错误处理指的是Express如何捕获和处理同步和异步发生的错误。Express自带一个默认的错误处理程序,直接使用即可。
1、错误抓取
1)同步代码错误抓取
发生在路由处理程序和中间件内的同步代码中的错误不需要额外的工作。如果同步代码抛出错误,那么Express将捕获并处理它。例如:
app.get('/', function (req, res) {
throw new Error('BROKEN') // 如果出错,Express会自动抓取到这个错误
})
2)异步代码错误抓取
对于由路由处理程序和中间件调用的异步函数返回的错误,必须将它们传递给next()函数,在那里Express将捕获和处理它们。例如:
app.get('/', function (req, res, next) {
fs.readFile('/file-does-not-exist', function (err, data) {
if (err) {
next(err) // 将错误传递给Express
} else {
res.send(data)
}
})
})
3)Express 5异步代码错误抓取
从Express 5开始,路由处理程序和中间件在拒绝或抛出错误时,将自动调用next(value) 。例如:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
如果getUserById抛出错误或拒绝,则用抛出的错误或拒绝的值调用next。如果没有提供拒绝的值,则使用Express路由器提供的默认Error对象调用next。
如果将任何内容传递给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。如果尝试……catch块,Express将不会捕获错误,因为它不是同步处理程序代码的一部分。
使用承诺来避免尝试的开销…Catch块或使用返回promise的函数时。例如:
app.get('/', function (req, res, next) {
Promise.resolve().then(function () {
throw new Error('BROKEN')
}).catch(next) // 错误将被传递给Express。
})
由于承诺自动捕获同步错误和被拒绝的承诺,您可以简单地提供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接收到错误。
2、错误默认处理
Express自带一个内置的错误处理程序,它负责处理应用程序中可能遇到的任何错误。这个默认的错误处理中间件函数添加在中间件函数栈的末尾。
如果你将一个错误传递给next(),而你没有在自定义错误处理程序中处理它,它将由内置错误处理程序处理;错误将与堆栈跟踪一起写入客户端。堆栈跟踪不包括在生产环境中。(将环境变量NODE_ENV设置为production,以在生产模式下运行应用程序)
当一个错误被写入时,以下信息被添加到响应中:
res.statusCode从err.status (or err.statusCode)获取。如果该值不在4xx或5xx范围内,则将其设置为500。
res.statusMessage根据状态代码设置。
在生产环境中,主体将是状态码消息的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(),则会触发默认的错误处理程序,即使已经有了定制的错误处理中间件。
3、自定义错误处理
自定义错误处理和普通中间件函数一样,只是错误处理函数有四个参数而不是三个:(err, req, res, next)。例如:
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
在其他app.use()和routes调用之后,最后定义错误处理中间件;例如:
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) {
// 错误处理逻辑
})
中间件函数内部的响应可以是任何格式,例如HTML错误页面、简单消息或JSON字符串。
出于组织(和更高级别框架)的目的,可以定义几个错误处理中间件函数,就像处理常规中间件函数一样。例如,为使用XHR和不使用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中/a_route_behind_paywall的任何剩余处理程序将继续执行
调用next()和next(err)表示当前处理程序已完成以及处于何种状态。Next (err)将跳过链中所有剩余的处理程序,除了上面描述的为处理错误而设置的处理程序。
四、调试
1、简述
Express在内部使用调试模块来记录关于路由匹配、正在使用的中间件功能、应用程序模式和请求-响应周期流的信息。
Debug类似于console.log的增强版本,但与console.log不同的是,不必在生产阶段将调试日志代码注释掉。日志记录在默认情况下是关闭的,可以通过使用DEBUG环境变量有条件地打开。
2、环境变量:DEBUG
要查看Express中使用的所有内部日志,在启动应用程序时将DEBUG环境变量设置为Express:*。
$ DEBUG=express:* node index.js
在Windows上,使用相应的命令。
> set DEBUG=express:* & node index.js
在express生成器生成的默认应用程序上运行此命令将输出以下输出:
$ DEBUG=express:* node ./bin/www
express:router:route new / +0ms
express:router:layer new / +1ms
略……
当向应用程序发出请求时,你会看到Express代码中指定的日志:
express:router dispatching GET / +4h
express:router query : / +2ms
express:router expressInit : / +0ms
略……
只从路由器实现中查看日志,将DEBUG的值设置为: express:router。同样,要查看仅来自应用程序实现的日志,请将DEBUG的值设置为express:application,,等等。
3、通过express生成的应用程序
express命令生成的应用程序也使用调试模块,其调试名称空间的范围是应用程序的名称。
例如,如果你用$ express sample-app生成应用程序,你可以用下面的命令启用调试语句:
$ DEBUG=sample-app:* node ./bin/www
你可以通过指定一个逗号分隔的名称列表来指定多个调试名称空间:
$ DEBUG=http,mail,express:* node index.js
4、高级选项
当运行Node.js时,你可以设置一些环境变量来改变调试日志记录的行为:
名字 | 目的 |
---|---|
debug | 打开/关闭特定的调试名称空间。 |
debug_colors | 是否在调试输出中使用颜色。 |
debug_depth | 对象检测深度。 |
debug_fd | 用来写入调试输出的文件描述符。 |
debug_show_hidden | 显示被检查对象的隐藏属性。 |
注意:以DEBUG_开头的环境变量最终会被转换为Options对象。
五、为Express设置代理
当在反向代理后运行Express应用程序时,一些Express api可能会返回与预期不同的值。为了对此进行调整,可以使用信任代理应用程序设置公开Express api中反向代理提供的信息。最常见的问题是,公开客户端IP地址的快速api可能会显示反向代理的内部IP地址。
在配置信任代理设置时,了解反向代理的确切设置非常重要。由于此设置将信任请求中提供的值,因此Express中的设置组合必须与反向代理的操作方式相匹配。
要信任作为反向代理的IP地址、子网或IP地址和子网的数组。预配置的子网名称如下所示:
app.set('trust proxy', 'loopback') // 指定单个子网
app.set('trust proxy', 'loopback, 123.123.123.123') // 请指定子网和地址
app.set('trust proxy', 'loopback, linklocal, uniquelocal') // CSV格式指定多个子网
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']) // 数组格式指定多个子网
启用信任代理将产生以下影响:
req.hostname 是从X-Forwarded-Proto头中设置的值派生出来的,该值可以由客户端或代理设置。
X-Forwarded-Proto:可以通过反向代理设置来告诉应用程序它是HTTPS还是HTTP,甚至是一个无效的名称。该值由req.protocol反映。