Skip to content

Commit aac3d2b

Browse files
authored
Merge pull request #43 from sapphi-red/fix/set-host-header-for-requests-from-http2-to-http1
fix: set host header for requests from HTTP2 to HTTP1
2 parents 0bbd19e + 9260b5a commit aac3d2b

File tree

6 files changed

+159
-4
lines changed

6 files changed

+159
-4
lines changed

lib/http-proxy/common.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export function setupOutgoing(
8181

8282
outgoing.method = options.method || req.method;
8383
outgoing.headers = { ...req.headers };
84+
if (req.headers?.[":authority"]) {
85+
outgoing.headers.host = req.headers[":authority"];
86+
}
8487

8588
if (options.headers) {
8689
outgoing.headers = { ...outgoing.headers, ...options.headers };
@@ -189,7 +192,8 @@ export function getPort(
189192
req: Request,
190193
// Return the port number, as a string.
191194
): string {
192-
const res = req.headers.host ? req.headers.host.match(/:(\d+)/) : "";
195+
const hostHeader = (req.headers[":authority"] as string | undefined) || req.headers.host;
196+
const res = hostHeader ? hostHeader.match(/:(\d+)/) : "";
193197
return res ? res[1] : hasEncryptedConnection(req) ? "443" : "80";
194198
}
195199

lib/http-proxy/passes/web-incoming.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function XHeaders(req: Request, _res: Response, options: ServerOptions) {
6969
(req.headers["x-forwarded-" + header] || "") + (req.headers["x-forwarded-" + header] ? "," : "") + values[header];
7070
}
7171

72-
req.headers["x-forwarded-host"] = req.headers["x-forwarded-host"] || req.headers["host"] || "";
72+
req.headers["x-forwarded-host"] = req.headers["x-forwarded-host"] || req.headers[":authority"] || req.headers["host"] || "";
7373
}
7474

7575
// Does the actual proxying. If `forward` is enabled fires up

lib/http-proxy/passes/web-outgoing.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function setRedirectHostRewrite(
7777
if (options.hostRewrite) {
7878
u.host = options.hostRewrite;
7979
} else if (options.autoRewrite) {
80-
u.host = req.headers["host"] ?? "";
80+
u.host = (req.headers[":authority"] as string | undefined) ?? req.headers["host"] ?? "";
8181
}
8282
if (options.protocolRewrite) {
8383
u.protocol = options.protocolRewrite;
@@ -143,7 +143,7 @@ export function writeHeaders(
143143

144144
for (const key0 in proxyRes.headers) {
145145
let key = key0;
146-
if (_req.httpVersionMajor > 1 && (key === "connection" || key === "keep-alive")) {
146+
if (_req.httpVersionMajor > 1 && (key === "connection" || key === "keep-alive")) {
147147
// don't send connection header to http2 client
148148
continue;
149149
}

lib/test/lib/http-proxy-passes-web-incoming.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,32 @@ describe("#XHeaders", () => {
7272
host: "192.168.1.2:8080",
7373
} as Record<string, string>,
7474
};
75+
const stubHttp2Request = {
76+
connection: {
77+
remoteAddress: "192.168.1.2",
78+
remotePort: "8080",
79+
},
80+
headers: {
81+
':authority': "192.168.1.2:8080",
82+
} as Record<string, string>,
83+
};
7584

7685
it("set the correct x-forwarded-* headers", () => {
7786
// @ts-ignore
7887
XHeaders(stubRequest, {}, { xfwd: true });
7988
expect(stubRequest.headers["x-forwarded-for"]).toEqual("192.168.1.2");
8089
expect(stubRequest.headers["x-forwarded-port"]).toEqual("8080");
8190
expect(stubRequest.headers["x-forwarded-proto"]).toEqual("http");
91+
expect(stubRequest.headers["x-forwarded-host"]).toEqual("192.168.1.2:8080");
92+
});
93+
94+
it("set the correct x-forwarded-* headers for http2", () => {
95+
// @ts-ignore
96+
XHeaders(stubHttp2Request, {}, { xfwd: true });
97+
expect(stubHttp2Request.headers["x-forwarded-for"]).toEqual("192.168.1.2");
98+
expect(stubHttp2Request.headers["x-forwarded-port"]).toEqual("8080");
99+
expect(stubHttp2Request.headers["x-forwarded-proto"]).toEqual("http");
100+
expect(stubHttp2Request.headers["x-forwarded-host"]).toEqual("192.168.1.2:8080");
82101
});
83102
});
84103

lib/test/lib/http-proxy-passes-web-outgoing.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ describe.each( ["http://backend.com", url.parse("http://backend.com")])("#setRed
105105
"http://ext-auto.com/",
106106
);
107107
});
108+
109+
it("on " + code + " (http2)", () => {
110+
state.proxyRes.statusCode = code;
111+
state.req.headers[":authority"] = state.req.headers.host;
112+
delete state.req.headers.host;
113+
setRedirectHostRewrite(state.req, {}, state.proxyRes, state.options);
114+
expect(state.proxyRes.headers.location).toEqual(
115+
"http://ext-auto.com/",
116+
);
117+
});
108118
});
109119

110120
it("not on 200", () => {

lib/test/lib/http2-proxy.test.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import * as httpProxy from "../..";
2+
import * as http from "node:http";
3+
import * as http2 from "node:http2";
4+
import getPort from "../get-port";
5+
import { join } from "node:path";
6+
import { readFileSync } from "node:fs";
7+
import { describe, it, expect } from 'vitest';
8+
9+
const ports: { [port: string]: number } = {};
10+
let portIndex = -1;
11+
const gen = {} as { port: number };
12+
Object.defineProperty(gen, "port", {
13+
get: function get() {
14+
portIndex++;
15+
return ports[portIndex];
16+
},
17+
});
18+
19+
describe("HTTP2 to HTTP", () => {
20+
it("creates some ports", async () => {
21+
for (let n = 0; n < 50; n++) {
22+
ports[n] = await getPort();
23+
}
24+
});
25+
26+
it("should proxy the request, then send back the response", () => new Promise<void>(done => {
27+
const ports = { source: gen.port, proxy: gen.port };
28+
const source = http
29+
.createServer((req, res) => {
30+
expect(req.method).toEqual("GET");
31+
expect(req.headers.host?.split(":")[1]).toEqual(`${ports.proxy}`);
32+
res.writeHead(200, { "Content-Type": "text/plain" });
33+
res.end("Hello from " + ports.source);
34+
})
35+
.listen(ports.source);
36+
37+
const proxy = httpProxy
38+
.createProxyServer({
39+
target: "http://127.0.0.1:" + ports.source,
40+
ssl: {
41+
key: readFileSync(
42+
join(__dirname, "..", "fixtures", "agent2-key.pem"),
43+
),
44+
cert: readFileSync(
45+
join(__dirname, "..", "fixtures", "agent2-cert.pem"),
46+
),
47+
ciphers: "AES128-GCM-SHA256",
48+
},
49+
})
50+
.listen(ports.proxy);
51+
52+
const client = http2.connect(`https://localhost:${ports.proxy}`)
53+
const req = client.request({ ':path': '/' });
54+
req.on('response', (headers, _flags) => {
55+
expect(headers[':status']).toEqual(200);
56+
req.setEncoding('utf8');
57+
req.on('data', (chunk) => {
58+
expect(chunk.toString()).toEqual("Hello from " + ports.source);
59+
});
60+
req.on('end', () => {
61+
source.close();
62+
proxy.close();
63+
done();
64+
});
65+
});
66+
req.end();
67+
}));
68+
});
69+
70+
describe("HTTP2 to HTTP using own server", () => {
71+
it("should proxy the request, then send back the response", () => new Promise<void>(done => {
72+
const ports = { source: gen.port, proxy: gen.port };
73+
const source = http
74+
.createServer((req, res) => {
75+
expect(req.method).toEqual("GET");
76+
expect(req.headers.host?.split(":")[1]).toEqual(`${ports.proxy}`);
77+
res.writeHead(200, { "Content-Type": "text/plain" });
78+
res.end("Hello from " + ports.source);
79+
})
80+
.listen(ports.source);
81+
82+
const proxy = httpProxy.createServer({
83+
agent: new http.Agent({ maxSockets: 2 }),
84+
});
85+
86+
const ownServer = http2
87+
.createSecureServer(
88+
{
89+
key: readFileSync(
90+
join(__dirname, "..", "fixtures", "agent2-key.pem"),
91+
),
92+
cert: readFileSync(
93+
join(__dirname, "..", "fixtures", "agent2-cert.pem"),
94+
),
95+
ciphers: "AES128-GCM-SHA256",
96+
},
97+
(req, res) => {
98+
// @ts-expect-error -- ignore type incompatibility
99+
proxy.web(req, res, {
100+
target: "http://127.0.0.1:" + ports.source,
101+
});
102+
},
103+
)
104+
.listen(ports.proxy);
105+
106+
const client = http2.connect(`https://localhost:${ports.proxy}`)
107+
const req = client.request({ ':path': '/' });
108+
req.on('response', (headers, _flags) => {
109+
expect(headers[':status']).toEqual(200);
110+
req.setEncoding('utf8');
111+
req.on('data', (chunk) => {
112+
expect(chunk.toString()).toEqual("Hello from " + ports.source);
113+
});
114+
req.on('end', () => {
115+
source.close();
116+
ownServer.close();
117+
done();
118+
});
119+
});
120+
req.end();
121+
}));
122+
});

0 commit comments

Comments
 (0)