Skip to content

Commit 397f39f

Browse files
midgleycchimurai
andauthored
feat: try to proxy body even after body-parser middleware (#492)
* feat: try to keep body even after body-parser middleware * chore: don't shadow request * test: set correct form of bodyparser for json * feat: extract fixRequestBody to external handler * chore: lint * chore: pluralise middlewares, silence DeepCode bot * docs: mention fixRequestBody * added double byte character test Co-authored-by: chimurai <[email protected]>
1 parent 1704875 commit 397f39f

File tree

7 files changed

+93
-5
lines changed

7 files changed

+93
-5
lines changed

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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';

test/e2e/_utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as express from 'express';
22
import { Express, RequestHandler } from 'express';
33

4-
export { createProxyMiddleware, responseInterceptor } from '../../dist/index';
4+
export { createProxyMiddleware, responseInterceptor, fixRequestBody } from '../../dist/index';
55

6-
export function createApp(middleware: RequestHandler): Express {
6+
export function createApp(...middlewares: RequestHandler[]): Express {
77
const app = express();
8-
app.use(middleware);
8+
app.use(...middlewares);
99
return app;
1010
}
1111

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 './_utils';
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) => {

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,7 @@ [email protected]:
14831483
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
14841484
integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
14851485

1486-
[email protected], body-parser@^1.15.2:
1486+
[email protected], body-parser@^1.15.2, body-parser@^1.19.0:
14871487
version "1.19.0"
14881488
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
14891489
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==

0 commit comments

Comments
 (0)