Node常用中间件

http-errors

创建express/koa链接的http错误

API

createError(status, message | error, properties)

  • expose 可以用来信号,如果消息应该发送到客户端,当状态>= 500时默认为false
  • headers 设置请求头,
  • message 设置错误信息
  • status 设置错误码

new createError(code, name)

1
2
new createError['401']('找不到')
new createError.BadRequest('找不到')

错误码详情

解析Cookie标头并使用由cookie名称键入的对象填充req.cookies。

API

未设置签名:

1
2
3
4
5
const express = require('express')
const cookieParser = require('cookie-parser')
const app = express();

app.use(cookieParser());

设置签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.use(cookieParser('impassword'));

res.cookie('a', '123', {
signed: true, // 设置签名
httpOnly:true, // 设置客户端不可修改
maxAge:1000*60, // 设置过期相对时间
path: '/'// 设置当前路径有效
expires: '', // cookie过期时间,类型Date
secure: true, // https下有效
domain: 'baidu.com' // 当前域名下有效
}))
res.signedCookies() // 查询签名
// { a: '123' }
document.cookie
// aa=s%3A123.a6pSztU5etJqsDWidxigdvu3hVtmtxhYhnJU3n7nN2Y

补充:利用cookie验证是否登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const express = require('express')
const app = express()
const router = express.Router()

function getList(author){
return {}
}

router.get('/list', (req, res, next) => {
const { author } = req.query
if (!req.cookies.username) {
return res.end('未登录')
} else {
res.json({
data: getArticle(author))
})
}
})
app.use(router)

express-session

express-session中间件将会话数据存储在服务器上 。它仅将会话标识存在cookie中(非会话数据)

API

1
2
3
4
5
6
const express = require('express')
const app = express()
const session = require('express-session')
const option = {}

app.use(session(option))

Opitons

  1. cookie

    • domain

      指定域名,没有指定默认当前域。

    • expires

      指定过期时间,没有指定即为会话cookie,退出即删除。maxAge权重高于expires。

    • httpOnly

      指定是否允许客户端在js中的document.cookie查看,没有指定即为true。

    • maxAge

      指定过期的相对时间,单位为毫秒。

    • path

      指定路径,不指定即为根路径。

    • sameSite

      指定是否严格执行相同的站点。值可为true、false、’lax’、’none’、’strict‘。

    • secure

      指定是否设置https选项。

      可根据NODE_ENV设置不同secure配置。

  2. genid

    生成新的会话ID,提供一个函数,该函数返回将用作会话ID的字符串,req可为第一个参数。

    1
    2
    3
    4
    5
    6
    app.use(session({
    genid: function(req) {
    return genuuid() // use UUIDs for session IDs
    },
    secret: 'keyboard cat'
    }))
  3. name

    指定要在响应中设置(并从请求中读取)的会话ID cookie的名称,默认为’connect.sid’

  4. proxy

    当设置了secure cookies(通过”x-forwarded-proto” header )时信任反向代理。当设定为true时,

    ”x-forwarded-proto” header 将被使用。当设定为false时,所有headers将被忽略。当该属性没有被设定时,将使用Express的trust proxy。

  5. resave

    强制保存session即使它并没有变化 (默认: true)

  6. rolling

    在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)

  7. saveUninitialized

    强制将未初始化的session存储。当新建了一个session且未设定属性或值时,它就处于未初始化状态。在设定一个cookie前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的。(默认:true)

  8. secret

    用它来对session cookie签名,防止篡改(必填项)

  9. secret

    session存储实例,可连接redis

  10. unset

    控制req.session是否取消(例如通过 delete,或者将它的值设置为null)。这可以使session保持存储,

    状态但忽略修改或删除的请求(默认:keep)

Req.session

要存储或访问会话数据,只需使用请求属性req。

  1. regenerate(cb)

    要重新生成session,只需调用该方法即可。 完成后,将在req.session初始化新的SID和Session实例,并将调用回调。

    1
    2
    3
    req.session.regenerate(function(err) {
    // will have a new session here
    })
  2. destory(cb)

    销毁session并取消设置req.session属性。 完成后,将调用回调。

    1
    2
    3
    req.session.destroy(function(err) {
    // cannot access session here
    })
  3. reload(cb)

    从存储重新加载session数据并重新填充req.session对象。 完成后,将调用回调

    1
    2
    3
    req.session.reload(function(err) {
    // session updated
    })
  4. save(cb)

    将会话保存回store,用内存中的内容替换store中的内容(尽管store可能会做其他事情 - 请参阅store的文档以了解确切的行为)。

    如果session数据已被更改,则在HTTP响应结束时会自动调用此方法(尽管可以使用中间件构造函数中的各种选项更改此行为)。 因此,通常不需要调用此方法。

    在某些情况下,调用此方法很有用,例如,重定向,长期请求或WebSockets。

    1
    2
    3
    req.session.save(function(err) {
    // session saved
    })
  5. touch(cb)

    更新.maxAge属性。 通常这不需要调用,因为session中间件为您执行此操作。

  6. id

    每个会话都有一个与之关联的唯一ID。 此属性是req.sessionID的别名,无法修改。

morgan

HTTP请求日志记录中间件

API

morgan(format, options)

format可以是:

  • 内置类型字符串:

    • combined

      1
      2
      3
      :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"

      ::1 - - [27/Apr/2021:15:26:53 +0000] "GET /api/blog/list?isadmin=1 HTTP/1.0" 304 - "http://localhost:8002/admin.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"
    • common

      1
      2
      3
      :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]

      ::ffff:127.0.0.1 - - [27/Apr/2021:15:27:34 +0000] "GET /api/blog/list?isadmin=1 HTTP/1.0" 304 -
    • dev

      1
      2
      3
      :method :url :status :response-time ms - :res[content-length]

      GET /api/blog/list?isadmin=1 304 10.546 ms - -
    • short

      1
      2
      3
      :remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms

      ::ffff:127.0.0.1 - GET /api/blog/list?isadmin=1 HTTP/1.0 304 - - 2.638 ms
    • tiny

      1
      2
      3
      :method :url :status :res[content-length] - :response-time ms

      GET /api/blog/list?isadmin=1 304 - - 11.431 ms
  • 格式字符串

  • 返回字符串的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
morgan('tiny')

morgan(':method :url :status :res[content-length] - :response-time ms')

morgan(function (tokens, req, res) {
return [
tokens.method(req, res),
tokens.url(req, res),
tokens.status(req, res),
tokens.res(req, res, 'content-length'), '-',
tokens['response-time'](req, res), 'ms'
].join(' ')
})

Options

  • immediate

    布尔值,默认是false。当为true时,一收到请求,就记录日志;如果为false,则在请求返回后,再记录日志。

  • skip

    是否跳过日志记录

    skip**:** function (req, res) { return res.statusCode < 400 }

  • sream

    用于写入日志行的输出流,默认为process.stdout

Token

自定义Token,即自定义日志字段

1
morgan.token('type', function (req, res) { return req.headers['content-type'] })
  1. :date[format]
    • web — Tue, 27 Apr 2021 15:51:24 GMT
    • clf — 27/Apr/2021:15:51:42 +0000
    • iso — 2021-04-27T15:51:55.046Z
  2. :http-version
  3. :method
  4. :referer
  5. :remote-addr
  6. :remote-user
  7. :req[header]
  8. :res[header]
  9. :response-time[digits]
  10. :status
  11. :total-time[digits]
  12. :url
  13. :user-agent

Compile

日志格式预编译

1
2
3
4
5
6
7
let format = logger.compile('[joke] :method') // 只能是字符串

logger.format('self', format)

app.use(logger('self', {
stream: accessLogStream
}));

日志拆分

日志拆分请看中间件file-stream-rotator。

file-stream-rotator

提供自动循环的Express / Connect日志或定期写入文件的其他任何功能(需要根据日期,大小限制或组合进行轮换),并根据计数或经过的天数删除旧的日志文件。

API

Options

  • filename

    文件名称,包含文件的完整路径

  • frequency

    拆分频率。选项为daily,custom,test。也可以是1h, 5m,

    如果以上都不是,则在文件名称的末尾添加一个’YYYYMMDD‘字符串

  • date_format

    可以通过它来设置频率。这个值会替换文件名中的%DATE%占位符

  • size

    日志分割后的文件最大值,单位为k,m,g

  • max_Logs

    最大保留日志数。如果不设置,则不删除。可以是文件数也可以是天数,天数加’d‘

  • audit_file

    日志审计文件存放位置。如果没有设置,它将存在根目录中

  • utc

    是否使用UTC时间为文件名,默认false

  • file_options

    传递给流的对象,可以用来设置flags,encode和mode

    默认为 { flags: ‘a’ }

  • extension

    拓展名,可以在文件名后面加扩展名

  • watch_log

    日志监听,观察正在写如的当前文件,并在意外删除时重新创建它,默认false

  • create_symlink

    创建可跟踪符号链接的日志文件,默认false

  • symlink_name

    创建符号链接的名称,默认current.log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const express = require('express')
const logger = require('morgan');
const fs = require('fs')
const app = express();
app.set('env', process.env.NODE_ENV)

const FileStreamRotator = require('file-stream-rotator')
const logDir = path.join(__dirname, 'logs')
fs.existsSync(logDir) || fs.mkdirSync(logDir)

function logStreamConf (mode) {
return {
date_format: 'YYYYMMDD HH-mm',
filename: path.join(logDirectory, `${mode}-%DATE%.log'`),
frequency: 'daily',
verbose: true,
}
}

var accessLogStream = FileStreamRotator.getStream(logStreamConf('access'))
var errLogStream = FileStreamRotator.getStream(logStreamConf('err'))

if (app.get('env') !== 'prod') {
app.use(logger('dev', {
stream: accessLogStream,
}))
app.use(logger('dev', {
stream: errLogStream,
skip: function (req, res) { return res.statusCode < 400 }
}))
} else {
// pm2处理
}

pm2

PM2是守护进程管理器,它将帮助您管理和保持应用程序在线,带有内置负载均衡。

特点:

​ 进程守护,系统崩溃自动重启。

​ 启动多进程,充分利用cpu和内存

​ 自带日志记录功能

背景: 操作系统限制一个进程的内存

​ 内存:无法充分利用机器的全部内存

​ cpu:无法充分利用多核CPU的优势

进程管理

  • 启动进程

    1
    pm2 start bin/www 或 pm2 start app.js
  • 查看进程

    1
    pm2 list
  • 停止进程

    1
    pm2 stop <app_name|namespace|id|'all'|json_conf>
  • 重启进程

    1
    pm2 restart <app_name|namespace|id|'all'|json_conf>
  • 删除进程

    1
    pm2 delete <app_name|namespace|id|'all'|json_conf>
  • 进程详情

    1
    pm2 describe <id|app_name>

    image-20210428153808670

  • 进程监控cpu

    1
    pm2 monit

    image-20210428155647031

日志管理

  • 查看日志

    1
    2
    3
    pm2 logs <app-name>
    pm2 logs --json
    pm2 log --format
  • 清空日志

    1
    pm2 flush
  • 重载日志

    1
    pm2 reloadLogs

配置项

属性说明备注
name应用进程名称
script启动脚本路径脚本所在路径
cwd应用启动的路径同process.cwd
args传递给脚本的参数
interpreter脚本解析
interpreter_args脚本解析参数
instances启动进程数仅在cluster模式有效,默认为fork
exec_mode应用启动模式fork或者cluster
watch监听重启
ignore_watch忽略监听项
max_memory_restart最大内存限制
env环境变量
log_date_format指定日志时间格式YYYY-MM-DD HH:mm:ss
error_file记录标准错误流
out_file记录标准输出流
min_uptime应用运行少于时间认为是异常启动
max_restarts最大异常重启次数
autorestart异常自动重启
cron_restartcrontab时间格式重启应用
force重复重启脚本false
restart_delay异常重启情况下,延时重启时间