Skip to content

Commit 59b8706

Browse files
committed
Add support for proxy request trailers (HTTP/1 only for now)
1 parent bcf6eb9 commit 59b8706

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/rules/requests/request-handlers.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,26 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
10331033
socket.once('close', () => this.outgoingSockets.delete(socket));
10341034
});
10351035

1036+
// Forward any request trailers received from the client:
1037+
const forwardTrailers = () => {
1038+
if (clientReq.rawTrailers?.length) {
1039+
if (serverReq.addTrailers) {
1040+
serverReq.addTrailers(clientReq.rawTrailers);
1041+
} else {
1042+
// See https://github.com/szmarczak/http2-wrapper/issues/103
1043+
console.warn('Not forwarding request trailers - not yet supported for HTTP/2');
1044+
}
1045+
}
1046+
};
1047+
// This has to be above the pipe setup below, or we end the stream before adding the
1048+
// trailers, and they're lost.
1049+
if (clientReqBody.readableEnded) {
1050+
forwardTrailers();
1051+
} else {
1052+
clientReqBody.once('end', forwardTrailers);
1053+
}
1054+
1055+
// Forward the request body to the upstream server:
10361056
if (reqBodyOverride) {
10371057
clientReqBody.resume(); // Dump any remaining real request body
10381058

test/integration/proxying/http-proxying.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
defaultNodeConnectionHeader
2222
} from "../../test-utils";
2323
import { isLocalIPv6Available } from "../../../src/util/socket-util";
24+
import { streamToBuffer } from "../../../src/util/buffer-utils";
2425

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

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

142+
it("should be able to pass through request trailers", async () => {
143+
await remoteServer.forAnyRequest().thenCallback(async (req) => {
144+
const trailers = req.rawTrailers;
145+
expect(trailers).to.deep.equal([
146+
['trailer-NAME', 'trailer-value']
147+
]);
148+
149+
return {
150+
statusCode: 200,
151+
body: 'Found expected trailers'
152+
};
153+
});
154+
155+
await server.forAnyRequest().thenPassThrough();
156+
157+
const request = http.request({
158+
method: 'POST',
159+
hostname: 'localhost',
160+
port: server.port,
161+
headers: {
162+
'Trailer': 'trailer-name',
163+
'Host': `localhost:${remoteServer.port}` // Manually proxy upstream
164+
}
165+
});
166+
167+
request.addTrailers({ 'trailer-NAME': 'trailer-value' });
168+
request.end();
169+
170+
const response = await new Promise<http.IncomingMessage>((resolve) =>
171+
request.on('response', resolve)
172+
);
173+
174+
expect(response.statusCode).to.equal(200);
175+
expect((await streamToBuffer(response)).toString('utf8'))
176+
.to.equal('Found expected trailers');
177+
});
178+
141179
it("should be able to pass back response headers", async () => {
142180
await remoteServer.forAnyRequest().thenCallback(async (req) => ({
143181
statusCode: 200,
@@ -166,6 +204,40 @@ nodeOnly(() => {
166204
]);
167205
});
168206

207+
it("should be able to pass back response trailers", async () => {
208+
await remoteServer.forAnyRequest().thenCallback(async (req) => ({
209+
statusCode: 200,
210+
body: await req.body.getText(),
211+
headers: {
212+
'Trailer': 'trailer-name',
213+
'Transfer-Encoding': 'chunked'
214+
},
215+
trailers: {
216+
'Trailer-Name': 'trailer-value' // N.b thenCallback is not case sensitive (yet?)
217+
}
218+
}));
219+
220+
await server.forAnyRequest().thenPassThrough();
221+
222+
const request = http.request({
223+
method: 'GET',
224+
hostname: 'localhost',
225+
port: server.port,
226+
headers: {
227+
'Host': `localhost:${remoteServer.port}` // Manually proxy upstream
228+
}
229+
}).end();
230+
231+
const response = await new Promise<http.IncomingMessage>((resolve) =>
232+
request.on('response', resolve)
233+
);
234+
235+
await streamToBuffer(response); // Wait for response to complete
236+
expect(response.rawTrailers).to.deep.equal([
237+
'Trailer-Name', 'trailer-value'
238+
]);
239+
});
240+
169241
it("should be able to pass through requests with a body", async () => {
170242
await remoteServer.forAnyRequest().thenCallback(async (req) => ({
171243
statusCode: 200,

0 commit comments

Comments
 (0)