Skip to content

Commit 31aa5de

Browse files
authored
Merge branch 'master' into docs-response-interceptor-utf8
2 parents 69ca15e + 2831df9 commit 31aa5de

14 files changed

+161
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [v1.2.1](https://github.com/chimurai/http-proxy-middleware/releases/tag/v1.2.1)
4+
5+
- fix(response interceptor): proxy original response headers ([#563](https://github.com/chimurai/http-proxy-middleware/pull/563))
6+
37
## [v1.2.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v1.2.0)
48

59
- feat(handler): response interceptor ([#520](https://github.com/chimurai/http-proxy-middleware/pull/520))

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#option
6969
- [app.use\(path, proxy\)](#appusepath-proxy)
7070
- [WebSocket](#websocket)
7171
- [External WebSocket upgrade](#external-websocket-upgrade)
72+
- [Intercept and manipulate requests](#intercept-and-manipulate-requests)
7273
- [Intercept and manipulate responses](#intercept-and-manipulate-responses)
7374
- [Working examples](#working-examples)
7475
- [Recipes](#recipes)
@@ -482,6 +483,25 @@ const server = app.listen(3000);
482483
server.on('upgrade', wsProxy.upgrade); // <-- subscribe to http 'upgrade'
483484
```
484485

486+
## Intercept and manipulate requests
487+
488+
Intercept requests from downstream by defining `onProxyReq` in `createProxyMiddleware`.
489+
490+
Currently the only pre-provided request interceptor is `fixRequestBody`, which is used to fix proxied POST requests when `bodyParser` is applied before this middleware.
491+
492+
Example:
493+
494+
```javascript
495+
const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware');
496+
497+
const proxy = createProxyMiddleware({
498+
/**
499+
* Fix bodyParser
500+
**/
501+
onProxyReq: fixRequestBody,
502+
});
503+
```
504+
485505
## Intercept and manipulate responses
486506

487507
Intercept responses from upstream with `responseInterceptor`. (Make sure to set `selfHandleResponse: true`)

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "http-proxy-middleware",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"description": "The one-liner node.js proxy middleware for connect, express and browser-sync",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
@@ -60,6 +60,7 @@
6060
"@types/ws": "^7.4.0",
6161
"@typescript-eslint/eslint-plugin": "^4.19.0",
6262
"@typescript-eslint/parser": "^4.19.0",
63+
"body-parser": "^1.19.0",
6364
"browser-sync": "^2.26.14",
6465
"connect": "^3.7.0",
6566
"eslint": "^7.23.0",

src/handlers/fix-request-body.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ClientRequest } from 'http';
2+
import type { Request } from '../types';
3+
import * as querystring from 'querystring';
4+
5+
/**
6+
* Fix proxied body if bodyParser is involved.
7+
*/
8+
export function fixRequestBody(proxyReq: ClientRequest, req: Request): void {
9+
if (!req.body || !Object.keys(req.body).length) {
10+
return;
11+
}
12+
13+
const contentType = proxyReq.getHeader('Content-Type') as string;
14+
const writeBody = (bodyData: string) => {
15+
// deepcode ignore ContentLengthInCode: bodyParser fix
16+
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
17+
proxyReq.write(bodyData);
18+
};
19+
20+
if (contentType.includes('application/json')) {
21+
writeBody(JSON.stringify(req.body));
22+
}
23+
24+
if (contentType === 'application/x-www-form-urlencoded') {
25+
writeBody(querystring.stringify(req.body));
26+
}
27+
}

src/handlers/public.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { responseInterceptor } from './response-interceptor';
2+
export { fixRequestBody } from './fix-request-body';

src/handlers/response-interceptor.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export function responseInterceptor(interceptor: Interceptor) {
3131
_proxyRes.on('data', (chunk) => (buffer = Buffer.concat([buffer, chunk])));
3232

3333
_proxyRes.on('end', async () => {
34-
// set original content type from upstream
35-
res.setHeader('content-type', originalProxyRes.headers['content-type'] || '');
34+
// copy original headers
35+
copyHeaders(proxyRes, res);
3636

3737
// call interceptor with intercepted response (buffer)
3838
const interceptedBuffer = Buffer.from(await interceptor(buffer, originalProxyRes, req, res));
@@ -79,3 +79,33 @@ function decompress(proxyRes: http.IncomingMessage, contentEncoding: string) {
7979

8080
return _proxyRes;
8181
}
82+
83+
/**
84+
* Copy original headers
85+
* https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
86+
*/
87+
function copyHeaders(originalResponse, response) {
88+
response.statusCode = originalResponse.statusCode;
89+
response.statusMessage = originalResponse.statusMessage;
90+
91+
if (response.setHeader) {
92+
let keys = Object.keys(originalResponse.headers);
93+
94+
// ignore chunked, brotli, gzip, deflate headers
95+
keys = keys.filter((key) => !['content-encoding', 'transfer-encoding'].includes(key));
96+
97+
keys.forEach((key) => {
98+
let value = originalResponse.headers[key];
99+
100+
if (key === 'set-cookie') {
101+
// remove cookie domain
102+
value = Array.isArray(value) ? value : [value];
103+
value = value.map((x) => x.replace(/Domain=[^;]+?/i, ''));
104+
}
105+
106+
response.setHeader(key, value);
107+
});
108+
} else {
109+
response.headers = originalResponse.headers;
110+
}
111+
}

test/e2e/express-router.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as express from 'express';
22
import * as http from 'http';
3-
import { createProxyMiddleware } from './_utils';
3+
import { createProxyMiddleware } from './test-kit';
44
import { Options } from '../../src/index';
55

66
describe('Usage in Express', () => {

test/e2e/http-proxy-middleware.spec.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { createProxyMiddleware, createApp, createAppWithPath } from './_utils';
1+
import { createProxyMiddleware, createApp, createAppWithPath, fixRequestBody } from './test-kit';
22
import * as request from 'supertest';
33
import { Mockttp, getLocal, CompletedRequest } from 'mockttp';
44
import { Request, Response } from '../../src/types';
55
import { NextFunction } from 'express';
6+
import * as bodyParser from 'body-parser';
67

78
describe('E2E http-proxy-middleware', () => {
89
describe('http-proxy-middleware creation', () => {
@@ -78,6 +79,44 @@ describe('E2E http-proxy-middleware', () => {
7879
});
7980
});
8081

82+
describe('basic setup with configured body-parser', () => {
83+
it('should proxy request body from form', async () => {
84+
agent = request(
85+
createApp(
86+
bodyParser.urlencoded({ extended: false }),
87+
createProxyMiddleware('/api', {
88+
target: `http://localhost:${mockTargetServer.port}`,
89+
onProxyReq: fixRequestBody,
90+
})
91+
)
92+
);
93+
94+
await mockTargetServer.post('/api').thenCallback((req) => {
95+
expect(req.body.text).toBe('foo=bar&bar=baz');
96+
return { status: 200 };
97+
});
98+
await agent.post('/api').send('foo=bar').send('bar=baz').expect(200);
99+
});
100+
101+
it('should proxy request body from json', async () => {
102+
agent = request(
103+
createApp(
104+
bodyParser.json(),
105+
createProxyMiddleware('/api', {
106+
target: `http://localhost:${mockTargetServer.port}`,
107+
onProxyReq: fixRequestBody,
108+
})
109+
)
110+
);
111+
112+
await mockTargetServer.post('/api').thenCallback((req) => {
113+
expect(req.body.json).toEqual({ foo: 'bar', bar: 'baz', doubleByte: '文' });
114+
return { status: 200 };
115+
});
116+
await agent.post('/api').send({ foo: 'bar', bar: 'baz', doubleByte: '文' }).expect(200);
117+
});
118+
});
119+
81120
describe('custom context matcher/filter', () => {
82121
it('should have response body: "HELLO WEB"', async () => {
83122
const filter = (path, req) => {

test/e2e/path-rewriter.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createProxyMiddleware, createApp } from './_utils';
1+
import { createProxyMiddleware, createApp } from './test-kit';
22
import * as request from 'supertest';
33
import { getLocal, Mockttp } from 'mockttp';
44

test/e2e/response-interceptor.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createProxyMiddleware, responseInterceptor } from '../../src';
2-
import { createApp } from './_utils';
2+
import { createApp } from './test-kit';
33
import * as request from 'supertest';
44

55
describe('responseInterceptor()', () => {
@@ -41,6 +41,32 @@ describe('responseInterceptor()', () => {
4141
});
4242
});
4343

44+
describe('intercept responses with original headers', () => {
45+
beforeEach(() => {
46+
agent = request(
47+
createApp(
48+
createProxyMiddleware({
49+
target: `http://httpbin.org`,
50+
changeOrigin: true, // for vhosted sites, changes host header to match to target's host
51+
selfHandleResponse: true,
52+
onProxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
53+
return responseBuffer;
54+
}),
55+
})
56+
)
57+
);
58+
});
59+
60+
it('should proxy and return original headers from http://httpbin.org/cookies/set/cookie/monster', async () => {
61+
return agent
62+
.get(`/cookies/set/cookie/monster`)
63+
.expect('Access-Control-Allow-Origin', '*')
64+
.expect('Date', /.+/)
65+
.expect('set-cookie', /.*cookie=monster.*/)
66+
.expect(302);
67+
});
68+
});
69+
4470
describe('intercept compressed responses', () => {
4571
beforeEach(() => {
4672
agent = request(

0 commit comments

Comments
 (0)