@@ -15,6 +15,8 @@ import {
1515 MockttpBreakpointedResponse ,
1616 InputCompletedRequest ,
1717 MockttpBreakpointResponseResult ,
18+ InputRuleEventDataMap ,
19+ RawHeaders
1820} from "../../types" ;
1921import {
2022 fakeBuffer ,
@@ -25,8 +27,10 @@ import { UnreachableCheck } from '../../util/error';
2527import { lazyObservablePromise , ObservablePromise , observablePromise } from "../../util/observable" ;
2628import {
2729 asHeaderArray ,
28- getHeaderValue
30+ getHeaderValue ,
31+ getHeaderValues
2932} from '../../util/headers' ;
33+ import { ParsedUrl } from '../../util/url' ;
3034
3135import { logError } from '../../errors' ;
3236
@@ -50,8 +54,14 @@ import {
5054 getResponseBreakpoint ,
5155 getDummyResponseBreakpoint
5256} from './exchange-breakpoint' ;
57+ import { UpstreamHttpExchange } from './upstream-exchange' ;
5358
54- function tryParseUrl ( request : InputRequest ) : ( URL & { parseable : true } ) | undefined {
59+ export type HttpVersion = 2 | 1 ;
60+ export function parseHttpVersion ( version : string | undefined ) : HttpVersion {
61+ return version === '2.0' ? 2 : 1 ;
62+ }
63+
64+ function tryParseUrl ( request : InputRequest ) : ParsedUrl | undefined {
5565 try {
5666 return Object . assign (
5767 new URL ( request . url , `${ request . protocol } ://${ request . hostname || 'unknown.invalid' } ` ) ,
@@ -64,7 +74,7 @@ function tryParseUrl(request: InputRequest): (URL & { parseable: true }) | undef
6474 }
6575}
6676
67- function getFallbackUrl ( request : InputRequest ) : URL & { parseable : false } {
77+ function getFallbackUrl ( request : InputRequest ) : ParsedUrl {
6878 try {
6979 return Object . assign (
7080 new URL ( "/[unparseable]" , `${ request . protocol } ://${ request . hostname || 'unknown.invalid' } ` ) ,
@@ -106,8 +116,8 @@ function addResponseMetadata(response: InputResponse): HtkResponse {
106116export class HttpBody implements MessageBody {
107117
108118 constructor (
109- message : InputMessage ,
110- headers : Headers
119+ message : InputMessage | { body : Uint8Array } ,
120+ headers : Headers | RawHeaders
111121 ) {
112122 if ( ! ( 'body' in message ) || ! message . body ) {
113123 this . _encoded = stringToBuffer ( "" ) ;
@@ -118,7 +128,7 @@ export class HttpBody implements MessageBody {
118128 this . _decoded = message . body . decoded ;
119129 }
120130
121- this . _contentEncoding = asHeaderArray ( headers [ 'content-encoding' ] ) ;
131+ this . _contentEncoding = asHeaderArray ( getHeaderValues ( headers , 'content-encoding' ) ) ;
122132 }
123133
124134 private _contentEncoding : string [ ] ;
@@ -179,17 +189,61 @@ export class HttpBody implements MessageBody {
179189 }
180190}
181191
182- export type CompletedRequest = Omit < HttpExchange , 'request' > & {
192+ export type CompletedRequest = Omit < ViewableHttpExchange , 'request' > & {
183193 matchedRule : { id : string , handlerRype : HandlerClassKey } | false
184194} ;
185- export type CompletedExchange = Omit < HttpExchange , 'response' > & {
195+ export type CompletedExchange = Omit < ViewableHttpExchange , 'response' > & {
186196 response : HtkResponse | 'aborted'
187197} ;
188- export type SuccessfulExchange = Omit < HttpExchange , 'response' > & {
198+ export type SuccessfulExchange = Omit < ViewableHttpExchange , 'response' > & {
189199 response : HtkResponse
190200} ;
191201
192- export class HttpExchange extends HTKEventBase {
202+ /**
203+ * HttpExchanges actually come in two types: downstream (client input to Mockttp)
204+ * and upstream (Mockttp extra data for what we really forwarded - e.g. after transform).
205+ * The events actually stored in the event store's list are always downstream
206+ * exchanges, but in many cases elsewhere either can be provided.
207+ *
208+ * Both define the exact same interface, and various higher-layer components
209+ * may switch which is passed to the final view components depending on user
210+ * configuration. Any code that just reads exchange data (i.e. which doesn't
211+ * update it from events, create/import exchanges, handle breakpoints, etc)
212+ * should generally use the ViewableHttpExchange readonly interface where possible.
213+ */
214+ export interface ViewableHttpExchange extends HTKEventBase {
215+
216+ get downstream ( ) : HttpExchange ;
217+ /**
218+ * Upstream is set if forwarded, but otherwise undefined
219+ */
220+ get upstream ( ) : UpstreamHttpExchange | undefined ;
221+
222+ get request ( ) : HtkRequest ;
223+ get response ( ) : HtkResponse | 'aborted' | undefined ;
224+ get abortMessage ( ) : string | undefined ;
225+ get api ( ) : ApiExchange | undefined ;
226+
227+ get httpVersion ( ) : 1 | 2 ;
228+ get matchedRule ( ) : { id : string , handlerStepTypes : HandlerClassKey [ ] } | false | undefined ;
229+ get tags ( ) : string [ ] ;
230+ get timingEvents ( ) : TimingEvents ;
231+
232+ isHttp ( ) : this is ViewableHttpExchange ;
233+ isCompletedRequest ( ) : this is CompletedRequest ;
234+ isCompletedExchange ( ) : this is CompletedExchange ;
235+ isSuccessfulExchange ( ) : this is SuccessfulExchange ;
236+ hasRequestBody ( ) : this is CompletedRequest ;
237+ hasResponseBody ( ) : this is SuccessfulExchange ;
238+
239+ get requestBreakpoint ( ) : RequestBreakpoint | undefined ;
240+ get responseBreakpoint ( ) : ResponseBreakpoint | undefined ;
241+
242+ hideErrors : boolean ;
243+
244+ }
245+
246+ export class HttpExchange extends HTKEventBase implements ViewableHttpExchange {
193247
194248 constructor ( apiStore : ApiStore , request : InputRequest ) {
195249 super ( ) ;
@@ -217,9 +271,13 @@ export class HttpExchange extends HTKEventBase {
217271 this . _apiMetadataPromise = apiStore . getApi ( this . request ) ;
218272 }
219273
220- public readonly request : HtkRequest ;
221274 public readonly id : string ;
222275
276+ public readonly request : HtkRequest ;
277+
278+ public readonly downstream = this ;
279+ public upstream : UpstreamHttpExchange | undefined ;
280+
223281 @observable
224282 // Undefined initially, defined for completed requests, false for 'not available'
225283 public matchedRule : { id : string , handlerStepTypes : HandlerClassKey [ ] } | false | undefined ;
@@ -232,7 +290,7 @@ export class HttpExchange extends HTKEventBase {
232290
233291 @computed
234292 get httpVersion ( ) {
235- return this . request . httpVersion === '2.0' ? 2 : 1 ;
293+ return parseHttpVersion ( this . request . httpVersion ) ;
236294 }
237295
238296 isHttp ( ) : this is HttpExchange {
@@ -261,7 +319,7 @@ export class HttpExchange extends HTKEventBase {
261319 }
262320
263321 @observable
264- public readonly timingEvents : Partial < TimingEvents > ; // May be {} if using an old server (<0.1.7)
322+ public readonly timingEvents : TimingEvents ;
265323
266324 @observable . ref
267325 public response : HtkResponse | 'aborted' | undefined ;
@@ -295,6 +353,20 @@ export class HttpExchange extends HTKEventBase {
295353 this . tags = _ . union ( this . tags , request . tags ) ;
296354 }
297355
356+ updateFromUpstreamRequestHead ( head : InputRuleEventDataMap [ 'passthrough-request-head' ] ) {
357+ if ( ! this . upstream ) {
358+ this . upstream = new UpstreamHttpExchange ( this ) ;
359+ }
360+ this . upstream . updateWithRequestHead ( head ) ;
361+ }
362+
363+ updateFromUpstreamRequestBody ( body : InputRuleEventDataMap [ 'passthrough-request-body' ] ) {
364+ if ( ! this . upstream ) {
365+ this . upstream = new UpstreamHttpExchange ( this ) ;
366+ }
367+ this . upstream . updateWithRequestBody ( body ) ;
368+ }
369+
298370 markAborted ( request : InputFailedRequest ) {
299371 this . response = 'aborted' ;
300372 this . searchIndex += '\naborted' ;
@@ -357,6 +429,10 @@ export class HttpExchange extends HTKEventBase {
357429 this . response . cache . clear ( ) ;
358430 this . response . body . cleanup ( ) ;
359431 }
432+
433+ if ( this . upstream ) {
434+ this . upstream . cleanup ( ) ;
435+ }
360436 }
361437
362438 // API metadata:
@@ -366,7 +442,7 @@ export class HttpExchange extends HTKEventBase {
366442
367443 // Parsed API info for this specific request, loaded & parsed lazily, only if it's used
368444 @observable . ref
369- private _apiPromise = lazyObservablePromise ( async ( ) => {
445+ private _apiPromise = lazyObservablePromise ( async ( ) : Promise < ApiExchange | undefined > => {
370446 const apiMetadata = await this . _apiMetadataPromise ;
371447
372448 if ( apiMetadata ) {
0 commit comments