Skip to content

Commit

Permalink
add support for file urls in fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
Linkgoron committed Feb 17, 2022
1 parent b081930 commit e0525c3
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 1 deletion.
71 changes: 70 additions & 1 deletion lib/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const { PassThrough, pipeline } = require('stream')
const { isErrored, isReadable } = require('../core/util')
const { kIsMockActive } = require('../mock/mock-symbols')
const { dataURLProcessor } = require('./dataURL')
const fs = require('fs')

/** @type {import('buffer').resolveObjectURL} */
let resolveObjectURL
Expand Down Expand Up @@ -882,7 +883,8 @@ async function schemeFetch (fetchParams) {
case 'file:': {
// For now, unfortunate as it is, file URLs are left as an exercise for the reader.
// When in doubt, return a network error.
return makeNetworkError('not implemented... yet...')
const currentURL = requestCurrentURL(request)
return fileFetch.call(this, request, currentURL)
}
case 'http:':
case 'https:': {
Expand Down Expand Up @@ -1990,4 +1992,71 @@ function httpNetworkFetch (
})
}

async function fileFetch (request, url) {
const context = this
if (request.method !== 'GET') {
return makeNetworkError(`Fetching files only supports the GET method. Received ${request.method}`)
}

let stream
let file

try {
const path = url.href.substring('file://'.length)
file = await fs.promises.open(path)
if (context.terminated) {
if (context.terminated.aborted) {
throw new AbortError()
} else {
throw new Error('terminated')
}
}
stream = file.createReadStream({ autoClose: true })
} catch (originalError) {
if (stream) {
stream.destroy(originalError)
} else if (file) {
try {
await file.close()
} catch (closeError) {
return createAggregateNetworkError(originalError, closeError)
}
}
return makeNetworkError(originalError)
}

try {
function onTerminated () {
const aborted = context.terminated.aborted
if (aborted) {
response.aborted = true
stream.destroy(new AbortError())
} else {
stream.destroy(new TypeError('terminated'))
}
}
function streamClose () {
context.off('terminated', onTerminated)
}
const response = makeResponse({
status: 200,
statusText: 'OK',
headersList: [],
body: safelyExtractBody(stream)[0]
})
context.on('terminated', onTerminated)
stream.on('close', streamClose)
return response
} catch (err) {
stream.destroy(err)
return makeNetworkError(err)
}
}

function createAggregateNetworkError (originalError, secondError) {
const err = new AggregateError([originalError, secondError], originalError.message)
err.code = originalError.code
return makeNetworkError(err)
}

module.exports = fetch
21 changes: 21 additions & 0 deletions test/fetch/client-fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { createServer } = require('http')
const { ReadableStream } = require('stream/web')
const { Blob } = require('buffer')
const { fetch, Response, Request, FormData, File, FileLike } = require('../..')
const { join } = require('path')

test('function signature', (t) => {
t.plan(2)
Expand Down Expand Up @@ -332,3 +333,23 @@ test('post FormData with File', (t) => {
t.ok(/filename123/.test(result))
})
})

test('fetch file', async (t) => {
t.plan(1)
const fileToRead = join(__dirname, '..', 'fixtures', 'file.text')
const body = await fetch(`file://${fileToRead}`)
const text = await body.text()
t.equal(text, 'hello world')
})

test('fetch file does not exist', async (t) => {
t.plan(2)
const fileToRead = join(__dirname, '..', 'fixtures', 'does-not-exist.text')
try {
await fetch(`file://${fileToRead}`)
t.fail('fetch should have thrown')
} catch (err) {
t.ok(err.cause)
t.equal(err.cause.code, 'ENOENT')
}
})
1 change: 1 addition & 0 deletions test/fixtures/file.text
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world

0 comments on commit e0525c3

Please sign in to comment.