response-interceptor.js 3.8 KB

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