Skip to content

Commit

Permalink
Add support for proxying response trailers (focusing on HTTP/2)
Browse files Browse the repository at this point in the history
This is a first step, which we're prioritising because it directly
affects gRPC behaviour in HTTP Toolkit. The overall goal though is to
fully expose trailer data, support it for requests too, and support it
throughout the wider API just like headers.
  • Loading branch information
pimterry committed Apr 30, 2024
1 parent 1e15901 commit 27c9ea2
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 4 deletions.
27 changes: 27 additions & 0 deletions src/rules/requests/request-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,33 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
reject(e);
});

// Forward server trailers, if we receive any:
serverRes.on('end', () => {
if (!serverRes.rawTrailers?.length) return;

const trailersToForward = pairFlatRawHeaders(serverRes.rawTrailers)
.filter(([key, value]) => {
if (!validateHeader(key, value)) {
console.warn(`Not forwarding invalid trailer: "${key}: ${value}"`);
// Nothing else we can do in this case regardless - setHeaders will
// throw within Node if we try to set this value.
return false;
}
return true;
});

try {
clientRes.addTrailers(
isHttp2(clientReq)
// HTTP/2 compat doesn't support raw headers here (yet)
? rawHeadersToObjectPreservingCase(trailersToForward)
: trailersToForward
);
} catch (e) {
console.warn(`Failed to forward response trailers: ${e}`);
}
});

let serverStatusCode = serverRes.statusCode!;
let serverStatusMessage = serverRes.statusMessage
let serverRawHeaders = pairFlatRawHeaders(serverRes.rawHeaders);
Expand Down
14 changes: 14 additions & 0 deletions test/integration/proxying/https-proxying.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ nodeOnly(() => {
"received-headers": JSON.stringify(req.headers),
"received-body": (await streamToBuffer(req)).toString('utf8') || ''
});
res.addTrailers({ 'test-response-trailer': 'trailer-value' });
res.end("Real HTTP/2 response");
}));

Expand All @@ -453,6 +454,19 @@ nodeOnly(() => {
expect(response.body.toString('utf8')).to.equal("Real HTTP/2 response");
});

it("can pass through response trailers successfully", async () => {
await server.forAnyRequest().thenPassThrough({
ignoreHostHttpsErrors: ['localhost']
});

const response = await http2ProxyRequest(server, `https://localhost:${targetPort}`, {
headers: { ':method': 'GET' }
});

expect(response.headers[':status']).to.equal(200);
expect(response.trailers['test-response-trailer']).to.equal('trailer-value');
});

it("should return a 502 for failing upstream requests by default", async () => {
await server.forAnyRequest().thenPassThrough();

Expand Down
20 changes: 16 additions & 4 deletions test/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ type Http2ResponseHeaders = http2.IncomingHttpHeaders & http2.IncomingHttpStatus
type Http2TestRequestResult = {
alpnProtocol: string | undefined,
headers: http2.IncomingHttpHeaders,
body: Buffer
body: Buffer,
trailers: http2.IncomingHttpHeaders
};

export function getHttp2Response(req: http2.ClientHttp2Stream) {
Expand All @@ -303,6 +304,14 @@ export function getHttp2Body(req: http2.ClientHttp2Stream) {
});
}

export function getHttp2ResponseTrailers(req: http2.ClientHttp2Stream) {
return new Promise<Http2ResponseHeaders>((resolve, reject) => {
req.on('trailers', resolve);
req.on('end', () => resolve({}));
req.on('error', reject);
});
}

export async function http2Request(
url: string,
headers: {},
Expand All @@ -321,18 +330,21 @@ export async function http2Request(

const [
responseHeaders,
responseBody
responseBody,
responseTrailers
] = await Promise.all([
getHttp2Response(req),
getHttp2Body(req)
getHttp2Body(req),
getHttp2ResponseTrailers(req)
]);

const alpnProtocol = client.alpnProtocol;

resolve({
alpnProtocol,
headers: responseHeaders,
body: responseBody
body: responseBody,
trailers: responseTrailers
});
} catch (e) {
reject(e);
Expand Down

0 comments on commit 27c9ea2

Please sign in to comment.