Skip to content

Commit

Permalink
Add support for proxy request trailers (HTTP/1 only for now)
Browse files Browse the repository at this point in the history
  • Loading branch information
pimterry committed May 1, 2024
1 parent bcf6eb9 commit 59b8706
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/rules/requests/request-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,26 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
socket.once('close', () => this.outgoingSockets.delete(socket));
});

// Forward any request trailers received from the client:
const forwardTrailers = () => {
if (clientReq.rawTrailers?.length) {
if (serverReq.addTrailers) {
serverReq.addTrailers(clientReq.rawTrailers);
} else {
// See https://github.com/szmarczak/http2-wrapper/issues/103
console.warn('Not forwarding request trailers - not yet supported for HTTP/2');
}
}
};
// This has to be above the pipe setup below, or we end the stream before adding the
// trailers, and they're lost.
if (clientReqBody.readableEnded) {
forwardTrailers();
} else {
clientReqBody.once('end', forwardTrailers);
}

// Forward the request body to the upstream server:
if (reqBodyOverride) {
clientReqBody.resume(); // Dump any remaining real request body

Expand Down
72 changes: 72 additions & 0 deletions test/integration/proxying/http-proxying.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
defaultNodeConnectionHeader
} from "../../test-utils";
import { isLocalIPv6Available } from "../../../src/util/socket-util";
import { streamToBuffer } from "../../../src/util/buffer-utils";

const INITIAL_ENV = _.cloneDeep(process.env);

Expand Down Expand Up @@ -138,6 +139,43 @@ nodeOnly(() => {
expect(response.statusCode).to.equal(200); // Callback expectations should run OK
});

it("should be able to pass through request trailers", async () => {
await remoteServer.forAnyRequest().thenCallback(async (req) => {
const trailers = req.rawTrailers;
expect(trailers).to.deep.equal([
['trailer-NAME', 'trailer-value']
]);

return {
statusCode: 200,
body: 'Found expected trailers'
};
});

await server.forAnyRequest().thenPassThrough();

const request = http.request({
method: 'POST',
hostname: 'localhost',
port: server.port,
headers: {
'Trailer': 'trailer-name',
'Host': `localhost:${remoteServer.port}` // Manually proxy upstream
}
});

request.addTrailers({ 'trailer-NAME': 'trailer-value' });
request.end();

const response = await new Promise<http.IncomingMessage>((resolve) =>
request.on('response', resolve)
);

expect(response.statusCode).to.equal(200);
expect((await streamToBuffer(response)).toString('utf8'))
.to.equal('Found expected trailers');
});

it("should be able to pass back response headers", async () => {
await remoteServer.forAnyRequest().thenCallback(async (req) => ({
statusCode: 200,
Expand Down Expand Up @@ -166,6 +204,40 @@ nodeOnly(() => {
]);
});

it("should be able to pass back response trailers", async () => {
await remoteServer.forAnyRequest().thenCallback(async (req) => ({
statusCode: 200,
body: await req.body.getText(),
headers: {
'Trailer': 'trailer-name',
'Transfer-Encoding': 'chunked'
},
trailers: {
'Trailer-Name': 'trailer-value' // N.b thenCallback is not case sensitive (yet?)
}
}));

await server.forAnyRequest().thenPassThrough();

const request = http.request({
method: 'GET',
hostname: 'localhost',
port: server.port,
headers: {
'Host': `localhost:${remoteServer.port}` // Manually proxy upstream
}
}).end();

const response = await new Promise<http.IncomingMessage>((resolve) =>
request.on('response', resolve)
);

await streamToBuffer(response); // Wait for response to complete
expect(response.rawTrailers).to.deep.equal([
'Trailer-Name', 'trailer-value'
]);
});

it("should be able to pass through requests with a body", async () => {
await remoteServer.forAnyRequest().thenCallback(async (req) => ({
statusCode: 200,
Expand Down

0 comments on commit 59b8706

Please sign in to comment.