这是我参与更文挑战的第15天,活动详情查看:更文挑战
简介
http-errors 主要供 express、koa 等后端框架使用,用于便捷地创建 HTTP 异常状态。简单使用如下所示:
var createError = require('http-errors')
var express = require('express')
var app = express()
const port = 3001
app.use(function (req, res, next) {
console.log(req)
if (!req.user) return next(createError(401, 'Please login to view this page.'))
next()
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
依赖
http-errors 的外部依赖包有 depd、setprototypeof、statuses、inherits、toidentifier。
var deprecate = require('depd')('http-errors')
var setPrototypeOf = require('setprototypeof')
var statuses = require('statuses')
var inherits = require('inherits')
var toIdentifier = require('toidentifier')
depd
depd 主要用于输出提示信息以及实现向后兼容操作,简单使用如下所示:
var deprecate = require('depd')('LvLin-test')
deprecate('Hello, I am LvLin.') // 输出提示语
function sayHello () {
console.log('Hello world!')
}
// 实际运行了 sayHello,并输出了相关提示
const sayHi = deprecate.function(sayHello, 'sayHi; use "sayHello" instead')
sayHi()
效果如下图所示:
statuses
statuses 是一个处理 HTTP 状态码的工具库。需要注意的是 http-errors 使用的 statuses 低于 2.0 版本,因为在 2.x 版本中部分 api 已经不再支持。
"statuses": ">= 1.5.0 < 2",
statuses 的简单使用如下所示:
status[404] // => 'Not Found'
status['Not Found'] // => 404
status.codes // 获取所有状态码
// [
// 100, 101, 102, 103, 200, 201, 202, 203, 204,
// 205, 206, 207, 208, 226, 300, 301, 302, 303,
// 304, 305, 306, 307, 308, 400, 401, 402, 403,
// 404, 405, 406, 407, 408, 409, 410, 411, 412,
// 413, 414, 415, 416, 417, 418, 421, 422, 423,
// 424, 425, 426, 428, 429, 431, 451, 500, 501,
// 502, 503, 504, 505, 506, 507, 508, 509, 510,
// 511
// ]
statuses 的源码可以简单看一下,相关解释见注释:
'use strict'
// 引入状态码映射表,结构如下
// {
// "100": "Continue",
// "101": "Switching Protocols",
// "102": "Processing",
// "103": "Early Hints",
// ...
// }
var codes = require('./codes.json')
module.exports = status
// status.STATUS_CODES 保存了所有状态码的原因短语映射
status.STATUS_CODES = codes
// status.codes 保存了所有的状态码
status.codes = populateStatusesMap(status, codes)
// 与重定向相关的状态码
status.redirect = {
300: true,
301: true,
302: true,
303: true,
305: true,
307: true,
308: true
}
// 响应报文没有 body 的状态码
status.empty = {
204: true,
205: true,
304: true
}
// 暂时无法处理请求,需要重试请求的状态码
status.retry = {
502: true,
503: true,
504: true
}
// 将状态码跟原因短语绑定到 statuses 上
function populateStatusesMap (statuses, codes) {
var arr = []
Object.keys(codes).forEach(function forEachCode (code) {
var message = codes[code]
var status = Number(code)
// 状态码跟原因短语绑定到 statuses上
// 全小写的原因短语也考虑
statuses[status] = message
statuses[message] = status
statuses[message.toLowerCase()] = status
// 将状态码保存,为了返回给 status.codes
arr.push(status)
})
return arr
}
// 接受一个code,只能是数字或者字符串
// 如果是数字:如果是支持的状态码,返回该数字,否则抛出异常
// 如果字符串:1、尝试转成数字,如果非 NaN,判断是否是支持的状态码,返回该数字或者抛出异常
// 2、当成原因短语处理,判断是否有符合的原因短语,返回相应的状态码或者抛出异常
function status (code) {
if (typeof code === 'number') {
if (!status[code]) throw new Error('invalid status code: ' + code)
return code
}
if (typeof code !== 'string') {
throw new TypeError('code must be a number or string')
}
// '403'
var n = parseInt(code, 10)
if (!isNaN(n)) {
if (!status[n]) throw new Error('invalid status code: ' + n)
return n
}
n = status[code.toLowerCase()]
if (!n) throw new Error('invalid status message: "' + code + '"')
return n
}
setprototypeof
setprototypeof 是一个Object.setPrototypeOf的垫片库,跨平台,兼容到 IE8。
看一下源码实现:
'use strict'
/* eslint no-proto: 0 */
// 优先使用 Object.setPrototypeOf
// 判断使用 __proto__ 能不能改变原型链,如果可以就用 setProtoOf
// 如果不行就用 mixinProperties
module.exports = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array ? setProtoOf : mixinProperties)
// 可以通过 __proto__ 修改原型链,直接修改
function setProtoOf (obj, proto) {
obj.__proto__ = proto
return obj
}
// 无法通过 __proto__ 修改原型链
function mixinProperties (obj, proto) {
for (var prop in proto) {
// 找出非本对象已有的属性,绑定到对象上
if (!Object.prototype.hasOwnProperty.call(obj, prop)) {
obj[prop] = proto[prop]
}
}
return obj
}
inherits
inherits 用于实现对象的原型继承,简单使用如下所示:
function Base() {
this.name = 'base';
this.base = 1991;
}
function Sub() {
this.name = 'sub';
}
inherits(Sub, Base);
let objSub = new Sub;
objSub.base // 1991
看一下源码:
// inherits.js
try {
// 如果是 node 环境,用 util.inherits
var util = require('util');
/* istanbul ignore next */
if (typeof util.inherits !== 'function') throw '';
module.exports = util.inherits;
} catch (e) {
// 如果是浏览器环境就使用 inherits_browser.js
/* istanbul ignore next */
module.exports = require('./inherits_browser.js');
}
// inherits_browser.js
if (typeof Object.create === 'function') {
// 如果支持 Object.create,使用 object.create 实现寄生组合式继承
module.exports = function inherits(ctor, superCtor) {
if (superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
})
}
};
} else {
// 利用空的构造函数实现寄生组合式继承
module.exports = function inherits(ctor, superCtor) {
if (superCtor) {
ctor.super_ = superCtor
var TempCtor = function () {}
TempCtor.prototype = superCtor.prototype
ctor.prototype = new TempCtor()
ctor.prototype.constructor = ctor
}
}
}
toIdentifier
toIdentifier 用于将字符串转成符合规范的变量名,源码及解释如下所示:
function toIdentifier (str) {
return str
.split(' ') // 按照空格将字符串分割成数组
.map(function (token) {
// 各单词首字母大写
return token.slice(0, 1).toUpperCase() + token.slice(1)
})
.join('') // 所有单词重新拼接在一起
.replace(/[^ _0-9a-z]/gi, '') // 去掉特殊字符
}
http-errors 源码分析
先从几个简单的工具函数看起。
// 将状态码根据开头数字进行归类,比如 401、402 的 codeClass 为 400
function codeClass (status) {
return Number(String(status).charAt(0) + '00')
}
// 从名称标符获取类名,以 Error 结尾,
function toClassName (name) {
return name.substr(-5) !== 'Error'
? name + 'Error'
: name
}
// 修改函数的名称
function nameFunc (func, name) {
var desc = Object.getOwnPropertyDescriptor(func, 'name')
if (desc && desc.configurable) {
// 如果可修改,通过 Object.defineProperty 修改函数名称
desc.value = name
Object.defineProperty(func, 'name', desc)
}
}
create.HttpError 是用于继承的抽象类,不能直接调用。
module.exports.HttpError = createHttpErrorConstructor() // 生成一个抽象基类
function createHttpErrorConstructor () {
// 这是一个抽象类,不能直接调用,只是用于继承
// 如果直接调用,抛出异常
function HttpError () {
throw new TypeError('cannot construct abstract class')
}
inherits(HttpError, Error)
return HttpError
}
createError.isHttpError 用于判断实例是否是 HttpError 类型。
module.exports.isHttpError = createIsHttpErrorFunction(module.exports.HttpError)
function createIsHttpErrorFunction (HttpError) {
return function isHttpError (val) {
if (!val || typeof val !== 'object') {
return false
}
// 如果继承自 HttpError,返回 true
if (val instanceof HttpError) {
return true
}
// 通过 createError() 创建的自定义错误类型条件
return val instanceof Error &&
typeof val.expose === 'boolean' &&
typeof val.statusCode === 'number' && val.status === val.statusCode
}
}
初始化时,创建各个状态码异常类的构造函数,并绑定到 createError 上。
// 将各类错误的 constructor 绑定到 createError 上,方便调用,比如 new createError.NotFound()
populateConstructorExports(module.exports, statuses.codes, module.exports.HttpError)
function populateConstructorExports (exports, codes, HttpError) {
codes.forEach(function forEachCode (code) {
var CodeError
var name = toIdentifier(statuses[code])
switch (codeClass(code)) {
case 400: // 4xx 状态码,提供客户端异常类的构造函数
CodeError = createClientErrorConstructor(HttpError, name, code)
break
case 500: // 5xx 状态码,提供服务端异常类的构造函数
CodeError = createServerErrorConstructor(HttpError, name, code)
break
}
if (CodeError) {
// createError[404] === createError.NotFound
// 相应的 code 和 name 对应同一个构造函数
exports[code] = CodeError
exports[name] = CodeError
}
})
// 兼容旧版本的 I'mateapot
exports["I'mateapot"] = deprecate.function(exports.ImATeapot,
'"I\'mateapot"; use "ImATeapot" instead')
}
再来看看客户端异常类和服务端异常类的构造函数是怎么生成的。
createServerErrorConstructor 返回 ServerError 类构造函数。
function createServerErrorConstructor (HttpError, name, code) {
var className = toClassName(name)
function ServerError (message) {
// 创建 Error 实例,错误描述为传入值或者
var msg = message != null ? message : statuses[code]
var err = new Error(msg)
// 捕获构造点的堆栈跟踪,具体使用见 http://nodejs.cn/api/errors.html#errors_error_capturestacktrace_targetobject_constructoropt
Error.captureStackTrace(err, ServerError)
// err.__proto__ = ServerError.prototype
// 即让 err 成为 ServerError 类实例
setPrototypeOf(err, ServerError.prototype)
// 重定义 err 的错误描述
Object.defineProperty(err, 'message', {
enumerable: true,
configurable: true,
value: msg,
writable: true
})
// 重定义 err 的 name
Object.defineProperty(err, 'name', {
enumerable: false,
configurable: true,
value: className,
writable: true
})
return err
}
// ServerError 类继承自 HttpError 类
inherits(ServerError, HttpError)
// 将构造函数重命名为 name + 'Error',避免重名
nameFunc(ServerError, className)
ServerError.prototype.status = code
ServerError.prototype.statusCode = code
ServerError.prototype.expose = false
return ServerError
}
createClientErrorConstructor 返回 ClientError 类构造函数,其实现与ServerError 基本一一致。
function createClientErrorConstructor (HttpError, name, code) {
var className = toClassName(name)
function ClientError (message) {
// 创建 Error 类实例
var msg = message != null ? message : statuses[code]
var err = new Error(msg)
// 捕获构造点的堆栈跟踪,具体使用见 http://nodejs.cn/api/errors.html#errors_error_capturestacktrace_targetobject_constructoropt
Error.captureStackTrace(err, ClientError)
// err.__proto__ = ClientError.prototype
// 即让 err 成为 ClientError 类实例
setPrototypeOf(err, ClientError.prototype)
// 重定义 err 的错误描述
Object.defineProperty(err, 'message', {
enumerable: true,
configurable: true,
value: msg,
writable: true
})
// 重定义 err 的 name
Object.defineProperty(err, 'name', {
enumerable: false,
configurable: true,
value: className,
writable: true
})
return err
}
// ClientError 类继承自 HttpError 类
inherits(ClientError, HttpError)
// 将构造函数重命名为 name + 'Error',避免重名
nameFunc(ClientError, className)
ClientError.prototype.status = code
ClientError.prototype.statusCode = code
ClientError.prototype.expose = true
return ClientError
}
最后来看 createError() 方法的实现,详细解释见注释:
// 有两种调用方式,但是由于参数都是可省略的,所以需要考虑的参数情况比较多
// createError([status], [message], [properties])
// createError([status], [error], [properties])
function createError () {
var err
var msg
var status = 500
var props = {}
// 遍历参数,判断各个参数类型
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i]
// 如果是继承自 Error,就是参数列表中对应的 error
if (arg instanceof Error) {
err = arg
status = err.status || err.statusCode || status
continue
}
switch (typeof arg) {
case 'string': // 参数列表中的 message
msg = arg
break
case 'number': // 参数列表中的 status
status = arg
if (i !== 0) { // 如果 status 不是第一个参数,就报异常
deprecate('non-first-argument status code; replace with createError(' + arg + ', ...)')
}
break
case 'object': // 如果是 object,就是参数列表中的 properties
props = arg
break
}
}
// 只支持处理 4xx 和 5xx 类型的异常状态
if (typeof status === 'number' && (status < 400 || status >= 600)) {
deprecate('non-error status code; use only 4xx or 5xx status codes')
}
// 如果 status 不满足条件,status 置为 500
if (typeof status !== 'number' ||
(!statuses[status] && (status < 400 || status >= 600))) {
status = 500
}
// 获取对应状态码的构造函数,已经挂载到 createError 上,没有就获取 400 或 500 对应的构造函数
var HttpError = createError[status] || createError[codeClass(status)]
if (!err) {
// 异常类实例创建
err = HttpError
? new HttpError(msg)
: new Error(msg || statuses[status])
// // 捕获构造点的堆栈跟踪,具体使用见 http://nodejs.cn/api/errors.html#errors_error_capturestacktrace_targetobject_constructoropt
Error.captureStackTrace(err, createError)
}
if (!HttpError || !(err instanceof HttpError) || err.status !== status) {
err.expose = status < 500
err.status = err.statusCode = status
}
for (var key in props) {
if (key !== 'status' && key !== 'statusCode') {
err[key] = props[key]
}
}
return err
}
相关资料
最后
如果文章对你有帮助,给个赞呗~