Created
November 25, 2019 10:13
-
-
Save vbfox/9250ec99e2ca4f562e197d3c13720300 to your computer and use it in GitHub Desktop.
Small function for express.js http-proxy-middleware to change response text
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable @typescript-eslint/unbound-method */ | |
/* eslint-disable no-param-reassign */ | |
import zlib from 'zlib'; | |
import concatStream from 'concat-stream'; | |
import BufferHelper from 'bufferhelper'; | |
import stream from 'stream'; | |
import { IncomingMessage, ServerResponse } from 'http'; | |
export type Modification = (original: string, proxyRes: IncomingMessage) => string | undefined; | |
/** | |
* handle compressed | |
*/ | |
function handleCompressed(proxyRes: IncomingMessage, res: ServerResponse, unzip: stream.Transform, zip: stream.Transform, callback: Modification | undefined) { | |
proxyRes.on('data', (chunk) => { | |
unzip.write(chunk); | |
}); | |
proxyRes.on('end', () => unzip.end()); | |
// Concat the unzip stream. | |
const concatWrite = concatStream(data => { | |
const responseString = data.toString(); | |
const modifiedResponseString = callback && callback(responseString, proxyRes); | |
const body = modifiedResponseString === undefined ? data : Buffer.from(modifiedResponseString); | |
zip.on('data', chunk => res.write(chunk)); | |
zip.on('end', () => res.end()); | |
zip.write(body); | |
zip.end(); | |
}); | |
unzip.pipe(concatWrite); | |
} | |
/** | |
* handle Uncompressed | |
*/ | |
function handleUncompressed(proxyRes: IncomingMessage, res: ServerResponse, callback: Modification | undefined) { | |
const buffer = new BufferHelper(); | |
proxyRes.on('data', (data: any) => { | |
buffer.concat(data); | |
}); | |
proxyRes.on('end', () => { | |
const data = buffer.toBuffer(); | |
const responseString = data.toString(); | |
const modifiedResponseString = callback && callback(responseString, proxyRes); | |
const body = modifiedResponseString === undefined ? data : Buffer.from(modifiedResponseString); | |
// Call the response method | |
res.write(body); | |
res.end(); | |
}); | |
} | |
export default function modifyProxiedResponse(proxyRes: IncomingMessage, res: ServerResponse, modification: Modification) { | |
let contentEncoding: string | undefined; | |
if (proxyRes && proxyRes.headers) { | |
contentEncoding = proxyRes.headers['content-encoding']; | |
// Delete the content-length if it exists. Otherwise, an exception will occur | |
if ('content-length' in proxyRes.headers) { | |
delete proxyRes.headers['content-length']; | |
} | |
} | |
for (const headerKey in proxyRes.headers) { | |
if (!Object.prototype.hasOwnProperty.call(proxyRes.headers, headerKey)) { | |
// eslint-disable-next-line no-continue | |
continue; | |
} | |
if (headerKey !== 'content-length') { | |
const headerValue = proxyRes.headers[headerKey]; | |
res.setHeader(headerKey, headerValue!); | |
} | |
} | |
let internalModification: Modification | undefined = modification; | |
if (proxyRes.statusCode !== 200) { | |
internalModification = undefined; | |
res.statusCode = proxyRes.statusCode || 200; | |
} | |
let unzip: stream.Transform | undefined; | |
let zip: stream.Transform | undefined; | |
// Now only deal with the gzip/deflate/undefined content-encoding. | |
switch (contentEncoding) { | |
case 'gzip': | |
unzip = zlib.createGunzip(); | |
zip = zlib.createGzip(); | |
break; | |
case 'deflate': | |
unzip = zlib.createInflate(); | |
zip = zlib.createDeflate(); | |
break; | |
case 'br': | |
unzip = zlib.createBrotliDecompress && zlib.createBrotliDecompress(); | |
zip = zlib.createBrotliCompress && zlib.createBrotliCompress(); | |
break; | |
} | |
if (zip && unzip) { | |
const realEnd = res.end.bind(res); | |
unzip.on('error', function onUnCompressError(e) { | |
console.log('Unzip error: ', e); | |
realEnd(); | |
}); | |
handleCompressed(proxyRes, res, unzip, zip, internalModification); | |
} else if (!contentEncoding) { | |
handleUncompressed(proxyRes, res, internalModification); | |
} else { | |
console.log(`Not supported content-encoding: ${contentEncoding}`); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import express from 'express'; | |
import proxy from 'http-proxy-middleware'; | |
import path from 'path'; | |
import { IncomingMessage, ServerResponse } from 'http'; | |
import modifyProxiedResponse from './modifyProxiedResponse'; | |
const homeDir = path.join(__dirname, 'dist'); | |
function onProxyRes(proxyRes: IncomingMessage, req: IncomingMessage, res: ServerResponse) { | |
modifyProxiedResponse(proxyRes, res, original => { | |
if (proxyRes.statusCode === 200) { | |
return original.replace('foo', 'bar'); | |
} | |
return undefined; | |
}); | |
} | |
// App | |
const app = express() | |
.use(express.static(homeDir)) | |
.use( | |
'/proxy/amplitude/cdn', | |
proxy('/proxy/amplitude/cdn', { | |
target: 'https://cdn.amplitude.com/', | |
changeOrigin: true, | |
pathRewrite: { '^/proxy/amplitude/cdn': '' }, | |
selfHandleResponse: true, | |
onProxyRes, | |
}), | |
); | |
app.listen(8084, '127.0.0.1'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
{ | |
"compilerOptions": { | |
"target": "es2018", | |
"module": "commonjs", | |
"moduleResolution": "node", | |
"noImplicitAny": true, | |
"strict": true, | |
"removeComments": false, | |
"allowSyntheticDefaultImports": true, | |
"outDir": "dist", | |
"esModuleInterop": true | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment