Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
tsctx committed Mar 1, 2024
1 parent ef5fec8 commit 1cf900d
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 26 deletions.
2 changes: 1 addition & 1 deletion lib/dispatcher/client-h1.js
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ function writeH1 (client, request) {
extractBody = require('../web/fetch/body.js').extractBody
}

const [bodyStream, contentType] = extractBody(body)
const { 0: bodyStream, 1: contentType } = extractBody(body)
if (request.contentType == null) {
headers.push('content-type', contentType)
}
Expand Down
41 changes: 28 additions & 13 deletions lib/web/fetch/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const File = NativeFile ?? UndiciFile
const textEncoder = new TextEncoder()
const textDecoder = new TextDecoder()

// https://github.com/nodejs/node/issues/44985
// fix-patch: https://github.com/nodejs/node/pull/51526
const needReadableStreamTee = util.nodeMajor <= 20 || (util.nodeMajor === 21 && util.nodeMinor <= 6)

// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
function extractBody (object, keepalive = false) {
// 1. Let stream be null.
Expand Down Expand Up @@ -125,17 +129,21 @@ function extractBody (object, keepalive = false) {

for (const [name, value] of object) {
if (typeof value === 'string') {
const chunk = textEncoder.encode(prefix +
`; name="${escape(normalizeLinefeeds(name))}"` +
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
const chunk = textEncoder.encode(
`${prefix}; name="${escape(
normalizeLinefeeds(name)
)}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`
)
blobParts.push(chunk)
length += chunk.byteLength
} else {
const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
(value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' +
`Content-Type: ${
const chunk = textEncoder.encode(
`${prefix}; name="${escape(normalizeLinefeeds(name))}"${
value.name ? `; filename="${escape(value.name)}"` : ''
}\r\nContent-Type: ${
value.type || 'application/octet-stream'
}\r\n\r\n`)
}\r\n\r\n`
)
blobParts.push(chunk, value, rn)
if (typeof value.size === 'number') {
length += chunk.byteLength + value.size + rn.byteLength
Expand Down Expand Up @@ -228,7 +236,7 @@ function extractBody (object, keepalive = false) {
// bytes into stream.
if (!isErrored(stream)) {
const buffer = new Uint8Array(value)
if (buffer.byteLength) {
if (buffer.byteLength > 0) {
controller.enqueue(buffer)
}
}
Expand Down Expand Up @@ -276,16 +284,23 @@ function cloneBody (body) {
// 1. Let « out1, out2 » be the result of teeing body’s stream.
const [out1, out2] = body.stream.tee()
const out2Clone = structuredClone(out2, { transfer: [out2] })
// This, for whatever reasons, unrefs out2Clone which allows
// the process to exit by itself.
const [, finalClone] = out2Clone.tee()
let streamClone

if (needReadableStreamTee) {
// This, for whatever reasons, unrefs out2Clone which allows
// the process to exit by itself.
const { 1: finalClone } = out2Clone.tee()
streamClone = finalClone
} else {
streamClone = out2Clone
}

// 2. Set body’s stream to out1.
body.stream = out1

// 3. Return a body whose stream is out2 and other members are copied from body.
return {
stream: finalClone,
stream: streamClone,
length: body.length,
source: body.source
}
Expand Down Expand Up @@ -435,7 +450,7 @@ function bodyMixinMethods (instance) {

// 3. Return a new FormData object whose entries are entries.
const formData = new FormData()
for (const [name, value] of entries) {
for (const { 0: name, 1: value } of entries) {
formData.append(name, value)
}
return formData
Expand Down
1 change: 1 addition & 0 deletions lib/web/fetch/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const requestCache = [
]

// https://fetch.spec.whatwg.org/#request-body-header-name
// Note: The header names are should be lowercase.
const requestBodyHeader = [
'content-encoding',
'content-language',
Expand Down
12 changes: 8 additions & 4 deletions lib/web/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1313,8 +1313,8 @@ function httpRedirectFetch (fetchParams, response) {

// 2. For each headerName of request-body-header name, delete headerName from
// request’s header list.
for (const headerName of requestBodyHeader) {
request.headersList.delete(headerName)
for (let i = 0; i < requestBodyHeader.length; ++i) {
request.headersList.delete(requestBodyHeader[i], true)
}
}

Expand Down Expand Up @@ -1475,7 +1475,7 @@ async function httpNetworkOrCacheFetch (
// user agents should append `User-Agent`/default `User-Agent` value to
// httpRequest’s header list.
if (!httpRequest.headersList.contains('user-agent', true)) {
httpRequest.headersList.append('user-agent', defaultUserAgent)
httpRequest.headersList.append('user-agent', defaultUserAgent, true)
}

// 15. If httpRequest’s cache mode is "default" and httpRequest’s header
Expand Down Expand Up @@ -2078,7 +2078,11 @@ async function httpNetworkFetch (
path: url.pathname + url.search,
origin: url.origin,
method: request.method,
body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
// https://github.com/nodejs/undici/issues/2418
body: agent.isMockActive
// FIXME: Why prioritize source?
? request.body && (request.body.source || request.body.stream)
: body,
headers: request.headersList.entries,
maxRedirections: 0,
upgrade: request.mode === 'websocket' ? 'websocket' : undefined
Expand Down
22 changes: 18 additions & 4 deletions lib/web/fetch/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Request {

webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' })

input = webidl.converters.RequestInfo(input)
input = webidl.converters.RequestInfo_DOMString(input)
init = webidl.converters.RequestInit(init)

// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
Expand Down Expand Up @@ -467,7 +467,8 @@ class Request {
// list, append header’s name/header’s value to this’s headers.
if (headers instanceof HeadersList) {
for (const [key, val] of headers) {
headersList.append(key, val)
// Note: The header names are already in lowercase.
headersList.append(key, val, true)
}
// Note: Copy the `set-cookie` meta-data.
headersList.cookies = headers.cookies
Expand Down Expand Up @@ -499,7 +500,7 @@ class Request {
// 1. Let Content-Type be null.
// 2. Set initBody and Content-Type to the result of extracting
// init["body"], with keepalive set to request’s keepalive.
const [extractedBody, contentType] = extractBody(
const { 0: extractedBody, 1: contentType } = extractBody(
init.body,
request.keepalive
)
Expand Down Expand Up @@ -904,6 +905,19 @@ webidl.converters.RequestInfo = function (V) {
return webidl.converters.USVString(V)
}

// DOMString is used because the value is converted to a USVString in `new URL()`.
webidl.converters.RequestInfo_DOMString = function (V) {
if (typeof V === 'string') {
return webidl.converters.DOMString(V)
}

if (V instanceof Request) {
return webidl.converters.Request(V)
}

return webidl.converters.DOMString(V)
}

webidl.converters.AbortSignal = webidl.interfaceConverter(
AbortSignal
)
Expand All @@ -921,7 +935,7 @@ webidl.converters.RequestInit = webidl.dictionaryConverter([
{
key: 'body',
converter: webidl.nullableConverter(
webidl.converters.BodyInit
webidl.converters.BodyInit_DOMString
)
},
{
Expand Down
44 changes: 42 additions & 2 deletions lib/web/fetch/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ class Response {

webidl.argumentLengthCheck(arguments, 1, { header: 'Response.redirect' })

url = webidl.converters.USVString(url)
// DOMString is used because the value is converted to a USVString in `new URL()`.
url = webidl.converters.DOMString(url)
status = webidl.converters['unsigned short'](status)

// 1. Let parsedURL be the result of parsing url with current settings
Expand Down Expand Up @@ -120,7 +121,7 @@ class Response {
}

if (body !== null) {
body = webidl.converters.BodyInit(body)
body = webidl.converters.BodyInit_DOMString(body)
}

init = webidl.converters.ResponseInit(init)
Expand Down Expand Up @@ -540,6 +541,30 @@ webidl.converters.XMLHttpRequestBodyInit = function (V) {
return webidl.converters.DOMString(V)
}

webidl.converters.XMLHttpRequestBodyInit_DOMString = function (V) {
if (typeof V === 'string') {
return webidl.converters.USVString(V)
}

if (isBlobLike(V)) {
return webidl.converters.Blob(V, { strict: false })
}

if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
return webidl.converters.BufferSource(V)
}

if (util.isFormDataLike(V)) {
return webidl.converters.FormData(V, { strict: false })
}

if (V instanceof URLSearchParams) {
return webidl.converters.URLSearchParams(V)
}

return webidl.converters.DOMString(V)
}

// https://fetch.spec.whatwg.org/#bodyinit
webidl.converters.BodyInit = function (V) {
if (V instanceof ReadableStream) {
Expand All @@ -555,6 +580,21 @@ webidl.converters.BodyInit = function (V) {
return webidl.converters.XMLHttpRequestBodyInit(V)
}

// https://fetch.spec.whatwg.org/#bodyinit
webidl.converters.BodyInit_DOMString = function (V) {
if (V instanceof ReadableStream) {
return webidl.converters.ReadableStream(V)
}

// Note: the spec doesn't include async iterables,
// this is an undici extension.
if (V?.[Symbol.asyncIterator]) {
return V
}

return webidl.converters.XMLHttpRequestBodyInit_DOMString(V)
}

webidl.converters.ResponseInit = webidl.dictionaryConverter([
{
key: 'status',
Expand Down
5 changes: 3 additions & 2 deletions lib/web/fetch/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1322,8 +1322,9 @@ function extractMimeType (headers) {

// 6.4.2. If mimeType’s parameters["charset"] exists, then set charset to
// mimeType’s parameters["charset"].
if (mimeType.parameters.has('charset')) {
charset = mimeType.parameters.get('charset')
const maybeCharset = mimeType.parameters.get('charset')
if (maybeCharset !== undefined) {
charset = maybeCharset
}

// 6.4.3. Set essence to mimeType’s essence.
Expand Down

0 comments on commit 1cf900d

Please sign in to comment.