@@ -23,9 +23,9 @@ export enum HttpMethod {
2323/** HTTP request protocol. */
2424export enum HttpProtocol {
2525 /** Unencrypted HTTP protocol. */
26- HTTP ,
26+ HTTP = "HTTP" ,
2727 /** Encrypted HTTPS protocol. */
28- HTTPS
28+ HTTPS = "HTTPS"
2929}
3030
3131/** HTTP request or response headers. */
@@ -75,57 +75,170 @@ export class HttpHeaders {
7575 }
7676}
7777
78+ /** HTTP request query parameters. */
79+ export class HttpQueryParameters {
80+ private parameters : Map < string , string [ ] > ;
81+
82+ constructor ( parameters ?: { string : string | string [ ] } ) {
83+ if ( parameters ) {
84+ this . parameters = new Map < string , string [ ] > ( ) ;
85+ Object . keys ( parameters ) . forEach ( parameterName => {
86+ let values : string | string [ ] = parameters [ parameterName ] ;
87+ if ( typeof values === "string" ) {
88+ values = [ values ] ;
89+ }
90+ if ( values . length > 0 ) {
91+ this . parameters . set ( parameterName . toLowerCase ( ) , values ) ;
92+ }
93+ } ) ;
94+ } else {
95+ this . parameters = new Map < string , string [ ] > ( ) ;
96+ }
97+ }
98+
99+ /**
100+ * The first parameter value for the given parameter name, if any.
101+ *
102+ * @param parameterName The parameter name.
103+ *
104+ * @returns The first parameter value, or null if none.
105+ * @see #getAll
106+ */
107+ get ( parameterName : string ) : string | null {
108+ const values = this . parameters . get ( parameterName . toLowerCase ( ) ) ;
109+ return values && values . length > 0 ? values [ 0 ] : null ;
110+ }
111+
112+ /**
113+ * All parameter values for the given parameter name.
114+ *
115+ * @param parameterName The parameter name.
116+ *
117+ * @returns a list of parameter values, or an empty list if none
118+ * @see #get
119+ */
120+ getAll ( parameterName : string ) : string [ ] {
121+ return (
122+ this . parameters . get ( parameterName . toLowerCase ( ) ) || new Array < string > ( )
123+ ) ;
124+ }
125+ }
126+
78127/** HTTP request. */
79- export class HttpRequest {
128+ export interface HttpRequest {
80129 timestamp ?: Date ;
81- protocol : HttpProtocol ;
82130 method : HttpMethod ;
131+ protocol : HttpProtocol ;
132+ host : string ;
83133 headers : HttpHeaders ;
84134 body ?: string ;
135+ path : string ;
136+ pathname : string ;
137+ query : HttpQueryParameters ;
138+ }
85139
86- public constructor ( builder : HttpRequestBuilder ) {
87- this . timestamp = builder . timestamp ;
88- this . protocol = builder . protocol ;
89- this . method = builder . method ;
90- this . headers = builder . headers ;
91- this . body = builder . body ;
92- }
140+ /** HTTP request. */
141+ export interface HttpRequestFromPath {
142+ timestamp ?: Date ;
143+ method : HttpMethod ;
144+ protocol : HttpProtocol ;
145+ host : string ;
146+ headers : HttpHeaders ;
147+ body ?: string ;
148+ path : string ;
93149}
94150
95- export class HttpRequestBuilder {
151+ /** HTTP request. */
152+ export interface HttpRequestFromPathNameAndQuery {
96153 timestamp ?: Date ;
97- protocol ?: HttpProtocol ;
98- method ?: HttpMethod ;
99- headers ?: HttpHeaders ;
154+ method : HttpMethod ;
155+ protocol : HttpProtocol ;
156+ host : string ;
157+ headers : HttpHeaders ;
100158 body ?: string ;
159+ pathname : string ;
160+ query : { string : string | string [ ] } ;
161+ }
101162
102- withTimestamp ( timestamp : Date ) : this {
103- this . timestamp = timestamp ;
104- return this ;
105- }
163+ export class HttpRequestBuilder {
164+ static build ( requestData : HttpRequestFromPath ) : HttpRequest {
165+ const url = new URL ( "file://" + requestData . path ) ;
106166
107- withProtocol ( protocol : HttpProtocol ) : this {
108- this . protocol = protocol ;
109- return this ;
110- }
167+ const queryMap = new Object ( ) ;
168+ const queryString = url . search . substring ( 1 ) ;
169+ for ( const entry of queryString . split ( "&" ) ) {
170+ const pair = entry . split ( "=" ) ;
171+ const parameterName = decodeURIComponent ( pair [ 0 ] ) ;
172+ const parameterValue = decodeURIComponent ( pair [ 1 ] ) ;
111173
112- withMethod ( method : HttpMethod ) : this {
113- this . method = method ;
114- return this ;
115- }
174+ let existingEntry = queryMap [ parameterName ] as string [ ] ;
175+ if ( ! existingEntry ) {
176+ existingEntry = new Array < string > ( ) ;
177+ queryMap [ parameterName ] = existingEntry ;
178+ }
179+ existingEntry . push ( parameterValue ) ;
180+ }
181+ const query = new HttpQueryParameters (
182+ queryMap as { string : string | string [ ] }
183+ ) ;
116184
117- withBody ( body : string ) : this {
118- this . body = body ;
119- return this ;
185+ return {
186+ timestamp : requestData . timestamp ,
187+ method : requestData . method ,
188+ protocol : requestData . protocol ,
189+ host : requestData . host ,
190+ headers : requestData . headers ,
191+ body : requestData . body ,
192+ path : requestData . path ,
193+ pathname : url . pathname ,
194+ query : query
195+ } ;
120196 }
197+ static buildFromPathnameAndQuery (
198+ requestData : HttpRequestFromPathNameAndQuery
199+ ) : HttpRequest {
200+ let path = requestData . pathname ;
201+ if ( requestData . query ) {
202+ path += "?" ;
203+ let first = true ;
204+ for ( const key in requestData . query ) {
205+ const value = requestData . query [ key ] ;
206+ if ( value instanceof String ) {
207+ if ( first ) {
208+ first = false ;
209+ } else {
210+ path += "&" ;
211+ }
212+ path +=
213+ encodeURIComponent ( key ) +
214+ "=" +
215+ encodeURIComponent ( value . toString ( ) ) ;
216+ } else {
217+ for ( const entry of value ) {
218+ if ( first ) {
219+ first = false ;
220+ } else {
221+ path += "&" ;
222+ }
223+ path += encodeURIComponent ( key ) + "=" + encodeURIComponent ( entry ) ;
224+ }
225+ }
226+ }
227+ }
121228
122- withHeaders ( headers : HttpHeaders ) : this {
123- this . headers = headers ;
124- return this ;
125- }
229+ const query = new HttpQueryParameters ( requestData . query ) ;
126230
127- build ( ) : HttpRequest {
128- return new HttpRequest ( this ) ;
231+ return {
232+ timestamp : requestData . timestamp ,
233+ method : requestData . method ,
234+ protocol : requestData . protocol ,
235+ host : requestData . host ,
236+ headers : requestData . headers ,
237+ body : requestData . body ,
238+ path : path ,
239+ pathname : requestData . pathname ,
240+ query : query
241+ } ;
129242 }
130243}
131244
@@ -136,11 +249,9 @@ export interface HttpResponse {
136249 body ?: string ;
137250}
138251
139- export class HttpExchange {
140- constructor ( public request : HttpRequest , public response : HttpResponse ) {
141- this . request = request ;
142- this . response = response ;
143- }
252+ export interface HttpExchange {
253+ request : HttpRequest ;
254+ response : HttpResponse ;
144255}
145256
146257export class HttpExchangeReader {
@@ -158,13 +269,32 @@ export class HttpExchangeReader {
158269 HttpMethod [ ( parsedRequest . method as string ) . toUpperCase ( ) ] ;
159270 const requestHeaders : HttpHeaders = new HttpHeaders ( parsedRequest . headers ) ;
160271
161- const request = new HttpRequestBuilder ( )
162- . withTimestamp ( requestTimestamp )
163- . withProtocol ( protocol )
164- . withMethod ( method )
165- . withHeaders ( requestHeaders )
166- . withBody ( parsedRequest . body )
167- . build ( ) ;
272+ let request : HttpRequest ;
273+
274+ if ( parsedRequest . path ) {
275+ request = HttpRequestBuilder . build ( {
276+ timestamp : requestTimestamp ,
277+ method : method ,
278+ protocol : protocol ,
279+ host : parsedRequest . host ,
280+ headers : requestHeaders ,
281+ body : parsedRequest . body ,
282+ path : parsedRequest . path
283+ } ) ;
284+ } else if ( parsedRequest . pathname ) {
285+ request = HttpRequestBuilder . buildFromPathnameAndQuery ( {
286+ timestamp : requestTimestamp ,
287+ method : method ,
288+ protocol : protocol ,
289+ host : parsedRequest . host ,
290+ headers : requestHeaders ,
291+ body : parsedRequest . body ,
292+ pathname : parsedRequest . pathname ,
293+ query : parsedRequest . query
294+ } ) ;
295+ } else {
296+ throw new Error ( "Either 'path' or 'pathname' is required" ) ;
297+ }
168298
169299 const responseTimestamp = parsedResponse . timestamp
170300 ? new Date ( parsedResponse . timestamp )
@@ -180,6 +310,6 @@ export class HttpExchangeReader {
180310 body : parsedResponse . body
181311 } ;
182312
183- return new HttpExchange ( request , response ) ;
313+ return { request, response } ;
184314 }
185315}
0 commit comments