1+ import assert from "assert" ;
12import http from "http" ;
23import { IncomingRequestCfProperties } from "@cloudflare/workers-types/experimental" ;
34import * as undici from "undici" ;
5+ import { UndiciHeaders } from "undici/types/dispatcher" ;
46import NodeWebSocket from "ws" ;
57import { CoreHeaders , DeferredPromise } from "../workers" ;
68import { Request , RequestInfo , RequestInit } from "./request" ;
79import { Response } from "./response" ;
810import { coupleWebSocket , WebSocketPair } from "./websocket" ;
911
1012const ignored = [ "transfer-encoding" , "connection" , "keep-alive" , "expect" ] ;
11- function headersFromIncomingRequest ( req : http . IncomingMessage ) : undici . Headers {
12- const entries = Object . entries ( req . headers ) . filter (
13- ( pair ) : pair is [ string , string | string [ ] ] => {
14- const [ name , value ] = pair ;
15- return ! ignored . includes ( name ) && value !== undefined ;
16- }
17- ) ;
18- return new undici . Headers ( Object . fromEntries ( entries ) ) ;
19- }
2013
2114export async function fetch (
2215 input : RequestInfo ,
@@ -40,13 +33,13 @@ export async function fetch(
4033
4134 // Normalise request headers to a format ws understands, extracting the
4235 // Sec-WebSocket-Protocol header as ws treats this differently
43- const headers : Record < string , string > = { } ;
36+ const headers = new undici . Headers ( ) ;
4437 let protocols : string [ ] | undefined ;
4538 for ( const [ key , value ] of request . headers . entries ( ) ) {
4639 if ( key . toLowerCase ( ) === "sec-websocket-protocol" ) {
4740 protocols = value . split ( "," ) . map ( ( protocol ) => protocol . trim ( ) ) ;
4841 } else {
49- headers [ key ] = value ;
42+ headers . append ( key , value ) ;
5043 }
5144 }
5245
@@ -59,13 +52,13 @@ export async function fetch(
5952 // Establish web socket connection
6053 const ws = new NodeWebSocket ( url , protocols , {
6154 followRedirects : request . redirect === "follow" ,
62- headers,
55+ headers : Object . fromEntries ( headers . entries ( ) ) ,
6356 ...rejectUnauthorized ,
6457 } ) ;
6558
6659 const responsePromise = new DeferredPromise < Response > ( ) ;
6760 ws . once ( "upgrade" , ( req ) => {
68- const headers = headersFromIncomingRequest ( req ) ;
61+ const headers = convertUndiciHeadersToStandard ( req . headers ) ;
6962 // Couple web socket with pair and resolve
7063 const [ worker , client ] = Object . values ( new WebSocketPair ( ) ) ;
7164 const couplePromise = coupleWebSocket ( ws , client ) ;
@@ -77,7 +70,7 @@ export async function fetch(
7770 responsePromise . resolve ( couplePromise . then ( ( ) => response ) ) ;
7871 } ) ;
7972 ws . once ( "unexpected-response" , ( _ , req ) => {
80- const headers = headersFromIncomingRequest ( req ) ;
73+ const headers = convertUndiciHeadersToStandard ( req . headers ) ;
8174 const response = new Response ( req , {
8275 status : req . statusCode ,
8376 headers,
@@ -99,9 +92,74 @@ export type DispatchFetch = (
9992) => Promise < Response > ;
10093
10194export type AnyHeaders = http . IncomingHttpHeaders | string [ ] ;
102- function addHeader ( /* mut */ headers : AnyHeaders , key : string , value : string ) {
103- if ( Array . isArray ( headers ) ) headers . push ( key , value ) ;
104- else headers [ key ] = value ;
95+
96+ function isIterable (
97+ headers : UndiciHeaders
98+ ) : headers is Iterable < [ string , string | string [ ] | undefined ] > {
99+ return Symbol . iterator in Object ( headers ) ;
100+ }
101+
102+ // See https://github.com/nodejs/undici/blob/main/docs/docs/api/Dispatcher.md?plain=1#L1151 for documentation
103+ function convertUndiciHeadersToStandard (
104+ headers : NonNullable < UndiciHeaders >
105+ ) : undici . Headers {
106+ // Array format: https://github.com/nodejs/undici/blob/main/docs/docs/api/Dispatcher.md?plain=1#L1157
107+ if ( Array . isArray ( headers ) ) {
108+ let name : string | undefined = undefined ;
109+ let value : string | undefined = undefined ;
110+ const standardHeaders = new undici . Headers ( ) ;
111+ for ( const element of headers ) {
112+ if ( name === undefined && value === undefined ) {
113+ name = element ;
114+ } else if ( name !== undefined && value === undefined ) {
115+ value = element ;
116+ } else if ( name !== undefined && value !== undefined ) {
117+ if ( ! ignored . includes ( name ) ) {
118+ standardHeaders . set ( name , value ) ;
119+ }
120+ name = undefined ;
121+ value = undefined ;
122+ }
123+ }
124+ // The string[] format for UndiciHeaders must have an even number of entries
125+ // https://github.com/nodejs/undici/blob/main/docs/docs/api/Dispatcher.md?plain=1#L1157
126+ assert ( name === undefined && value === undefined ) ;
127+ return standardHeaders ;
128+ } else if ( isIterable ( headers ) ) {
129+ const standardHeaders = new undici . Headers ( ) ;
130+ for ( const [ name , value ] of headers ) {
131+ if ( ! ignored . includes ( name ) ) {
132+ if ( ! value ) {
133+ continue ;
134+ }
135+ if ( typeof value === "string" ) {
136+ standardHeaders . append ( name , value ) ;
137+ } else {
138+ for ( const v of value ) {
139+ standardHeaders . append ( name , v ) ;
140+ }
141+ }
142+ }
143+ }
144+ return standardHeaders ;
145+ } else {
146+ const standardHeaders = new undici . Headers ( ) ;
147+ for ( const [ name , value ] of Object . entries ( headers ) ) {
148+ if ( ! ignored . includes ( name ) ) {
149+ if ( ! value ) {
150+ continue ;
151+ }
152+ if ( typeof value === "string" ) {
153+ standardHeaders . append ( name , value ) ;
154+ } else {
155+ for ( const v of value ) {
156+ standardHeaders . append ( name , v ) ;
157+ }
158+ }
159+ }
160+ }
161+ return standardHeaders ;
162+ }
105163}
106164
107165/**
@@ -135,22 +193,22 @@ export class DispatchFetchDispatcher extends undici.Dispatcher {
135193 }
136194
137195 addHeaders (
138- /* mut */ headers : AnyHeaders ,
196+ /* mut */ headers : undici . Headers ,
139197 path : string // Including query parameters
140198 ) {
141199 // Reconstruct URL using runtime origin specified with `dispatchFetch()`
142200 const originalURL = this . userRuntimeOrigin + path ;
143- addHeader ( headers , CoreHeaders . ORIGINAL_URL , originalURL ) ;
144- addHeader ( headers , CoreHeaders . DISABLE_PRETTY_ERROR , "true" ) ;
201+ headers . set ( CoreHeaders . ORIGINAL_URL , originalURL ) ;
202+ headers . set ( CoreHeaders . DISABLE_PRETTY_ERROR , "true" ) ;
145203 if ( this . cfBlobJson !== undefined ) {
146204 // Only add this header if a `cf` override was set
147- addHeader ( headers , CoreHeaders . CF_BLOB , this . cfBlobJson ) ;
205+ headers . set ( CoreHeaders . CF_BLOB , this . cfBlobJson ) ;
148206 }
149207 }
150208
151209 dispatch (
152210 /* mut */ options : undici . Dispatcher . DispatchOptions ,
153- handler : undici . Dispatcher . DispatchHandlers
211+ handler : undici . Dispatcher . DispatchHandler
154212 ) : boolean {
155213 let origin = String ( options . origin ) ;
156214 // The first request in a redirect chain will always match the user origin
@@ -170,9 +228,11 @@ export class DispatchFetchDispatcher extends undici.Dispatcher {
170228 path = url . pathname + url . search ;
171229 }
172230
173- // ...and add special Miniflare headers for runtime requests
174- options . headers ??= { } ;
175- this . addHeaders ( options . headers , path ) ;
231+ const headers = convertUndiciHeadersToStandard ( options . headers ?? { } ) ;
232+
233+ this . addHeaders ( headers , path ) ;
234+
235+ options . headers = headers ;
176236
177237 // Dispatch with runtime dispatcher to avoid certificate errors if using
178238 // self-signed certificate
0 commit comments