Skip to content

Commit d3e505e

Browse files
committed
Fire passthrough-response-body even if beforeResponse closes the conn
1 parent f07d739 commit d3e505e

File tree

2 files changed

+76
-7
lines changed

2 files changed

+76
-7
lines changed

src/rules/requests/request-handlers.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -947,13 +947,26 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
947947
trailers: rawHeadersToObject(clientReq.rawTrailers ?? []),
948948
});
949949

950-
if (modifiedRes === 'close') {
951-
(clientReq as any).socket.end();
952-
throw new AbortError('Connection closed intentionally by rule');
953-
} else if (modifiedRes === 'reset') {
954-
requireSocketResetSupport();
955-
resetOrDestroy(clientReq);
956-
throw new AbortError('Connection reset intentionally by rule');
950+
if (modifiedRes === 'close' || modifiedRes === 'reset') {
951+
// If you kill the connection, we need to fire an upstream event separately here, since
952+
// this means the body won't be delivered in normal response events.
953+
if (options.emitEventCallback) {
954+
options.emitEventCallback!('passthrough-response-body', {
955+
overridden: true,
956+
rawBody: originalBody
957+
});
958+
}
959+
960+
if (modifiedRes === 'close') {
961+
(clientReq as any).socket.end();
962+
} else if (modifiedRes === 'reset') {
963+
requireSocketResetSupport();
964+
resetOrDestroy(clientReq);
965+
}
966+
967+
throw new AbortError(
968+
`Connection ${modifiedRes === 'close' ? 'closed' : 'reset'} intentionally by rule`
969+
);
957970
}
958971

959972
validateCustomHeaders(serverHeaders, modifiedRes?.headers);

test/integration/subscriptions/rule-events.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,24 @@ describe("Rule event susbcriptions", () => {
129129
expect(responseBodyEvent).to.deep.equal({ overridden: false });
130130
});
131131

132+
it("should fire no events if beforeRequest closes response", async () => {
133+
await remoteServer.forAnyRequest().thenReply(200);
134+
const forwardingRule = await server.forAnyRequest().thenForwardTo(remoteServer.url, {
135+
beforeRequest: () => ({ response: 'close' })
136+
});
137+
138+
const ruleEvents: RuleEvent<any>[] = [];
139+
await server.on('rule-event', (e) => ruleEvents.push(e));
140+
141+
const response = await fetch(server.url).catch((e) => e);
142+
expect(response).to.be.instanceOf(Error);
143+
expect(response).to.match(isNode ? /socket hang up/ : /Failed to fetch/);
144+
145+
await delay(100);
146+
147+
expect(ruleEvents.length).to.equal(0);
148+
});
149+
132150
it("should include upstream-perspective (= unmodified) response bodies", async () => {
133151
await remoteServer.forAnyRequest().thenReply(200, 'Original response body');
134152
const forwardingRule = await server.forAnyRequest().thenForwardTo(remoteServer.url, {
@@ -171,6 +189,44 @@ describe("Rule event susbcriptions", () => {
171189
expect(responseBodyEvent.rawBody.toString('utf8')).to.equal('Original response body');
172190
});
173191

192+
it("should include response bodies after beforeResponse 'close'", async () => {
193+
await remoteServer.forAnyRequest().thenReply(200, 'Original response body');
194+
const forwardingRule = await server.forAnyRequest().thenForwardTo(remoteServer.url, {
195+
beforeResponse: () => 'close'
196+
});
197+
198+
const ruleEvents: RuleEvent<any>[] = [];
199+
await server.on('rule-event', (e) => ruleEvents.push(e));
200+
201+
const response = await fetch(server.url).catch((e) => e);
202+
expect(response).to.be.instanceOf(Error);
203+
expect(response).to.match(isNode ? /socket hang up/ : /Failed to fetch/);
204+
205+
await delay(100);
206+
207+
expect(ruleEvents.length).to.equal(4);
208+
209+
const requestId = (await forwardingRule.getSeenRequests())[0].id;
210+
ruleEvents.forEach((event) => {
211+
expect(event.ruleId).to.equal(forwardingRule.id);
212+
expect(event.requestId).to.equal(requestId);
213+
});
214+
215+
expect(ruleEvents.map(e => e.eventType)).to.deep.equal([
216+
'passthrough-request-head',
217+
'passthrough-request-body',
218+
'passthrough-response-head',
219+
'passthrough-response-body'
220+
]);
221+
222+
const responseHeadEvent = ruleEvents[2].eventData;
223+
expect(responseHeadEvent.statusCode).to.equal(200); // <-- Original status
224+
225+
const responseBodyEvent = ruleEvents[3].eventData;
226+
expect(responseBodyEvent.overridden).to.equal(true);
227+
expect(responseBodyEvent.rawBody.toString('utf8')).to.equal('Original response body');
228+
});
229+
174230
it("should fire for proxied websockets", async () => {
175231
await remoteServer.forAnyWebSocket().thenPassivelyListen();
176232
const forwardingRule = await server.forAnyWebSocket().thenForwardTo(remoteServer.url);

0 commit comments

Comments
 (0)