Nestjs Fastify 响应压缩详解

2022-11-26 15:16:57 浏览数 (2)

NestJs中使用Fastify时,需要使用 @fastify/compress 库进行压缩,官方实例代码

代码语言:javascript复制
import compression from '@fastify/compress';

await app.register(compression);

@fastify/compress 具体如何使用,可以从其Github首页学习。

这个插件可以自动给响应内容压缩,是否可以进行压缩,是根据请求响应的content-type来确定。

要注意,如果同时使用了 @fastify/static 插件,@fastify/compress 又是global模式,则需要保证 @fastify/compress 在 @fastify/static 之前注册

再看下@fastify/compress的中参数

代码语言:javascript复制
type EncodingToken = 'br' | 'deflate' | 'gzip' | 'identity';

export interface FastifyCompressOptions {
  brotliOptions?: BrotliOptions;
  customTypes?: RegExp;
  encodings?: EncodingToken[];
  forceRequestEncoding?: EncodingToken;
  global?: boolean;
  inflateIfDeflated?: boolean;
  onInvalidRequestPayload?: (encoding: string, request: FastifyRequest, error: Error) => Error | undefined | null;
  onUnsupportedEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => string | Buffer | Stream;
  onUnsupportedRequestEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => Error | undefined | null;
  removeContentLengthHeader?: boolean;
  requestEncodings?: EncodingToken[];
  threshold?: number;
  zlib?: unknown;
  zlibOptions?: ZlibOptions;
}

其中的重要参数

threshold

响应体的大小最小是多少时会进行压缩,默认1024bytes

customTypes

使用 mime-db 库来确定响应类型是否需要压缩,可以用这个参数设置正则表达式压缩来其他类型的响应体,默认值 /^text/(?!event-stream)| json$| text$| xml$|octet-stream$/

global

是否全局开启压缩,默认为true,会对所有响应进行处理,如果要关闭设置为false

encodings

采用的压缩算法的数组,可选值为br、deflate、gzip、identity(不压缩),数组前边的值比后边的值优先级高,Brotli压缩率更高,但是压缩更耗时,选用哪种压缩方式可以根据自己的业务情况做取舍

forceRequestEncoding

忽略content-encoding,强制使用设置的算法压缩

removeContentLengthHeader

是否删除响应头中的Content-Length,默认为true,会删除

onUnsupportedEncoding 当不支持编码时,可以通过设置 onUnsupportedEncoding(encoding, request, reply) 来修改响应并返回自定义返回,可以返回 string Buffer Stream Error

具体看下@fastify/compress插件如何判断是否需要进行压缩?

可以参看源码

代码语言:javascript复制
const noCompress =
      // don't compress on x-no-compression header
      (req.headers['x-no-compression'] !== undefined) ||
      // don't compress if not one of the indicated compressible types
      (shouldCompress(reply.getHeader('Content-Type') || 'application/json', params.compressibleTypes) === false) ||
      // don't compress on missing or identity `accept-encoding` header
      ((encoding = getEncodingHeader(params.encodings, req)) == null || encoding === 'identity')

首先是请求头明确说明不能压缩

看下shouldCompress方法

代码语言:javascript复制
function shouldCompress (type, compressibleTypes) {
  if (compressibleTypes.test(type)) return true
  const data = mimedb[type.split(';', 1)[0].trim().toLowerCase()]
  if (data === undefined) return false
  return data.compressible === true
}

customTypes配置的正则匹配通过允许压缩,正则默认值是/^text/(?!event-stream)| json$| text$| xml$|octet-stream$/,可以自行定义以实现自己的需求

大部分情况是根据响应头中的Content-Type匹配 mime-db 库是否可以进行压缩

再看下getEncodingHeader方法

代码语言:javascript复制
function getEncodingHeader (encodings, request) {
  let header = request.headers['accept-encoding']
  if (header != null) {
    header = header.toLowerCase()
      // consider the no-preference token as gzip for downstream compat
      // and x-gzip as an alias of gzip
      // ref.: [HTTP/1.1 RFC 7230 section 4.2.3](https://datatracker.ietf.org/doc/html/rfc7230#section-4.2.3)
      .replace(/*|x-gzip/g, 'gzip')
    return encodingNegotiator.negotiate(header, encodings)
  } else {
    return undefined
  }
}

请求头中的accept-encoding的值未设置或者设置为identity时不压缩

最后就是

代码语言:javascript复制
if (typeof payload.pipe !== 'function') {
      if (Buffer.byteLength(payload) < params.threshold) {
        return next()
      }
      payload = intoStream(payload)
}

响应体的大小要大于等于threshold设置的值,默认1024bytes,就会进行压缩

在大流量的生产环境,官方建议不要使用插件进行压缩,使用反向代理比如Nginx进行压缩

0 人点赞