response-interceptor.js 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.responseInterceptor = void 0;
  4. const zlib = require("zlib");
  5. /**
  6. * Intercept responses from upstream.
  7. * Automatically decompress (deflate, gzip, brotli).
  8. * Give developer the opportunity to modify intercepted Buffer and http.ServerResponse
  9. *
  10. * NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
  11. */
  12. function responseInterceptor(interceptor) {
  13. return async function proxyRes(proxyRes, req, res) {
  14. const originalProxyRes = proxyRes;
  15. let buffer = Buffer.from('', 'utf8');
  16. // decompress proxy response
  17. const _proxyRes = decompress(proxyRes, proxyRes.headers['content-encoding']);
  18. // concat data stream
  19. _proxyRes.on('data', (chunk) => (buffer = Buffer.concat([buffer, chunk])));
  20. _proxyRes.on('end', async () => {
  21. // copy original headers
  22. copyHeaders(proxyRes, res);
  23. // call interceptor with intercepted response (buffer)
  24. const interceptedBuffer = Buffer.from(await interceptor(buffer, originalProxyRes, req, res));
  25. // set correct content-length (with double byte character support)
  26. res.setHeader('content-length', Buffer.byteLength(interceptedBuffer, 'utf8'));
  27. res.write(interceptedBuffer);
  28. res.end();
  29. });
  30. _proxyRes.on('error', (error) => {
  31. res.end(`Error fetching proxied request: ${error.message}`);
  32. });
  33. };
  34. }
  35. exports.responseInterceptor = responseInterceptor;
  36. /**
  37. * Streaming decompression of proxy response
  38. * source: https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L116
  39. */
  40. function decompress(proxyRes, contentEncoding) {
  41. let _proxyRes = proxyRes;
  42. let decompress;
  43. switch (contentEncoding) {
  44. case 'gzip':
  45. decompress = zlib.createGunzip();
  46. break;
  47. case 'br':
  48. decompress = zlib.createBrotliDecompress();
  49. break;
  50. case 'deflate':
  51. decompress = zlib.createInflate();
  52. break;
  53. default:
  54. break;
  55. }
  56. if (decompress) {
  57. _proxyRes.pipe(decompress);
  58. _proxyRes = decompress;
  59. }
  60. return _proxyRes;
  61. }
  62. /**
  63. * Copy original headers
  64. * https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
  65. */
  66. function copyHeaders(originalResponse, response) {
  67. response.statusCode = originalResponse.statusCode;
  68. response.statusMessage = originalResponse.statusMessage;
  69. if (response.setHeader) {
  70. let keys = Object.keys(originalResponse.headers);
  71. // ignore chunked, brotli, gzip, deflate headers
  72. keys = keys.filter((key) => !['content-encoding', 'transfer-encoding'].includes(key));
  73. keys.forEach((key) => {
  74. let value = originalResponse.headers[key];
  75. if (key === 'set-cookie') {
  76. // remove cookie domain
  77. value = Array.isArray(value) ? value : [value];
  78. value = value.map((x) => x.replace(/Domain=[^;]+?/i, ''));
  79. }
  80. response.setHeader(key, value);
  81. });
  82. }
  83. else {
  84. response.headers = originalResponse.headers;
  85. }
  86. }