• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chimurai / http-proxy-middleware / 18263701359

05 Oct 2025 08:07PM UTC coverage: 97.15%. Remained the same
18263701359

push

github

web-flow
ci(ci.yml): unpin node 24 (#1148)

* ci(ci.yml): unpin node 24

* chore(package): reinstall mockttp deps

* build(mockttp): use 2048 bits in generateCACertificate

170 of 183 branches covered (92.9%)

409 of 421 relevant lines covered (97.15%)

24.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.96
/src/handlers/response-interceptor.ts
1
import type * as http from 'node:http';
2
import * as zlib from 'node:zlib';
13✔
3

4
import { Debug } from '../debug';
13✔
5
import { getFunctionName } from '../utils/function';
13✔
6

7
const debug = Debug.extend('response-interceptor');
13✔
8

9
type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (
10
  buffer: Buffer,
11
  proxyRes: TReq,
12
  req: TReq,
13
  res: TRes,
14
) => Promise<Buffer | string>;
15

16
/**
17
 * Intercept responses from upstream.
18
 * Automatically decompress (deflate, gzip, brotli).
19
 * Give developer the opportunity to modify intercepted Buffer and http.ServerResponse
20
 *
21
 * NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
22
 */
23
export function responseInterceptor<
13✔
24
  TReq extends http.IncomingMessage = http.IncomingMessage,
25
  TRes extends http.ServerResponse = http.ServerResponse,
26
>(interceptor: Interceptor<TReq, TRes>) {
27
  return async function proxyResResponseInterceptor(
11✔
28
    proxyRes: TReq,
29
    req: TReq,
30
    res: TRes,
31
  ): Promise<void> {
32
    debug('intercept proxy response');
9✔
33
    const originalProxyRes = proxyRes;
9✔
34
    let buffer = Buffer.from('', 'utf8');
9✔
35

36
    // decompress proxy response
37
    const _proxyRes = decompress<TReq>(proxyRes, proxyRes.headers['content-encoding']);
9✔
38

39
    // concat data stream
40
    _proxyRes.on('data', (chunk) => (buffer = Buffer.concat([buffer, chunk])));
9✔
41

42
    _proxyRes.on('end', async () => {
9✔
43
      // copy original headers
44
      copyHeaders(proxyRes, res);
8✔
45

46
      // call interceptor with intercepted response (buffer)
47
      debug('call interceptor function: %s', getFunctionName(interceptor));
8✔
48
      const interceptedBuffer = Buffer.from(await interceptor(buffer, originalProxyRes, req, res));
8✔
49

50
      // set correct content-length (with double byte character support)
51
      debug('set content-length: %s', Buffer.byteLength(interceptedBuffer, 'utf8'));
8✔
52
      res.setHeader('content-length', Buffer.byteLength(interceptedBuffer, 'utf8'));
8✔
53

54
      debug('write intercepted response');
8✔
55
      res.write(interceptedBuffer);
8✔
56
      res.end();
8✔
57
    });
58

59
    _proxyRes.on('error', (error) => {
9✔
60
      res.end(`Error fetching proxied request: ${error.message}`);
1✔
61
    });
62
  };
63
}
64

65
/**
66
 * Streaming decompression of proxy response
67
 * source: https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L116
68
 */
69
function decompress<TReq extends http.IncomingMessage = http.IncomingMessage>(
70
  proxyRes: TReq,
71
  contentEncoding?: string,
72
): TReq | zlib.Gunzip | zlib.Inflate | zlib.BrotliDecompress {
73
  let _proxyRes: TReq | zlib.Gunzip | zlib.Inflate | zlib.BrotliDecompress = proxyRes;
9✔
74
  let decompress;
75

76
  switch (contentEncoding) {
9✔
77
    case 'gzip':
78
      decompress = zlib.createGunzip();
1✔
79
      break;
1✔
80
    case 'br':
81
      decompress = zlib.createBrotliDecompress();
1✔
82
      break;
1✔
83
    case 'deflate':
84
      decompress = zlib.createInflate();
1✔
85
      break;
1✔
86
    default:
87
      break;
6✔
88
  }
89

90
  if (decompress) {
9✔
91
    debug(`decompress proxy response with 'content-encoding': %s`, contentEncoding);
3✔
92
    _proxyRes.pipe(decompress);
3✔
93
    _proxyRes = decompress;
3✔
94
  }
95

96
  return _proxyRes;
9✔
97
}
98

99
/**
100
 * Copy original headers
101
 * https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
102
 */
103
function copyHeaders(originalResponse, response): void {
104
  debug('copy original response headers');
8✔
105

106
  response.statusCode = originalResponse.statusCode;
8✔
107
  response.statusMessage = originalResponse.statusMessage;
8✔
108

109
  if (response.setHeader) {
8!
110
    let keys = Object.keys(originalResponse.headers);
8✔
111

112
    // ignore chunked, brotli, gzip, deflate headers
113
    keys = keys.filter((key) => !['content-encoding', 'transfer-encoding'].includes(key));
54✔
114

115
    keys.forEach((key) => {
8✔
116
      let value = originalResponse.headers[key];
51✔
117

118
      if (key === 'set-cookie') {
51✔
119
        // remove cookie domain
120
        value = Array.isArray(value) ? value : [value];
1!
121
        value = value.map((x) => x.replace(/Domain=[^;]+?/i, ''));
1✔
122
      }
123

124
      response.setHeader(key, value);
51✔
125
    });
126
  } else {
127
    response.headers = originalResponse.headers;
×
128
  }
129
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc