Skip to content

Commit 0762f07

Browse files
committed
Add trailer data to request & response subscription events
1 parent c3d88d1 commit 0762f07

File tree

8 files changed

+213
-79
lines changed

8 files changed

+213
-79
lines changed

src/admin/mockttp-schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ export const MockttpSchema = gql`
159159
rawHeaders: Json!
160160
161161
body: Buffer!
162+
rawTrailers: Json!
162163
}
163164
164165
type AbortedRequest {
@@ -195,6 +196,7 @@ export const MockttpSchema = gql`
195196
headers: Json!
196197
rawHeaders: Json!
197198
body: Buffer!
199+
rawTrailers: Json!
198200
}
199201
200202
type WebSocketMessage {

src/client/mockttp-admin-request-builder.ts

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ function normalizeHttpMessage(message: any, event?: SubscribableEvent) {
3838
message.rawHeaders = objectHeadersToRaw(message.headers);
3939
}
4040

41+
if (message.rawTrailers) {
42+
message.rawTrailers = JSON.parse(message.rawTrailers);
43+
message.trailers = rawHeadersToObject(message.rawTrailers);
44+
} else if (message.rawHeaders && message.body) { // HTTP events with bodies should have trailers
45+
message.rawTrailers = [];
46+
message.trailers = {};
47+
}
48+
4149
if (message.body !== undefined) {
4250
// Body is serialized as the raw encoded buffer in base64
4351
message.body = buildBodyReader(Buffer.from(message.body, 'base64'), message.headers);
@@ -218,128 +226,134 @@ export class MockttpAdminRequestBuilder {
218226
const query = {
219227
'request-initiated': gql`subscription OnRequestInitiated {
220228
requestInitiated {
221-
id,
222-
protocol,
223-
method,
224-
url,
225-
path,
226-
${this.schema.asOptionalField('InitiatedRequest', 'remoteIpAddress')},
227-
${this.schema.asOptionalField('InitiatedRequest', 'remotePort')},
228-
hostname,
229+
id
230+
protocol
231+
method
232+
url
233+
path
234+
${this.schema.asOptionalField('InitiatedRequest', 'remoteIpAddress')}
235+
${this.schema.asOptionalField('InitiatedRequest', 'remotePort')}
236+
hostname
229237
230238
${this.schema.typeHasField('InitiatedRequest', 'rawHeaders')
231239
? 'rawHeaders'
232240
: 'headers'
233241
}
234-
timingEvents,
235-
httpVersion,
242+
timingEvents
243+
httpVersion
236244
${this.schema.asOptionalField('InitiatedRequest', 'tags')}
237245
}
238246
}`,
239247
request: gql`subscription OnRequest {
240248
requestReceived {
241-
id,
249+
id
242250
${this.schema.asOptionalField('Request', 'matchedRuleId')}
243-
protocol,
244-
method,
245-
url,
246-
path,
247-
${this.schema.asOptionalField('Request', 'remoteIpAddress')},
248-
${this.schema.asOptionalField('Request', 'remotePort')},
249-
hostname,
251+
protocol
252+
method
253+
url
254+
path
255+
${this.schema.asOptionalField('Request', 'remoteIpAddress')}
256+
${this.schema.asOptionalField('Request', 'remotePort')}
257+
hostname
250258
251259
${this.schema.typeHasField('Request', 'rawHeaders')
252260
? 'rawHeaders'
253261
: 'headers'
254262
}
255263
256-
body,
264+
body
265+
${this.schema.asOptionalField('Request', 'rawTrailers')}
266+
257267
${this.schema.asOptionalField('Request', 'timingEvents')}
258268
${this.schema.asOptionalField('Request', 'httpVersion')}
259269
${this.schema.asOptionalField('Request', 'tags')}
260270
}
261271
}`,
262272
response: gql`subscription OnResponse {
263273
responseCompleted {
264-
id,
265-
statusCode,
266-
statusMessage,
274+
id
275+
statusCode
276+
statusMessage
267277
268278
${this.schema.typeHasField('Response', 'rawHeaders')
269279
? 'rawHeaders'
270280
: 'headers'
271281
}
272282
273-
body,
283+
body
284+
${this.schema.asOptionalField('Response', 'rawTrailers')}
285+
274286
${this.schema.asOptionalField('Response', 'timingEvents')}
275287
${this.schema.asOptionalField('Response', 'tags')}
276288
}
277289
}`,
278290
'websocket-request': gql`subscription OnWebSocketRequest {
279291
webSocketRequest {
280-
id,
281-
matchedRuleId,
282-
protocol,
283-
method,
284-
url,
285-
path,
286-
remoteIpAddress,
287-
remotePort,
288-
hostname,
292+
id
293+
matchedRuleId
294+
protocol
295+
method
296+
url
297+
path
298+
remoteIpAddress
299+
remotePort
300+
hostname
289301
290-
rawHeaders,
291-
body,
302+
rawHeaders
303+
body
304+
${this.schema.asOptionalField('Request', 'rawTrailers')}
292305
293-
timingEvents,
294-
httpVersion,
306+
timingEvents
307+
httpVersion
295308
tags
296309
}
297310
}`,
298311
'websocket-accepted': gql`subscription OnWebSocketAccepted {
299312
webSocketAccepted {
300-
id,
301-
statusCode,
302-
statusMessage,
313+
id
314+
statusCode
315+
statusMessage
303316
304-
rawHeaders,
305-
body,
317+
rawHeaders
318+
body
319+
${this.schema.asOptionalField('Response', 'rawTrailers')}
306320
307-
timingEvents,
321+
timingEvents
308322
tags
309323
}
310324
}`,
311325
'websocket-message-received': gql`subscription OnWebSocketMessageReceived {
312326
webSocketMessageReceived {
313-
streamId,
314-
direction,
315-
content,
316-
isBinary,
317-
eventTimestamp,
327+
streamId
328+
direction
329+
content
330+
isBinary
331+
eventTimestamp
318332
319-
timingEvents,
333+
timingEvents
320334
tags
321335
}
322336
}`,
323337
'websocket-message-sent': gql`subscription OnWebSocketMessageSent {
324338
webSocketMessageSent {
325-
streamId,
326-
direction,
327-
content,
328-
isBinary,
329-
eventTimestamp,
339+
streamId
340+
direction
341+
content
342+
isBinary
343+
eventTimestamp
330344
331-
timingEvents,
345+
timingEvents
332346
tags
333347
}
334348
}`,
335349
'websocket-close': gql`subscription OnWebSocketClose {
336350
webSocketClose {
337-
streamId,
351+
streamId
338352
339-
closeCode,
340-
closeReason,
353+
closeCode
354+
closeReason
341355
342-
timingEvents,
356+
timingEvents
343357
tags
344358
}
345359
}`,
@@ -417,8 +431,8 @@ export class MockttpAdminRequestBuilder {
417431
: 'headers'
418432
}
419433
420-
${this.schema.asOptionalField('ClientErrorRequest', 'remoteIpAddress')},
421-
${this.schema.asOptionalField('ClientErrorRequest', 'remotePort')},
434+
${this.schema.asOptionalField('ClientErrorRequest', 'remoteIpAddress')}
435+
${this.schema.asOptionalField('ClientErrorRequest', 'remotePort')}
422436
}
423437
response {
424438
id
@@ -433,6 +447,7 @@ export class MockttpAdminRequestBuilder {
433447
}
434448
435449
body
450+
${this.schema.asOptionalField('Response', 'rawTrailers')}
436451
}
437452
}
438453
}`,

src/rules/requests/request-rule.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ export class RequestRule implements RequestRule {
9696
const initiatedRequest = buildInitiatedRequest(req);
9797
return {
9898
...initiatedRequest,
99-
body: buildBodyReader(Buffer.from([]), req.headers)
99+
body: buildBodyReader(Buffer.from([]), req.headers),
100+
rawTrailers: [],
101+
trailers: {}
100102
};
101103
})
102104
);

src/server/mockttp-server.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import {
2626
WebSocketMessage,
2727
WebSocketClose,
2828
TlsPassthroughEvent,
29-
RuleEvent
29+
RuleEvent,
30+
RawTrailers
3031
} from "../types";
3132
import { DestroyableServer } from "destroyable-server";
3233
import {
@@ -614,10 +615,21 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
614615
makePropertyWritable(req, 'headers');
615616
makePropertyWritable(req, 'rawHeaders');
616617

618+
let rawTrailers: RawTrailers | undefined;
619+
Object.defineProperty(req, 'rawTrailers', {
620+
get: () => rawTrailers,
621+
set: (flatRawTrailers) => {
622+
rawTrailers = flatRawTrailers
623+
? pairFlatRawHeaders(flatRawTrailers)
624+
: undefined;
625+
}
626+
});
627+
617628
return Object.assign(req, {
618629
id,
619630
headers,
620631
rawHeaders,
632+
rawTrailers, // Just makes the type happy - really managed by property above
621633
remoteIpAddress: req.socket.remoteAddress,
622634
remotePort: req.socket.remotePort,
623635
timingEvents,
@@ -967,6 +979,8 @@ ${await this.suggestRule(request)}`
967979
...commonParams,
968980
headers: { 'connection': 'close' },
969981
rawHeaders: [['Connection', 'close']],
982+
trailers: {},
983+
rawTrailers: [],
970984
statusCode:
971985
isHeaderOverflow
972986
? 431

src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface Trailers {
4242
}
4343

4444
export type RawHeaders = Array<[key: string, value: string]>;
45+
export type RawTrailers = RawHeaders; // Just a convenient alias
4546

4647
export interface Request {
4748
id: string;
@@ -147,6 +148,7 @@ export interface TlsFailureTimingEvents extends TlsTimingEvents {
147148
// Internal representation of an ongoing HTTP request whilst it's being processed
148149
export interface OngoingRequest extends Request, EventEmitter {
149150
body: OngoingBody;
151+
rawTrailers?: RawHeaders;
150152
}
151153

152154
export interface OngoingBody {
@@ -229,6 +231,8 @@ export interface AbortedRequest extends InitiatedRequest {
229231
// Internal & external representation of a fully completed HTTP request
230232
export interface CompletedRequest extends Request {
231233
body: CompletedBody;
234+
rawTrailers: RawTrailers;
235+
trailers: Trailers;
232236
}
233237

234238
export interface TimingEvents {
@@ -253,6 +257,7 @@ export interface OngoingResponse extends http.ServerResponse {
253257
getHeaders(): Headers;
254258
getRawHeaders(): RawHeaders;
255259
body: OngoingBody;
260+
getRawTrailers(): RawTrailers;
256261
timingEvents: TimingEvents;
257262
tags: string[];
258263
}
@@ -264,6 +269,8 @@ export interface CompletedResponse {
264269
headers: Headers;
265270
rawHeaders: RawHeaders;
266271
body: CompletedBody;
272+
rawTrailers: RawTrailers;
273+
trailers: Trailers;
267274
timingEvents: TimingEvents;
268275
tags: string[];
269276
}

0 commit comments

Comments
 (0)