feature: InsertScriptMiddleware.js 支持编码方式 `deflate` 和 `br`,不再是只有 `gzip` 了 (#299)
parent
3968272843
commit
c7cdb14daa
|
@ -1,23 +1,47 @@
|
||||||
|
const log = require('../../../utils/util.log')
|
||||||
const through = require('through2')
|
const through = require('through2')
|
||||||
const zlib = require('zlib')
|
const zlib = require('zlib')
|
||||||
|
|
||||||
const httpUtil = {}
|
// 编解码器
|
||||||
httpUtil.getCharset = function (res) {
|
const codecMap = {
|
||||||
const contentType = res.getHeader('content-type')
|
gzip: {
|
||||||
const reg = /charset=(.*)/
|
createCompressor: () => zlib.createGzip(),
|
||||||
const matched = contentType.match(reg)
|
createDecompressor: () => zlib.createGunzip()
|
||||||
if (matched) {
|
},
|
||||||
return matched[1]
|
deflate: {
|
||||||
|
createCompressor: () => zlib.createDeflate(),
|
||||||
|
createDecompressor: () => zlib.createInflate()
|
||||||
|
},
|
||||||
|
br: {
|
||||||
|
createCompressor: () => zlib.createBrotliCompress(),
|
||||||
|
createDecompressor: () => zlib.createBrotliDecompress()
|
||||||
}
|
}
|
||||||
return 'utf-8'
|
|
||||||
}
|
}
|
||||||
httpUtil.isGzip = function (res) {
|
const supportedEncodings = Object.keys(codecMap)
|
||||||
const contentEncoding = res.headers['content-encoding']
|
const supportedEncodingsStr = supportedEncodings.join(', ')
|
||||||
return !!(contentEncoding && contentEncoding.toLowerCase() === 'gzip')
|
|
||||||
}
|
const httpUtil = {
|
||||||
httpUtil.isHtml = function (res) {
|
// 获取响应内容编码
|
||||||
const contentType = res.headers['content-type']
|
getContentEncoding (res) {
|
||||||
return (typeof contentType !== 'undefined') && /text\/html|application\/xhtml\+xml/.test(contentType)
|
const encoding = res.headers['content-encoding']
|
||||||
|
if (encoding) {
|
||||||
|
return encoding.toLowerCase()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
// 获取编解码器
|
||||||
|
getCodec (encoding) {
|
||||||
|
return codecMap[encoding]
|
||||||
|
},
|
||||||
|
// 获取支持的编解码器名称字符串
|
||||||
|
supportedEncodingsStr () {
|
||||||
|
return supportedEncodingsStr
|
||||||
|
},
|
||||||
|
// 是否HTML代码
|
||||||
|
isHtml (res) {
|
||||||
|
const contentType = res.headers['content-type']
|
||||||
|
return (typeof contentType !== 'undefined') && /text\/html|application\/xhtml\+xml/.test(contentType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const HEAD = Buffer.from('</head>')
|
const HEAD = Buffer.from('</head>')
|
||||||
const HEAD_UP = Buffer.from('</HEAD>')
|
const HEAD_UP = Buffer.from('</HEAD>')
|
||||||
|
@ -25,16 +49,18 @@ const BODY = Buffer.from('</body>')
|
||||||
const BODY_UP = Buffer.from('</BODY>')
|
const BODY_UP = Buffer.from('</BODY>')
|
||||||
|
|
||||||
function chunkByteReplace (_this, chunk, enc, callback, append) {
|
function chunkByteReplace (_this, chunk, enc, callback, append) {
|
||||||
if (append && append.head) {
|
if (append) {
|
||||||
const ret = injectScriptIntoHtml([HEAD, HEAD_UP], chunk, append.head)
|
if (append.head) {
|
||||||
if (ret != null) {
|
const ret = injectScriptIntoHtml([HEAD, HEAD_UP], chunk, append.head)
|
||||||
chunk = ret
|
if (ret != null) {
|
||||||
|
chunk = ret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (append.body) {
|
||||||
if (append && append.body) {
|
const ret = injectScriptIntoHtml([BODY, BODY_UP], chunk, append.body)
|
||||||
const ret = injectScriptIntoHtml([BODY, BODY_UP], chunk, append.body)
|
if (ret != null) {
|
||||||
if (ret != null) {
|
chunk = ret
|
||||||
chunk = ret
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_this.push(chunk)
|
_this.push(chunk)
|
||||||
|
@ -57,10 +83,41 @@ function injectScriptIntoHtml (tags, chunk, script) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleResponseHeaders (res, proxyRes) {
|
||||||
|
Object.keys(proxyRes.headers).forEach(function (key) {
|
||||||
|
if (proxyRes.headers[key] !== undefined) {
|
||||||
|
// let newkey = key.replace(/^[a-z]|-[a-z]/g, (match) => {
|
||||||
|
// return match.toUpperCase()
|
||||||
|
// })
|
||||||
|
const newkey = key
|
||||||
|
if (key === 'content-length') {
|
||||||
|
// do nothing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (key === 'content-security-policy') {
|
||||||
|
// content-security-policy
|
||||||
|
let policy = proxyRes.headers[key]
|
||||||
|
const reg = /script-src ([^:]*);/i
|
||||||
|
const matched = policy.match(reg)
|
||||||
|
if (matched) {
|
||||||
|
if (matched[1].indexOf('self') < 0) {
|
||||||
|
policy = policy.replace('script-src', 'script-src \'self\' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.setHeader(newkey, policy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader(newkey, proxyRes.headers[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
res.writeHead(proxyRes.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
const contextPath = '/____ds_script____/'
|
const contextPath = '/____ds_script____/'
|
||||||
const monkey = require('../../monkey')
|
const monkey = require('../../monkey')
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
requestIntercept (context, req, res, ssl, next) {
|
requestIntercept (context, req, res, ssl, next) {
|
||||||
const { rOptions, log, setting } = context
|
const { rOptions, log, setting } = context
|
||||||
if (rOptions.path.indexOf(contextPath) !== 0) {
|
if (rOptions.path.indexOf(contextPath) !== 0) {
|
||||||
|
@ -87,7 +144,7 @@ module.exports = {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
responseInterceptor (req, res, proxyReq, proxyRes, ssl, next, append) {
|
responseInterceptor (req, res, proxyReq, proxyRes, ssl, next, append) {
|
||||||
if (!append.head && !append.body) {
|
if (append == null || (!append.head && !append.body)) {
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -99,50 +156,38 @@ module.exports = {
|
||||||
if (!isHtml || contentLengthIsZero) {
|
if (!isHtml || contentLengthIsZero) {
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
Object.keys(proxyRes.headers).forEach(function (key) {
|
|
||||||
if (proxyRes.headers[key] !== undefined) {
|
|
||||||
// let newkey = key.replace(/^[a-z]|-[a-z]/g, (match) => {
|
|
||||||
// return match.toUpperCase()
|
|
||||||
// })
|
|
||||||
const newkey = key
|
|
||||||
if (isHtml && key === 'content-length') {
|
|
||||||
// do nothing
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (isHtml && key === 'content-security-policy') {
|
|
||||||
// content-security-policy
|
|
||||||
let policy = proxyRes.headers[key]
|
|
||||||
const reg = /script-src ([^:]*);/i
|
|
||||||
const matched = policy.match(reg)
|
|
||||||
if (matched) {
|
|
||||||
if (matched[1].indexOf('self') < 0) {
|
|
||||||
policy = policy.replace('script-src', 'script-src \'self\' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.setHeader(newkey, policy)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader(newkey, proxyRes.headers[key])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
res.writeHead(proxyRes.statusCode)
|
|
||||||
|
|
||||||
const isGzip = httpUtil.isGzip(proxyRes)
|
|
||||||
|
|
||||||
if (isGzip) {
|
|
||||||
proxyRes.pipe(new zlib.Gunzip())
|
|
||||||
.pipe(through(function (chunk, enc, callback) {
|
|
||||||
chunkByteReplace(this, chunk, enc, callback, append)
|
|
||||||
})).pipe(new zlib.Gzip()).pipe(res)
|
|
||||||
} else {
|
|
||||||
proxyRes.pipe(through(function (chunk, enc, callback) {
|
|
||||||
chunkByteReplace(this, chunk, enc, callback, append)
|
|
||||||
})).pipe(res)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 先处理头信息
|
||||||
|
handleResponseHeaders(res, proxyRes)
|
||||||
|
|
||||||
|
// 获取响应内容编码
|
||||||
|
const encoding = httpUtil.getContentEncoding(proxyRes)
|
||||||
|
if (encoding) {
|
||||||
|
// 获取编解码器
|
||||||
|
const codec = httpUtil.getCodec(encoding)
|
||||||
|
if (codec) {
|
||||||
|
proxyRes
|
||||||
|
.pipe(codec.createDecompressor()) // 解码
|
||||||
|
.pipe(through(function (chunk, enc, callback) {
|
||||||
|
// 插入head和body
|
||||||
|
chunkByteReplace(this, chunk, enc, callback, append)
|
||||||
|
}))
|
||||||
|
.pipe(codec.createCompressor()) // 编码
|
||||||
|
.pipe(res)
|
||||||
|
} else {
|
||||||
|
log.error(`InsertScriptMiddleware.responseInterceptor(): 暂不支持编码方式 ${encoding}, 目前支持:`, httpUtil.supportedEncodingsStr())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proxyRes
|
||||||
|
.pipe(through(function (chunk, enc, callback) {
|
||||||
|
chunkByteReplace(this, chunk, enc, callback, append)
|
||||||
|
}))
|
||||||
|
.pipe(res)
|
||||||
|
}
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
},
|
||||||
|
httpUtil,
|
||||||
|
handleResponseHeaders
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue