@@ -79,6 +79,51 @@ class RequestState {
79
79
this . _response = response ;
80
80
}
81
81
82
+ public headers ( kind : HeaderKind ) : NodeJS . Dict < string | string [ ] | number > {
83
+ switch ( kind ) {
84
+ case HeaderKind . REQUEST :
85
+ return this . _request . headers ;
86
+ case HeaderKind . RESPONSE :
87
+ return this . _response . getHeaders ( ) ;
88
+ case HeaderKind . REQUEST_TRAILERS :
89
+ return this . _request . trailers ;
90
+ case HeaderKind . RESPONSE_TRAILERS :
91
+ return ( this . _response as BufferedResponse ) . buffer . trailers ;
92
+ }
93
+ }
94
+
95
+ public setHeader ( kind : HeaderKind , key : string , value : string [ ] ) {
96
+ switch ( kind ) {
97
+ case HeaderKind . REQUEST :
98
+ this . _request . headers [ key ] = value ;
99
+ break ;
100
+ case HeaderKind . RESPONSE :
101
+ this . _response . setHeader ( key , value ) ;
102
+ break ;
103
+ case HeaderKind . REQUEST_TRAILERS :
104
+ this . _request . trailers [ key ] = value [ 0 ] ;
105
+ break ;
106
+ case HeaderKind . RESPONSE_TRAILERS :
107
+ ( this . _response as BufferedResponse ) . buffer . trailers [ key ] = value ;
108
+ }
109
+ }
110
+
111
+ public removeHeader ( kind : HeaderKind , key : string ) {
112
+ switch ( kind ) {
113
+ case HeaderKind . REQUEST :
114
+ delete this . _request . headers [ key ] ;
115
+ break ;
116
+ case HeaderKind . RESPONSE :
117
+ this . _response . removeHeader ( key ) ;
118
+ break ;
119
+ case HeaderKind . REQUEST_TRAILERS :
120
+ delete this . _request . trailers [ key ] ;
121
+ break ;
122
+ case HeaderKind . RESPONSE_TRAILERS :
123
+ delete ( this . _response as BufferedResponse ) . buffer . trailers [ key ] ;
124
+ }
125
+ }
126
+
82
127
public get nextCalled ( ) : boolean {
83
128
return this . _nextCalled ;
84
129
}
@@ -217,7 +262,7 @@ class HttpHandler {
217
262
218
263
// Circumvent null checking with !, setMemory must be called before
219
264
// host functions.
220
- private memory ! : Uint8Array ;
265
+ private memory ! : WebAssembly . Memory ;
221
266
222
267
public getImport ( ) {
223
268
return {
@@ -232,7 +277,9 @@ class HttpHandler {
232
277
log : this . log . bind ( this ) ,
233
278
log_enabled : this . logEnabled . bind ( this ) ,
234
279
read_body : this . readBody . bind ( this ) ,
280
+ add_header_value : this . addHeader . bind ( this ) ,
235
281
set_header_value : this . setHeader . bind ( this ) ,
282
+ remove_header : this . removeHeader . bind ( this ) ,
236
283
set_method : this . setMethod . bind ( this ) ,
237
284
set_status_code : this . setStatusCode . bind ( this ) ,
238
285
set_uri : this . setUri . bind ( this ) ,
@@ -241,7 +288,7 @@ class HttpHandler {
241
288
}
242
289
243
290
public setMemory ( memory : WebAssembly . Memory ) {
244
- this . memory = new Uint8Array ( memory . buffer ) ;
291
+ this . memory = memory ;
245
292
}
246
293
247
294
private enableFeatures ( features : number ) : number {
@@ -265,20 +312,7 @@ class HttpHandler {
265
312
bufLimit : number ,
266
313
) : bigint {
267
314
const state = stateStorage . getStore ( ) ! ;
268
- let headers : NodeJS . Dict < string | string [ ] | number > ;
269
- switch ( kind ) {
270
- case HeaderKind . REQUEST :
271
- headers = state . request . headers ;
272
- break ;
273
- case HeaderKind . RESPONSE :
274
- headers = state . response . getHeaders ( ) ;
275
- break ;
276
- case HeaderKind . REQUEST_TRAILERS :
277
- headers = state . request . trailers ;
278
- break ;
279
- case HeaderKind . RESPONSE_TRAILERS :
280
- headers = ( state . response as BufferedResponse ) . buffer . trailers ;
281
- }
315
+ const headers = state . headers ( kind ) ;
282
316
283
317
const headerNames = Object . keys ( headers ) ;
284
318
return this . writeNullTerminated ( buf , bufLimit , headerNames ) ;
@@ -296,29 +330,49 @@ class HttpHandler {
296
330
}
297
331
298
332
const state = stateStorage . getStore ( ) ! ;
299
- let headers : NodeJS . Dict < string | string [ ] | number > ;
300
- switch ( kind ) {
301
- case HeaderKind . REQUEST :
302
- headers = state . request . headers ;
303
- break ;
304
- case HeaderKind . RESPONSE :
305
- headers = state . response . getHeaders ( ) ;
306
- break ;
307
- case HeaderKind . REQUEST_TRAILERS :
308
- headers = state . request . trailers ;
309
- break ;
310
- case HeaderKind . RESPONSE_TRAILERS :
311
- headers = ( state . response as BufferedResponse ) . buffer . trailers ;
312
- }
333
+ const headers = state . headers ( kind ) ;
313
334
314
335
const n = this . mustReadString ( 'name' , name , nameLen ) . toLowerCase ( ) ;
315
- let values : string [ ] = [ ] ;
316
336
const value = headers [ n ] ;
337
+ let values : string [ ] = [ ] ;
317
338
if ( value ) {
318
339
if ( Array . isArray ( value ) ) {
319
340
values = value ;
320
341
} else {
321
- values . push ( value . toString ( ) ) ;
342
+ // NodeJS array vs join behavior is dependent on header name
343
+ // https://nodejs.org/api/http.html#messageheaders
344
+ switch ( n ) {
345
+ // TODO(anuraaga): date is not mentioned as a header where duplicates are discarded.
346
+ // However, since it has a comma inside, it seems it must be handled as a single
347
+ // string. Double-check this.
348
+ case 'date' :
349
+ case 'age' :
350
+ case 'authorization' :
351
+ case 'content-length' :
352
+ case 'content-type' :
353
+ case 'etag' :
354
+ case 'expires' :
355
+ case 'from' :
356
+ case 'host' :
357
+ case 'if-modified-since' :
358
+ case 'if-unmodified-since' :
359
+ case 'last-modified' :
360
+ case 'location' :
361
+ case 'max-forwards' :
362
+ case 'proxy-authorization' :
363
+ case 'referer' :
364
+ case 'retry-after' :
365
+ case 'server' :
366
+ case 'user-agent' :
367
+ values = [ value as string ] ;
368
+ break ;
369
+ case 'cookie' :
370
+ values = ( value as string ) . split ( '; ' ) ;
371
+ break ;
372
+ default :
373
+ values = ( value as string ) . split ( ', ' ) ;
374
+ break ;
375
+ }
322
376
}
323
377
}
324
378
@@ -391,7 +445,7 @@ class HttpHandler {
391
445
const start = state . requestBodyReadIndex ;
392
446
const end = Math . min ( start + bufLimit , body . length ) ;
393
447
const slice = body . subarray ( start , end ) ;
394
- this . memory . set ( slice , buf ) ;
448
+ this . memoryBuffer . set ( slice , buf ) ;
395
449
state . requestBodyReadIndex = end ;
396
450
if ( end === body . length ) {
397
451
return ( 1n << 32n ) | BigInt ( slice . length ) ;
@@ -411,7 +465,7 @@ class HttpHandler {
411
465
const start = state . responseBodyReadIndex ;
412
466
const end = Math . min ( start + bufLimit , body . length ) ;
413
467
const slice = body . subarray ( start , end ) ;
414
- this . memory . set ( slice , buf ) ;
468
+ this . memoryBuffer . set ( slice , buf ) ;
415
469
state . responseBodyReadIndex = end ;
416
470
if ( end === body . length ) {
417
471
return ( 1n << 32n ) | BigInt ( slice . length ) ;
@@ -457,7 +511,7 @@ class HttpHandler {
457
511
}
458
512
}
459
513
460
- private setHeader (
514
+ private addHeader (
461
515
kind : HeaderKind ,
462
516
name : number ,
463
517
nameLen : number ,
@@ -467,13 +521,47 @@ class HttpHandler {
467
521
if ( nameLen == 0 ) {
468
522
throw new Error ( 'HTTP header name cannot be empty' ) ;
469
523
}
470
- if ( kind !== HeaderKind . RESPONSE ) {
471
- throw new Error ( 'TODO: Support non-response set_header' ) ;
524
+
525
+ const n = this . mustReadString ( 'name' , name , nameLen ) ;
526
+ const v = this . mustReadString ( 'value' , value , valueLen ) ;
527
+
528
+ const headers = stateStorage . getStore ( ) ! . headers ( kind ) ;
529
+ const existing = headers [ n ] ;
530
+ let newValue : string [ ] ;
531
+ if ( existing ) {
532
+ newValue = Array . isArray ( existing )
533
+ ? existing . concat ( v )
534
+ : [ existing . toString ( ) , v ] ;
535
+ } else {
536
+ newValue = [ v ] ;
537
+ }
538
+ stateStorage . getStore ( ) ! . setHeader ( kind , n , newValue ) ;
539
+ }
540
+
541
+ private setHeader (
542
+ kind : HeaderKind ,
543
+ name : number ,
544
+ nameLen : number ,
545
+ value : number ,
546
+ valueLen : number ,
547
+ ) : void {
548
+ if ( nameLen == 0 ) {
549
+ throw new Error ( 'HTTP header name cannot be empty' ) ;
472
550
}
473
551
474
552
const n = this . mustReadString ( 'name' , name , nameLen ) ;
475
553
const v = this . mustReadString ( 'value' , value , valueLen ) ;
476
- stateStorage . getStore ( ) ?. response . setHeader ( n , v ) ;
554
+
555
+ stateStorage . getStore ( ) ! . setHeader ( kind , n , [ v ] ) ;
556
+ }
557
+
558
+ private removeHeader ( kind : HeaderKind , name : number , nameLen : number ) : void {
559
+ if ( nameLen == 0 ) {
560
+ throw new Error ( 'HTTP header name cannot be empty' ) ;
561
+ }
562
+
563
+ const n = this . mustReadString ( 'name' , name , nameLen ) ;
564
+ stateStorage . getStore ( ) ?. removeHeader ( kind , n ) ;
477
565
}
478
566
479
567
private setStatusCode ( statusCode : number ) : void {
@@ -510,13 +598,15 @@ class HttpHandler {
510
598
}
511
599
512
600
if (
513
- offset >= this . memory . length ||
514
- offset + byteCount >= this . memory . length
601
+ offset >= this . memoryBuffer . length ||
602
+ offset + byteCount >= this . memoryBuffer . length
515
603
) {
516
- throw new Error ( `out of memory reading ${ fieldName } ` ) ;
604
+ throw new Error (
605
+ `out of memory reading ${ fieldName } , offset: ${ offset } , byteCount: ${ byteCount } ` ,
606
+ ) ;
517
607
}
518
608
519
- return this . memory . slice ( offset , offset + byteCount ) ;
609
+ return this . memoryBuffer . slice ( offset , offset + byteCount ) ;
520
610
}
521
611
522
612
private writeStringIfUnderLimit (
@@ -533,7 +623,7 @@ class HttpHandler {
533
623
return vLen ;
534
624
}
535
625
536
- this . memory . set ( v , offset ) ;
626
+ this . memoryBuffer . set ( v , offset ) ;
537
627
return vLen ;
538
628
}
539
629
@@ -559,14 +649,18 @@ class HttpHandler {
559
649
let offset = 0 ;
560
650
for ( const s of encodedInput ) {
561
651
const sLen = s . length ;
562
- this . memory . set ( s , buf + offset ) ;
652
+ this . memoryBuffer . set ( s , buf + offset ) ;
563
653
offset += sLen ;
564
- this . memory [ buf + offset ] = 0 ;
654
+ this . memoryBuffer [ buf + offset ] = 0 ;
565
655
offset ++ ;
566
656
}
567
657
568
658
return countLen ;
569
659
}
660
+
661
+ private get memoryBuffer ( ) : Uint8Array {
662
+ return new Uint8Array ( this . memory . buffer ) ;
663
+ }
570
664
}
571
665
572
666
const host = ( wasi : WASI , httpHandler : HttpHandler ) => {
@@ -611,18 +705,23 @@ export default async (options: Options) => {
611
705
}
612
706
const state = new RequestState ( req , res ) ;
613
707
stateStorage . run ( state , ( ) => {
614
- const ctxNext = handleRequest ( ) ;
615
- if ( ( ctxNext & 0x1n ) !== 0x1n ) {
616
- // wasm populated a response so end it.
617
- res . end ( ) ;
618
- } else {
619
- next ( ) ;
620
- state . nextCalled = true ;
621
- const ctx = Number ( ctxNext >> 32n ) ;
622
- handleResponse ( ctx ) ;
623
- }
624
- if ( mwState . features . has ( Feature . BUFFER_RESPONSE ) ) {
625
- ( res as BufferedResponse ) . buffer . release ( ) ;
708
+ try {
709
+ const ctxNext = handleRequest ( ) ;
710
+ if ( ( ctxNext & 0x1n ) !== 0x1n ) {
711
+ // wasm populated a response so end it.
712
+ res . end ( ) ;
713
+ } else {
714
+ next ( ) ;
715
+ state . nextCalled = true ;
716
+ const ctx = Number ( ctxNext >> 32n ) ;
717
+ handleResponse ( ctx ) ;
718
+ }
719
+ if ( mwState . features . has ( Feature . BUFFER_RESPONSE ) ) {
720
+ ( res as BufferedResponse ) . buffer . release ( ) ;
721
+ }
722
+ } catch ( e ) {
723
+ console . log ( 'exception in wasm middleware' , e ) ;
724
+ throw e ;
626
725
}
627
726
} ) ;
628
727
} ) ;
0 commit comments