Skip to content

Commit dd94fb0

Browse files
author
ROUSSE Kevin
committed
feat: handle proxy cookies, origin & body parsing (POST & PUT)
1 parent bf39614 commit dd94fb0

File tree

3 files changed

+75
-8
lines changed

3 files changed

+75
-8
lines changed

src/proxy.ts

+60-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,53 @@
11
import express from 'express';
2-
import { createProxyServer } from 'http-proxy';
2+
import { ClientRequest } from 'http';
3+
import { createProxyServer, ProxyReqCallback, ProxyResCallback } from 'http-proxy';
4+
import * as querystring from 'querystring';
35

4-
const proxy = createProxyServer({ changeOrigin: true });
6+
import { ProxyParams } from './stubServer';
57

6-
// Exported for testing purposes only, see [Jest mock inner function](https://stackoverflow.com/q/51269431)
8+
// [Body Parsing with express](https://github.com/chimurai/http-proxy-middleware/issues/320)
9+
const handleBodyParsing: ProxyReqCallback = (proxyReq, req) => {
10+
// @ts-ignore
11+
const { body } = req;
712

8-
export const send = (
13+
const contentType = proxyReq.getHeader('Content-Type');
14+
const writeBody = (_body: string) => {
15+
proxyReq.setHeader('Content-Length', Buffer.byteLength(_body));
16+
proxyReq.write(_body);
17+
};
18+
19+
if (
20+
contentType === 'application/json' ||
21+
(Array.isArray(contentType) && contentType.includes('application/json'))
22+
) {
23+
writeBody(JSON.stringify(body));
24+
}
25+
26+
if (
27+
contentType === 'application/x-www-form-urlencoded' ||
28+
(Array.isArray(contentType) && contentType.includes('application/x-www-form-urlencoded'))
29+
) {
30+
writeBody(querystring.stringify(body));
31+
}
32+
};
33+
34+
const changeOrigin = (proxyReq: ClientRequest, origin: string | undefined) => {
35+
if (origin) {
36+
proxyReq.setHeader('Origin', origin);
37+
}
38+
};
39+
40+
const removeSecurityFromCookie: ProxyResCallback = proxyRes => {
41+
/* eslint-disable no-param-reassign */
42+
proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie']?.map((cookie: string) =>
43+
cookie.replace('Path=/; Secure', 'Path=/')
44+
);
45+
/* eslint-enable no-param-reassign */
46+
};
47+
48+
const send = (
949
target: string,
50+
params: ProxyParams,
1051
req: express.Request,
1152
res: express.Response,
1253
next: express.NextFunction
@@ -21,5 +62,20 @@ export const send = (
2162
//
2263
// [Proxy with express.js](https://stackoverflow.com/q/10435407)
2364

65+
const { host = false, origin } = params;
66+
67+
const proxy = createProxyServer({
68+
target,
69+
changeOrigin: true,
70+
cookieDomainRewrite: host
71+
});
72+
73+
proxy.on('proxyReq', (proxyReq, ...args) => {
74+
changeOrigin(proxyReq, origin);
75+
handleBodyParsing(proxyReq, ...args);
76+
});
77+
proxy.on('proxyRes', removeSecurityFromCookie);
2478
proxy.web(req, res, { target }, next);
2579
};
80+
81+
export { send };

src/stubServer.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'path';
33
import request from 'supertest';
44

55
import * as proxy from './proxy';
6-
import { stubServer } from './stubServer';
6+
import { ProxyParams, stubServer } from './stubServer';
77

88
const configPath = path.resolve(__dirname, 'config-test', 'config');
99

@@ -205,6 +205,7 @@ describe('proxy', () => {
205205
.mockImplementationOnce(
206206
(
207207
_target: string,
208+
_params: ProxyParams,
208209
_req: express.Request,
209210
res: express.Response,
210211
_next: express.NextFunction
@@ -225,6 +226,7 @@ describe('proxy', () => {
225226
.mockImplementationOnce(
226227
(
227228
_target: string,
229+
_params: ProxyParams,
228230
_req: express.Request,
229231
_res: express.Response,
230232
next: express.NextFunction
@@ -258,6 +260,7 @@ describe('proxy', () => {
258260
.mockImplementationOnce(
259261
(
260262
_target: string,
263+
_params: ProxyParams,
261264
_req: express.Request,
262265
res: express.Response,
263266
_next: express.NextFunction

src/stubServer.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@ type Response = {
1919

2020
type Route = { delay?: Delay } & { headers?: IncomingHttpHeaders } & Response;
2121

22+
export interface ProxyParams {
23+
host: string | undefined;
24+
origin: string | undefined;
25+
}
2226
export interface StubServerConfig {
2327
delay?: Delay;
2428
headers?: IncomingHttpHeaders;
2529
routes: {
2630
[apiPath: string]: Route;
2731
};
32+
proxyParams?: ProxyParams;
2833
}
2934

3035
// Allows to modify the imported files without restarting the server
@@ -97,6 +102,7 @@ async function parseConfig(apiPath: string, req: express.Request) {
97102

98103
async function processStubRequest(
99104
apiPath: string,
105+
proxyParams: ProxyParams,
100106
req: express.Request,
101107
res: express.Response,
102108
next: express.NextFunction
@@ -105,7 +111,7 @@ async function processStubRequest(
105111

106112
if (isUrl(stubName)) {
107113
const url = stubName;
108-
send(url, req, res, next);
114+
send(url, proxyParams, req, res, next);
109115
} else {
110116
const filename = stubName;
111117

@@ -154,14 +160,16 @@ export function stubServer(configPath: string, app: express.Application) {
154160

155161
_configPath = configPath;
156162

157-
const { routes } = getConfig();
163+
const { routes, proxyParams } = getConfig();
164+
165+
const defaultProxyParams = { host: undefined, origin: undefined };
158166

159167
Object.entries(routes).forEach(([apiPath, route]) => {
160168
Object.keys(route).forEach(httpVerb => {
161169
if (httpVerb !== 'delay' && httpVerb !== 'headers') {
162170
// If invalid HTTP verb, crash with "TypeError: app[httpVerb] is not a function"
163171
app[httpVerb.toLowerCase() as ExpressRoute](apiPath, (req, res, next) =>
164-
processStubRequest(apiPath, req, res, next)
172+
processStubRequest(apiPath, proxyParams || defaultProxyParams, req, res, next)
165173
);
166174
}
167175
});

0 commit comments

Comments
 (0)