Skip to content

Commit

Permalink
refactor: fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
ronag committed Aug 5, 2021
1 parent f1e485c commit 22efd70
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 196 deletions.
66 changes: 66 additions & 0 deletions lib/api/api-fetch/body.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict'

const util = require('../../core/util')
const { Readable } = require('stream')

let TransformStream

// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
function extractBody (body) {
// TODO: FormBody

if (body == null) {
return [null, null]
} else if (body instanceof URLSearchParams) {
// spec says to run application/x-www-form-urlencoded on body.list
// this is implemented in Node.js as apart of an URLSearchParams instance toString method
// See: https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L490
// and https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L1100
return [{
source: body.toString()
}, 'application/x-www-form-urlencoded;charset=UTF-8']
} else if (typeof body === 'string') {
return [{
source: body
}, 'text/plain;charset=UTF-8']
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
return [{
source: body
}, null]
} else if (util.isBlob(body)) {
return [{
source: body,
length: body.size
}, body.type || null]
} else if (util.isStream(body) || typeof body.pipeThrough === 'function') {
if (util.isDisturbed(body)) {
throw new TypeError('disturbed')
}

let stream
if (util.isStream(body)) {
stream = Readable.toWeb(body)
} else {
if (body.locked) {
throw new TypeError('locked')
}

if (!TransformStream) {
TransformStream = require('stream/web').TransformStream
}

// https://streams.spec.whatwg.org/#readablestream-create-a-proxy
const identityTransform = new TransformStream()
body.pipeThrough(identityTransform)
stream = identityTransform
}

return [{
stream
}, null]
} else {
throw Error('Cannot extract Body from input: ', body)
}
}

module.exports = { extractBody }
4 changes: 2 additions & 2 deletions lib/api/headers.js → lib/api/api-fetch/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

const { types } = require('util')
const { validateHeaderName, validateHeaderValue } = require('http')
const { kHeadersList } = require('../core/symbols')
const { InvalidHTTPTokenError, HTTPInvalidHeaderValueError, InvalidArgumentError, InvalidThisError } = require('../core/errors')
const { kHeadersList } = require('../../core/symbols')
const { InvalidHTTPTokenError, HTTPInvalidHeaderValueError, InvalidArgumentError, InvalidThisError } = require('../../core/errors')

function binarySearch (arr, val) {
let low = 0
Expand Down
200 changes: 6 additions & 194 deletions lib/api/api-fetch.js → lib/api/api-fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,148 +3,18 @@
'use strict'

const Headers = require('./headers')
const { kHeadersList } = require('../core/symbols')
const { Readable } = require('stream')
const { METHODS, STATUS_CODES } = require('http')
const { kHeadersList } = require('../../core/symbols')
const { METHODS } = require('http')
const Response = require('./response')
const {
InvalidArgumentError,
NotSupportedError,
RequestAbortedError
} = require('../core/errors')
const util = require('../core/util')
const { addSignal, removeSignal } = require('./abort-signal')
const { Blob } = require('buffer')

const kType = Symbol('type')
const kStatus = Symbol('status')
const kStatusText = Symbol('status text')
const kUrlList = Symbol('url list')
const kHeaders = Symbol('headers')
const kBody = Symbol('body')
} = require('../../core/errors')
const { addSignal, removeSignal } = require('../abort-signal')
const { extractBody } = require('./body')

let ReadableStream
let TransformStream

class Response {
constructor ({
type,
url,
body,
statusCode,
headers,
context
}) {
this[kType] = type || 'default'
this[kStatus] = statusCode || 0
this[kStatusText] = STATUS_CODES[statusCode] || ''
this[kUrlList] = Array.isArray(url) ? url : (url ? [url] : [])
this[kHeaders] = headers || new Headers()
this[kBody] = body || null

if (context && context.history) {
this[kUrlList].push(...context.history)
}
}

get type () {
return this[kType]
}

get url () {
const length = this[kUrlList].length
return length === 0 ? '' : this[kUrlList][length - 1].toString()
}

get redirected () {
return this[kUrlList].length > 1
}

get status () {
return this[kStatus]
}

get ok () {
return this[kStatus] >= 200 && this[kStatus] <= 299
}

get statusText () {
return this[kStatusText]
}

get headers () {
return this[kHeaders]
}

async blob () {
const chunks = []
if (this.body) {
if (this.bodyUsed || this.body.locked) {
throw new TypeError('unusable')
}

for await (const chunk of this.body) {
chunks.push(chunk)
}
}

return new Blob(chunks, { type: this.headers.get('Content-Type') || '' })
}

async arrayBuffer () {
const blob = await this.blob()
return await blob.arrayBuffer()
}

async text () {
const blob = await this.blob()
return await blob.text()
}

async json () {
return JSON.parse(await this.text())
}

async formData () {
// TODO: Implement.
throw new NotSupportedError('formData')
}

get body () {
return this[kBody]
}

get bodyUsed () {
return util.isDisturbed(this.body)
}

clone () {
let body = null

if (this[kBody]) {
if (util.isDisturbed(this[kBody])) {
throw new TypeError('disturbed')
}

if (this[kBody].locked) {
throw new TypeError('locked')
}

// https://fetch.spec.whatwg.org/#concept-body-clone
const [out1, out2] = this[kBody].tee()

this[kBody] = out1
body = out2
}

return new Response({
type: this[kType],
statusCode: this[kStatus],
url: this[kUrlList],
headers: this[kHeaders],
body
})
}
}

class FetchHandler {
constructor (opts, callback) {
Expand Down Expand Up @@ -378,62 +248,4 @@ function normalizeAndValidateRequestMethod (method) {
return normalizedMethod
}

// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
function extractBody (body) {
// TODO: FormBody

if (body == null) {
return [null, null]
} else if (body instanceof URLSearchParams) {
// spec says to run application/x-www-form-urlencoded on body.list
// this is implemented in Node.js as apart of an URLSearchParams instance toString method
// See: https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L490
// and https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L1100
return [{
source: body.toString()
}, 'application/x-www-form-urlencoded;charset=UTF-8']
} else if (typeof body === 'string') {
return [{
source: body
}, 'text/plain;charset=UTF-8']
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
return [{
source: body
}, null]
} else if (util.isBlob(body)) {
return [{
source: body,
length: body.size
}, body.type || null]
} else if (util.isStream(body) || typeof body.pipeThrough === 'function') {
if (util.isDisturbed(body)) {
throw new TypeError('disturbed')
}

let stream
if (util.isStream(body)) {
stream = Readable.toWeb(body)
} else {
if (body.locked) {
throw new TypeError('locked')
}

if (!TransformStream) {
TransformStream = require('stream/web').TransformStream
}

// https://streams.spec.whatwg.org/#readablestream-create-a-proxy
const identityTransform = new TransformStream()
body.pipeThrough(identityTransform)
stream = identityTransform
}

return [{
stream
}, null]
} else {
throw Error('Cannot extract Body from input: ', body)
}
}

module.exports = fetch
Loading

0 comments on commit 22efd70

Please sign in to comment.