Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fetch: replace HeadersList Array inheritance to Map #1309

Merged
merged 12 commits into from
Apr 9, 2022
87 changes: 48 additions & 39 deletions lib/fetch/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ class HeadersList {
this[kHeadersSortedMap] = init[kHeadersSortedMap]
} else {
this[kHeadersMap] = new Map(init)
this[kHeadersSortedMap] = undefined
this[kHeadersSortedMap] = null
}
}

append (name, value) {
this[kHeadersSortedMap] = null
ronag marked this conversation as resolved.
Show resolved Hide resolved

const normalizedName = normalizeAndValidateHeaderName(name)
const normalizedValue = normalizeAndValidateHeaderValue(name, value)

Expand All @@ -98,11 +100,19 @@ class HeadersList {
this[kHeadersMap].set(normalizedName, `${exists}, ${normalizedValue}`)
} else {
this[kHeadersMap].set(normalizedName, `${normalizedValue}`)
this[kHeadersSortedMap] = undefined
}
}

set (name, value) {
this[kHeadersSortedMap] = null

const normalizedName = normalizeAndValidateHeaderName(name)
return this[kHeadersMap].set(normalizedName, value)
}

delete (name) {
this[kHeadersSortedMap] = null

const normalizedName = normalizeAndValidateHeaderName(name)
return this[kHeadersMap].delete(normalizedName)
}
Expand All @@ -117,24 +127,20 @@ class HeadersList {
return this[kHeadersMap].has(normalizedName)
}

sortMap () {
this[kHeadersSortedMap] = new Map([...this[kHeadersMap]].sort((a, b) => a[0] < b[0] ? -1 : 1))
keys() {
return this[kHeadersMap].keys()
}

// bypass normalization
set (key, val) {
this[kHeadersSortedMap] = undefined
return this[kHeadersMap].set(key, val)
values () {
return this[kHeadersMap].values()
}

* [Symbol.iterator] () {
if (!this[kHeadersSortedMap]) {
this.sortMap()
}
entries () {
return this[kHeadersMap].entries()
}

for (const [key, val] of this[kHeadersSortedMap].entries()) {
yield [key, val]
}
[Symbol.iterator] () {
return this[kHeadersMap][Symbol.iterator]()
}
}

Expand Down Expand Up @@ -276,56 +282,60 @@ class Headers {
)
}

const normalizedName = normalizeAndValidateHeaderName(String(args[0]))

if (this[kGuard] === 'immutable') {
throw new TypeError('immutable')
} else if (
this[kGuard] === 'request' &&
forbiddenHeaderNames.includes(normalizedName)
forbiddenHeaderNames.includes(String(args[0]).toLocaleLowerCase())
) {
return
} else if (this[kGuard] === 'request-no-cors') {
// TODO
} else if (
this[kGuard] === 'response' &&
forbiddenResponseHeaderNames.includes(normalizedName)
forbiddenResponseHeaderNames.includes(String(args[0]).toLocaleLowerCase())
) {
return
}

return this[kHeadersList].set(String(args[0]), String(args[1]))
}

// The value pairs to iterate over are the return value of running sort
// and combine with this’s header list.
_getHeadersListIterable () {
if (!this[kHeadersList][kHeadersSortedMap]) {
this[kHeadersList].sortMap()
get [kHeadersSortedMap] () {
this[kHeadersList][kHeadersSortedMap] ??= new Map([...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1))
return this[kHeadersList][kHeadersSortedMap]
}

keys () {
if (!(this instanceof Headers)) {
throw new TypeError('Illegal invocation')
}

return this[kHeadersList][kHeadersSortedMap]
return this[kHeadersSortedMap].keys()
}

* keys () {
const sortedHeaders = this._getHeadersListIterable()
for (const key of sortedHeaders.keys()) {
yield key
values () {
if (!(this instanceof Headers)) {
throw new TypeError('Illegal invocation')
}

return this[kHeadersSortedMap].values()
}

* values () {
const sortedHeaders = this._getHeadersListIterable()
for (const val of sortedHeaders.values()) {
yield val
entries () {
if (!(this instanceof Headers)) {
throw new TypeError('Illegal invocation')
}

return this[kHeadersSortedMap].entries()
}

* entries () {
const sortedHeaders = this._getHeadersListIterable()
for (const [key, val] of sortedHeaders) {
yield [key, val]
[Symbol.iterator] () {
if (!(this instanceof Headers)) {
throw new TypeError('Illegal invocation')
}

return this[kHeadersSortedMap]
}

forEach (...args) {
Expand All @@ -345,8 +355,7 @@ class Headers {
const callback = args[0]
const thisArg = args[1]

const sortedHeaders = this._getHeadersListIterable()
sortedHeaders.forEach((value, index) => {
this[kHeadersSortedMap].forEach((value, index) => {
callback.apply(thisArg, [value, index, this])
})
}
Expand Down
5 changes: 5 additions & 0 deletions lib/fetch/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,11 @@ class Request {
// 4. If headers is a Headers object, then for each header in its header
// list, append header’s name/header’s value to this’s headers.
if (headers instanceof Headers) {
// TODO (fix): Why doesn't this work?
// for (const [key, val] of headers[kHeadersList]) {
// this[kHeaders].append(key, val)
// }

this[kState].headersList = new HeadersList([
...this[kState].headersList,
...headers[kHeadersList]
Expand Down
13 changes: 5 additions & 8 deletions lib/fetch/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,16 +394,13 @@ function makeFilteredHeadersList (headersList, filter) {
// Override methods used by Headers class.
if (prop === 'get' || prop === 'has') {
return (name) => filter(name) ? target[prop](name) : undefined
} else if (prop === 'slice') {
return (...args) => {
assert(args.length === 0)
const arr = []
for (let index = 0; index < target.length; index += 2) {
if (filter(target[index])) {
arr.push(target[index], target[index + 1])
} else if (prop === Symbol.iterator) {
return function * () {
for (const entry of target) {
if (filter(entry[0])) {
yield entry
}
}
return arr
}
} else {
return target[prop]
Expand Down
15 changes: 1 addition & 14 deletions test/fetch/headers.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
'use strict'

const util = require('util')
const tap = require('tap')
const {
Headers,
normalizeAndValidateHeaderName,
normalizeAndValidateHeaderValue,
fill
} = require('../../lib/fetch/headers')
const { kHeadersList } = require('../../lib/core/symbols')
const { kGuard } = require('../../lib/fetch/symbols')
const {
forbiddenHeaderNames,
Expand Down Expand Up @@ -454,7 +452,7 @@ tap.test('Headers as Iterable', t => {
['f', '6']
])]

t.same([...headers[kHeadersList]], expected)
t.same([...headers], expected)
})
})

Expand Down Expand Up @@ -562,17 +560,6 @@ tap.test('various init paths of Headers', (t) => {
t.end()
})

tap.test('node inspect', (t) => {
const headers = new Headers()
headers.set('k1', 'v1')
headers.set('k2', 'v2')
t.equal(util.inspect(headers), `HeadersList {
[Symbol(headers map)]: Map(2) { 'k1' => 'v1', 'k2' => 'v2' },
[Symbol(headers map sorted)]: undefined
}`)
t.end()
})

tap.test('immutable guard', (t) => {
const headers = new Headers()
headers.set('key', 'val')
Expand Down