Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/http-proxy/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export interface Outgoing extends Outgoing0 {
// See https://github.com/http-party/node-http-proxy/issues/1647
const HEADER_BLACKLIST = "trailer";

const HTTP2_HEADER_BLACKLIST = [
':method',
':path',
':scheme',
':authority',
]

// setupOutgoing -- Copies the right headers from `options` and `req` to
// `outgoing` which is then used to fire the proxied request by calling
// http.request or https.request with outgoing as input.
Expand Down Expand Up @@ -81,6 +88,12 @@ export function setupOutgoing(
}
}

if (req.httpVersionMajor > 1) {
for (const header of HTTP2_HEADER_BLACKLIST) {
delete outgoing.headers[header];
}
}

if (options.auth) {
delete outgoing.headers.authorization;
outgoing.auth = options.auth;
Expand Down
15 changes: 8 additions & 7 deletions lib/http-proxy/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as http from "node:http";
import * as https from "node:https";
import * as http2 from "node:http2";
import * as net from "node:net";
import { WEB_PASSES } from "./passes/web-incoming";
import { WS_PASSES } from "./passes/ws-incoming";
Expand Down Expand Up @@ -220,7 +220,7 @@ export class ProxyServer<TIncomingMessage extends typeof http.IncomingMessage =
private options: ServerOptions;
private webPasses: Array<PassFunctions<TIncomingMessage, TServerResponse, TError>['web']>;
private wsPasses: Array<PassFunctions<TIncomingMessage, TServerResponse, TError>['ws']>;
private _server?: http.Server<TIncomingMessage, TServerResponse> | https.Server<TIncomingMessage, TServerResponse> | null;
private _server?: http.Server<TIncomingMessage, TServerResponse> | http2.Http2SecureServer<TIncomingMessage, TServerResponse> | null;

/**
* Creates the proxy server with specified options.
Expand Down Expand Up @@ -367,13 +367,14 @@ export class ProxyServer<TIncomingMessage extends typeof http.IncomingMessage =
listen = (port: number, hostname?: string) => {
log("listen", { port, hostname });

const requestListener = (req: InstanceType<TIncomingMessage>, res: InstanceType<TServerResponse>) => {
this.web(req, res);
const requestListener = (req: InstanceType<TIncomingMessage> | http2.Http2ServerRequest, res: InstanceType<TServerResponse> |http2.Http2ServerResponse) => {
this.web(req as InstanceType<TIncomingMessage>, res as InstanceType<TServerResponse>);
};

this._server = this.options.ssl
? https.createServer<TIncomingMessage, TServerResponse>(this.options.ssl, requestListener)
: http.createServer<TIncomingMessage, TServerResponse>(requestListener);
this._server = this.options.ssl ? http2.createSecureServer(
{ ...this.options.ssl, allowHTTP1: true },
requestListener
) : http.createServer<TIncomingMessage, TServerResponse>(requestListener);

if (this.options.ws) {
this._server.on("upgrade", (req: InstanceType<TIncomingMessage>, socket, head) => {
Expand Down
11 changes: 7 additions & 4 deletions lib/http-proxy/passes/web-outgoing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ export function writeHeaders(

for (const key0 in proxyRes.headers) {
let key = key0;
if (_req.httpVersionMajor > 1 && key === "connection") {
// don't send connection header to http2 client
continue;
}
const header = proxyRes.headers[key];
if (preserveHeaderKeyCase && rawHeaderKeyMap) {
key = rawHeaderKeyMap[key] ?? key;
Expand All @@ -158,11 +162,10 @@ export function writeStatusCode(
proxyRes: ProxyResponse,
) {
// From Node.js docs: response.writeHead(statusCode[, statusMessage][, headers])
if (proxyRes.statusMessage) {
res.statusCode = proxyRes.statusCode!;
res.statusCode = proxyRes.statusCode!;

if (proxyRes.statusMessage && _req.httpVersionMajor === 1) {
res.statusMessage = proxyRes.statusMessage;
} else {
res.statusCode = proxyRes.statusCode!;
}
}

Expand Down
68 changes: 68 additions & 0 deletions lib/test/http/proxy-http2-to-http.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
pnpm test proxy-https-to-http.test.ts
*/

import * as http from "node:http";
import * as httpProxy from "../..";
import getPort from "../get-port";
import { join } from "node:path";
import { readFile } from "node:fs/promises";
import fetch from "node-fetch";
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Agent, setGlobalDispatcher } from "undici";

setGlobalDispatcher(new Agent({
allowH2: true
}));


const fixturesDir = join(__dirname, "..", "fixtures");

describe("Basic example of proxying over HTTPS to a target HTTP server", () => {
let ports: Record<'http' | 'proxy', number>;
beforeAll(async () => {
ports = { http: await getPort(), proxy: await getPort() };
});

const servers: any = {};

it("Create the target HTTP server", async () => {
servers.http = http
.createServer((_req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.write("hello http over https\n");
res.end();
})
.listen(ports.http);
});

it("Create the HTTPS proxy server", async () => {
servers.proxy = httpProxy
.createServer({
target: {
host: "localhost",
port: ports.http,
},
ssl: {
key: await readFile(join(fixturesDir, "agent2-key.pem"), "utf8"),
cert: await readFile(join(fixturesDir, "agent2-cert.pem"), "utf8"),
},
})
.listen(ports.proxy);
});

it("Use fetch to test non-https server", async () => {
const r = await (await fetch(`http://localhost:${ports.http}`)).text();
expect(r).toContain("hello http over https");
});

it("Use fetch to test the ACTUAL https server", async () => {
const r = await (await fetch(`https://localhost:${ports.proxy}`)).text();
expect(r).toContain("hello http over https");
});

afterAll(async () => {
// cleans up
Object.values(servers).map((x: any) => x?.close());
});
});
69 changes: 69 additions & 0 deletions lib/test/http/proxy-http2-to-https.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
pnpm test proxy-https-to-https.test.ts

*/

import * as https from "node:https";
import * as httpProxy from "../..";
import getPort from "../get-port";
import { join } from "node:path";
import { readFile } from "node:fs/promises";
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Agent, setGlobalDispatcher } from "undici";

setGlobalDispatcher(new Agent({
allowH2: true
}));

const fixturesDir = join(__dirname, "..", "fixtures");

describe("Basic example of proxying over HTTPS to a target HTTPS server", () => {
let ports: Record<'https' | 'proxy', number>;
beforeAll(async () => {
// Gets ports
ports = { https: await getPort(), proxy: await getPort() };
});

const servers: any = {};
let ssl: { key: string; cert: string };

it("Create the target HTTPS server", async () => {
ssl = {
key: await readFile(join(fixturesDir, "agent2-key.pem"), "utf8"),
cert: await readFile(join(fixturesDir, "agent2-cert.pem"), "utf8"),
};
servers.https = https
.createServer(ssl, (_req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.write("hello over https\n");
res.end();
})
.listen(ports.https);
});

it("Create the HTTPS proxy server", async () => {
servers.proxy = httpProxy
.createServer({
target: `https://localhost:${ports.https}`,
ssl,
// without secure false, clients will fail and this is broken:
secure: false,
})
.listen(ports.proxy);
});

it("Use fetch to test direct non-proxied https server", async () => {
const r = await (await fetch(`https://localhost:${ports.https}`)).text();
expect(r).toContain("hello over https");
});

it("Use fetch to test the proxy server", async () => {
const r = await (await fetch(`https://localhost:${ports.proxy}`)).text();
expect(r).toContain("hello over https");
});

afterAll(async () => {
// cleanup
Object.values(servers).map((x: any) => x?.close());
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
"typescript": "^5.8.3",
"undici": "^7.16.0",
"vitest": "^3.2.4",
"ws": "^8.18.2"
},
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.