Skip to content

Instantly share code, notes, and snippets.

@vbfox
Created November 25, 2019 10:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vbfox/9250ec99e2ca4f562e197d3c13720300 to your computer and use it in GitHub Desktop.
Save vbfox/9250ec99e2ca4f562e197d3c13720300 to your computer and use it in GitHub Desktop.
Small function for express.js http-proxy-middleware to change response text
/* 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}`);
}
}
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');
{
"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