@@ -12,6 +12,7 @@ const {
12
12
} = require ( './util' )
13
13
const { webidl } = require ( './webidl' )
14
14
const assert = require ( 'node:assert' )
15
+ const { sort } = require ( './sort' )
15
16
16
17
const kHeadersMap = Symbol ( 'headers map' )
17
18
const kHeadersSortedMap = Symbol ( 'headers map sorted' )
@@ -120,6 +121,10 @@ function appendHeader (headers, name, value) {
120
121
// privileged no-CORS request headers from headers
121
122
}
122
123
124
+ function compareHeaderName ( a , b ) {
125
+ return a [ 0 ] < b [ 0 ] ? - 1 : 1
126
+ }
127
+
123
128
class HeadersList {
124
129
/** @type {[string, string][]|null } */
125
130
cookies = null
@@ -237,7 +242,7 @@ class HeadersList {
237
242
238
243
* [ Symbol . iterator ] ( ) {
239
244
// use the lowercased name
240
- for ( const [ name , { value } ] of this [ kHeadersMap ] ) {
245
+ for ( const { 0 : name , 1 : { value } } of this [ kHeadersMap ] ) {
241
246
yield [ name , value ]
242
247
}
243
248
}
@@ -253,6 +258,79 @@ class HeadersList {
253
258
254
259
return headers
255
260
}
261
+
262
+ // https://fetch.spec.whatwg.org/#convert-header-names-to-a-sorted-lowercase-set
263
+ toSortedArray ( ) {
264
+ const size = this [ kHeadersMap ] . size
265
+ const array = new Array ( size )
266
+ // In most cases, you will use the fast-path.
267
+ // fast-path: Use binary insertion sort for small arrays.
268
+ if ( size <= 32 ) {
269
+ if ( size === 0 ) {
270
+ // If empty, it is an empty array. To avoid the first index assignment.
271
+ return array
272
+ }
273
+ // Improve performance by unrolling loop and avoiding double-loop.
274
+ // Double-loop-less version of the binary insertion sort.
275
+ const iterator = this [ kHeadersMap ] [ Symbol . iterator ] ( )
276
+ const firstValue = iterator . next ( ) . value
277
+ // set [name, value] to first index.
278
+ array [ 0 ] = [ firstValue [ 0 ] , firstValue [ 1 ] . value ]
279
+ // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
280
+ // 3.2.2. Assert: value is non-null.
281
+ assert ( firstValue [ 1 ] . value !== null )
282
+ for (
283
+ let i = 1 , j = 0 , right = 0 , left = 0 , pivot = 0 , x , value ;
284
+ i < size ;
285
+ ++ i
286
+ ) {
287
+ // get next value
288
+ value = iterator . next ( ) . value
289
+ // set [name, value] to current index.
290
+ x = array [ i ] = [ value [ 0 ] , value [ 1 ] . value ]
291
+ // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
292
+ // 3.2.2. Assert: value is non-null.
293
+ assert ( x [ 1 ] !== null )
294
+ left = 0
295
+ right = i
296
+ // binary search
297
+ while ( left < right ) {
298
+ // middle index
299
+ pivot = left + ( ( right - left ) >> 1 )
300
+ // compare header name
301
+ if ( array [ pivot ] [ 0 ] <= x [ 0 ] ) {
302
+ left = pivot + 1
303
+ } else {
304
+ right = pivot
305
+ }
306
+ }
307
+ if ( i !== pivot ) {
308
+ j = i
309
+ while ( j > left ) {
310
+ array [ j ] = array [ -- j ]
311
+ }
312
+ array [ left ] = x
313
+ }
314
+ }
315
+ /* c8 ignore next 4 */
316
+ if ( ! iterator . next ( ) . done ) {
317
+ // This is for debugging and will never be called.
318
+ throw new TypeError ( 'Unreachable' )
319
+ }
320
+ return array
321
+ } else {
322
+ // This case would be a rare occurrence.
323
+ // slow-path: fallback
324
+ let i = 0
325
+ for ( const { 0 : name , 1 : { value } } of this [ kHeadersMap ] ) {
326
+ array [ i ++ ] = [ name , value ]
327
+ // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
328
+ // 3.2.2. Assert: value is non-null.
329
+ assert ( value !== null )
330
+ }
331
+ return sort ( array , compareHeaderName )
332
+ }
333
+ }
256
334
}
257
335
258
336
// https://fetch.spec.whatwg.org/#headers-class
@@ -454,27 +532,19 @@ class Headers {
454
532
455
533
// 2. Let names be the result of convert header names to a sorted-lowercase
456
534
// set with all the names of the headers in list.
457
- const names = [ ...this [ kHeadersList ] ]
458
- const namesLength = names . length
459
- if ( namesLength <= 16 ) {
460
- // Note: Use insertion sort for small arrays.
461
- for ( let i = 1 , value , j = 0 ; i < namesLength ; ++ i ) {
462
- value = names [ i ]
463
- for ( j = i - 1 ; j >= 0 ; -- j ) {
464
- if ( names [ j ] [ 0 ] <= value [ 0 ] ) break
465
- names [ j + 1 ] = names [ j ]
466
- }
467
- names [ j + 1 ] = value
468
- }
469
- } else {
470
- names . sort ( ( a , b ) => a [ 0 ] < b [ 0 ] ? - 1 : 1 )
471
- }
535
+ const names = this [ kHeadersList ] . toSortedArray ( )
472
536
473
537
const cookies = this [ kHeadersList ] . cookies
474
538
539
+ // fast-path
540
+ if ( cookies === null ) {
541
+ // Note: The non-null assertion of value has already been done by `HeadersList#toSortedArray`
542
+ return ( this [ kHeadersList ] [ kHeadersSortedMap ] = names )
543
+ }
544
+
475
545
// 3. For each name of names:
476
- for ( let i = 0 ; i < namesLength ; ++ i ) {
477
- const [ name , value ] = names [ i ]
546
+ for ( let i = 0 ; i < names . length ; ++ i ) {
547
+ const { 0 : name , 1 : value } = names [ i ]
478
548
// 1. If name is `set-cookie`, then:
479
549
if ( name === 'set-cookie' ) {
480
550
// 1. Let values be a list of all values of headers in list whose name
@@ -491,17 +561,15 @@ class Headers {
491
561
// 1. Let value be the result of getting name from list.
492
562
493
563
// 2. Assert: value is non-null.
494
- assert ( value !== null )
564
+ // Note: This operation was done by `HeadersList#toSortedArray`.
495
565
496
566
// 3. Append (name, value) to headers.
497
567
headers . push ( [ name , value ] )
498
568
}
499
569
}
500
570
501
- this [ kHeadersList ] [ kHeadersSortedMap ] = headers
502
-
503
571
// 4. Return headers.
504
- return headers
572
+ return ( this [ kHeadersList ] [ kHeadersSortedMap ] = headers )
505
573
}
506
574
507
575
[ Symbol . for ( 'nodejs.util.inspect.custom' ) ] ( ) {
@@ -546,6 +614,8 @@ webidl.converters.HeadersInit = function (V) {
546
614
547
615
module . exports = {
548
616
fill,
617
+ // for test.
618
+ compareHeaderName,
549
619
Headers,
550
620
HeadersList
551
621
}
0 commit comments