@helia/verified-fetch 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.min.js +7 -7
- package/dist/src/types.d.ts +1 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/byte-range-context.d.ts +80 -0
- package/dist/src/utils/byte-range-context.d.ts.map +1 -0
- package/dist/src/utils/byte-range-context.js +277 -0
- package/dist/src/utils/byte-range-context.js.map +1 -0
- package/dist/src/utils/get-stream-from-async-iterable.js +1 -1
- package/dist/src/utils/parse-url-string.js +4 -4
- package/dist/src/utils/request-headers.d.ts +13 -0
- package/dist/src/utils/request-headers.d.ts.map +1 -0
- package/dist/src/utils/request-headers.js +62 -0
- package/dist/src/utils/request-headers.js.map +1 -0
- package/dist/src/utils/response-headers.d.ts +12 -0
- package/dist/src/utils/response-headers.d.ts.map +1 -0
- package/dist/src/utils/response-headers.js +37 -0
- package/dist/src/utils/response-headers.js.map +1 -0
- package/dist/src/utils/responses.d.ts +21 -4
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/responses.js +58 -0
- package/dist/src/utils/responses.js.map +1 -1
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +60 -18
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +1 -1
- package/src/types.ts +2 -0
- package/src/utils/byte-range-context.ts +301 -0
- package/src/utils/get-stream-from-async-iterable.ts +1 -1
- package/src/utils/parse-url-string.ts +4 -4
- package/src/utils/request-headers.ts +63 -0
- package/src/utils/response-headers.ts +42 -0
- package/src/utils/responses.ts +82 -4
- package/src/verified-fetch.ts +65 -21
|
@@ -106,7 +106,7 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
|
|
|
106
106
|
log.trace('resolved %s to %c from cache', cidOrPeerIdOrDnsLink, cid)
|
|
107
107
|
} else {
|
|
108
108
|
// protocol is ipns
|
|
109
|
-
log.trace('
|
|
109
|
+
log.trace('attempting to resolve PeerId for %s', cidOrPeerIdOrDnsLink)
|
|
110
110
|
let peerId = null
|
|
111
111
|
try {
|
|
112
112
|
peerId = peerIdFromString(cidOrPeerIdOrDnsLink)
|
|
@@ -117,10 +117,10 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
|
|
|
117
117
|
ipnsCache.set(cidOrPeerIdOrDnsLink, resolveResult, 60 * 1000 * 2)
|
|
118
118
|
} catch (err) {
|
|
119
119
|
if (peerId == null) {
|
|
120
|
-
log.error('
|
|
120
|
+
log.error('could not parse PeerId string "%s"', cidOrPeerIdOrDnsLink, err)
|
|
121
121
|
errors.push(new TypeError(`Could not parse PeerId in ipns url "${cidOrPeerIdOrDnsLink}", ${(err as Error).message}`))
|
|
122
122
|
} else {
|
|
123
|
-
log.error('
|
|
123
|
+
log.error('could not resolve PeerId %c', peerId, err)
|
|
124
124
|
errors.push(new TypeError(`Could not resolve PeerId "${cidOrPeerIdOrDnsLink}", ${(err as Error).message}`))
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -140,7 +140,7 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
|
|
|
140
140
|
log.trace('resolved %s to %c', decodedDnsLinkLabel, cid)
|
|
141
141
|
ipnsCache.set(cidOrPeerIdOrDnsLink, resolveResult, 60 * 1000 * 2)
|
|
142
142
|
} catch (err: any) {
|
|
143
|
-
log.error('
|
|
143
|
+
log.error('could not resolve DnsLink for "%s"', cidOrPeerIdOrDnsLink, err)
|
|
144
144
|
errors.push(err)
|
|
145
145
|
}
|
|
146
146
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function getHeader (headers: HeadersInit | undefined, header: string): string | undefined {
|
|
2
|
+
if (headers == null) {
|
|
3
|
+
return undefined
|
|
4
|
+
}
|
|
5
|
+
if (headers instanceof Headers) {
|
|
6
|
+
return headers.get(header) ?? undefined
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(headers)) {
|
|
9
|
+
const entry = headers.find(([key]) => key.toLowerCase() === header.toLowerCase())
|
|
10
|
+
return entry?.[1]
|
|
11
|
+
}
|
|
12
|
+
const key = Object.keys(headers).find(k => k.toLowerCase() === header.toLowerCase())
|
|
13
|
+
if (key == null) {
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return headers[key]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Given two ints from a Range header, and potential fileSize, returns:
|
|
22
|
+
* 1. number of bytes the response should contain.
|
|
23
|
+
* 2. the start index of the range. // inclusive
|
|
24
|
+
* 3. the end index of the range. // inclusive
|
|
25
|
+
*/
|
|
26
|
+
// eslint-disable-next-line complexity
|
|
27
|
+
export function calculateByteRangeIndexes (start: number | undefined, end: number | undefined, fileSize?: number): { byteSize?: number, start?: number, end?: number } {
|
|
28
|
+
if ((start ?? 0) > (end ?? Infinity)) {
|
|
29
|
+
throw new Error('Invalid range: Range-start index is greater than range-end index.')
|
|
30
|
+
}
|
|
31
|
+
if (start != null && (end ?? 0) >= (fileSize ?? Infinity)) {
|
|
32
|
+
throw new Error('Invalid range: Range-end index is greater than or equal to the size of the file.')
|
|
33
|
+
}
|
|
34
|
+
if (start == null && (end ?? 0) > (fileSize ?? Infinity)) {
|
|
35
|
+
throw new Error('Invalid range: Range-end index is greater than the size of the file.')
|
|
36
|
+
}
|
|
37
|
+
if (start != null && start < 0) {
|
|
38
|
+
throw new Error('Invalid range: Range-start index cannot be negative.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (start != null && end != null) {
|
|
42
|
+
return { byteSize: end - start + 1, start, end }
|
|
43
|
+
} else if (start == null && end != null) {
|
|
44
|
+
// suffix byte range requested
|
|
45
|
+
if (fileSize == null) {
|
|
46
|
+
return { end }
|
|
47
|
+
}
|
|
48
|
+
if (end === fileSize) {
|
|
49
|
+
return { byteSize: fileSize, start: 0, end: fileSize - 1 }
|
|
50
|
+
}
|
|
51
|
+
return { byteSize: end, start: fileSize - end, end: fileSize - 1 }
|
|
52
|
+
} else if (start != null && end == null) {
|
|
53
|
+
if (fileSize == null) {
|
|
54
|
+
// we only have the start index, and no fileSize, so we can't return a valid range.
|
|
55
|
+
return { start }
|
|
56
|
+
}
|
|
57
|
+
const end = fileSize - 1
|
|
58
|
+
const byteSize = fileSize - start
|
|
59
|
+
return { byteSize, start, end }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { byteSize: fileSize, start: 0, end: fileSize != null ? fileSize - 1 : 0 }
|
|
63
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function returns the value of the `Content-Range` header for a given range.
|
|
3
|
+
* If you know the total size of the body, pass it as `byteSize`
|
|
4
|
+
*
|
|
5
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range
|
|
6
|
+
*/
|
|
7
|
+
export function getContentRangeHeader ({ byteStart, byteEnd, byteSize }: { byteStart: number | undefined, byteEnd: number | undefined, byteSize: number | undefined }): string {
|
|
8
|
+
const total = byteSize ?? '*' // if we don't know the total size, we should use *
|
|
9
|
+
|
|
10
|
+
if ((byteEnd ?? 0) >= (byteSize ?? Infinity)) {
|
|
11
|
+
throw new Error('Invalid range: Range-end index is greater than or equal to the size of the file.')
|
|
12
|
+
}
|
|
13
|
+
if ((byteStart ?? 0) >= (byteSize ?? Infinity)) {
|
|
14
|
+
throw new Error('Invalid range: Range-start index is greater than or equal to the size of the file.')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (byteStart != null && byteEnd == null) {
|
|
18
|
+
// only byteStart in range
|
|
19
|
+
if (byteSize == null) {
|
|
20
|
+
return `bytes */${total}`
|
|
21
|
+
}
|
|
22
|
+
return `bytes ${byteStart}-${byteSize - 1}/${byteSize}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (byteStart == null && byteEnd != null) {
|
|
26
|
+
// only byteEnd in range
|
|
27
|
+
if (byteSize == null) {
|
|
28
|
+
return `bytes */${total}`
|
|
29
|
+
}
|
|
30
|
+
const end = byteSize - 1
|
|
31
|
+
const start = end - byteEnd + 1
|
|
32
|
+
|
|
33
|
+
return `bytes ${start}-${end}/${byteSize}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (byteStart == null && byteEnd == null) {
|
|
37
|
+
// neither are provided, we can't return a valid range.
|
|
38
|
+
return `bytes */${total}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `bytes ${byteStart}-${byteEnd}/${total}`
|
|
42
|
+
}
|
package/src/utils/responses.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { ByteRangeContext } from './byte-range-context'
|
|
2
|
+
import type { SupportedBodyTypes } from '../types.js'
|
|
3
|
+
import type { Logger } from '@libp2p/interface'
|
|
4
|
+
|
|
1
5
|
function setField (response: Response, name: string, value: string | boolean): void {
|
|
2
6
|
Object.defineProperty(response, name, {
|
|
3
7
|
enumerable: true,
|
|
@@ -23,7 +27,7 @@ export interface ResponseOptions extends ResponseInit {
|
|
|
23
27
|
redirected?: boolean
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
export function okResponse (url: string, body?:
|
|
30
|
+
export function okResponse (url: string, body?: SupportedBodyTypes, init?: ResponseOptions): Response {
|
|
27
31
|
const response = new Response(body, {
|
|
28
32
|
...(init ?? {}),
|
|
29
33
|
status: 200,
|
|
@@ -34,13 +38,27 @@ export function okResponse (url: string, body?: BodyInit | null, init?: Response
|
|
|
34
38
|
setRedirected(response)
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
setType(response, 'basic')
|
|
42
|
+
setUrl(response, url)
|
|
43
|
+
response.headers.set('Accept-Ranges', 'bytes')
|
|
44
|
+
|
|
45
|
+
return response
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function badGatewayResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
49
|
+
const response = new Response(body, {
|
|
50
|
+
...(init ?? {}),
|
|
51
|
+
status: 502,
|
|
52
|
+
statusText: 'Bad Gateway'
|
|
53
|
+
})
|
|
54
|
+
|
|
37
55
|
setType(response, 'basic')
|
|
38
56
|
setUrl(response, url)
|
|
39
57
|
|
|
40
58
|
return response
|
|
41
59
|
}
|
|
42
60
|
|
|
43
|
-
export function notSupportedResponse (url: string, body?:
|
|
61
|
+
export function notSupportedResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
44
62
|
const response = new Response(body, {
|
|
45
63
|
...(init ?? {}),
|
|
46
64
|
status: 501,
|
|
@@ -54,7 +72,7 @@ export function notSupportedResponse (url: string, body?: BodyInit | null, init?
|
|
|
54
72
|
return response
|
|
55
73
|
}
|
|
56
74
|
|
|
57
|
-
export function notAcceptableResponse (url: string, body?:
|
|
75
|
+
export function notAcceptableResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
58
76
|
const response = new Response(body, {
|
|
59
77
|
...(init ?? {}),
|
|
60
78
|
status: 406,
|
|
@@ -67,7 +85,7 @@ export function notAcceptableResponse (url: string, body?: BodyInit | null, init
|
|
|
67
85
|
return response
|
|
68
86
|
}
|
|
69
87
|
|
|
70
|
-
export function badRequestResponse (url: string, body?:
|
|
88
|
+
export function badRequestResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
71
89
|
const response = new Response(body, {
|
|
72
90
|
...(init ?? {}),
|
|
73
91
|
status: 400,
|
|
@@ -96,3 +114,63 @@ export function movedPermanentlyResponse (url: string, location: string, init?:
|
|
|
96
114
|
|
|
97
115
|
return response
|
|
98
116
|
}
|
|
117
|
+
|
|
118
|
+
interface RangeOptions {
|
|
119
|
+
byteRangeContext: ByteRangeContext
|
|
120
|
+
log?: Logger
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function okRangeResponse (url: string, body: SupportedBodyTypes, { byteRangeContext, log }: RangeOptions, init?: ResponseOptions): Response {
|
|
124
|
+
if (!byteRangeContext.isRangeRequest) {
|
|
125
|
+
return okResponse(url, body, init)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!byteRangeContext.isValidRangeRequest) {
|
|
129
|
+
return badRangeResponse(url, body, init)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let response: Response
|
|
133
|
+
try {
|
|
134
|
+
response = new Response(body, {
|
|
135
|
+
...(init ?? {}),
|
|
136
|
+
status: 206,
|
|
137
|
+
statusText: 'Partial Content',
|
|
138
|
+
headers: {
|
|
139
|
+
...(init?.headers ?? {}),
|
|
140
|
+
'content-range': byteRangeContext.contentRangeHeaderValue
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
} catch (e: any) {
|
|
144
|
+
log?.error('failed to create range response', e)
|
|
145
|
+
return badRangeResponse(url, body, init)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (init?.redirected === true) {
|
|
149
|
+
setRedirected(response)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
setType(response, 'basic')
|
|
153
|
+
setUrl(response, url)
|
|
154
|
+
response.headers.set('Accept-Ranges', 'bytes')
|
|
155
|
+
|
|
156
|
+
return response
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* We likely need to catch errors handled by upstream helia libraries if range-request throws an error. Some examples:
|
|
161
|
+
* * The range is out of bounds
|
|
162
|
+
* * The range is invalid
|
|
163
|
+
* * The range is not supported for the given type
|
|
164
|
+
*/
|
|
165
|
+
export function badRangeResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
166
|
+
const response = new Response(body, {
|
|
167
|
+
...(init ?? {}),
|
|
168
|
+
status: 416,
|
|
169
|
+
statusText: 'Requested Range Not Satisfiable'
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
setType(response, 'basic')
|
|
173
|
+
setUrl(response, url)
|
|
174
|
+
|
|
175
|
+
return response
|
|
176
|
+
}
|
package/src/verified-fetch.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { car } from '@helia/car'
|
|
2
2
|
import { ipns as heliaIpns, type IPNS } from '@helia/ipns'
|
|
3
|
-
import { unixfs as heliaUnixFs, type UnixFS as HeliaUnixFs
|
|
3
|
+
import { unixfs as heliaUnixFs, type UnixFS as HeliaUnixFs } from '@helia/unixfs'
|
|
4
4
|
import * as ipldDagCbor from '@ipld/dag-cbor'
|
|
5
5
|
import * as ipldDagJson from '@ipld/dag-json'
|
|
6
6
|
import { code as dagPbCode } from '@ipld/dag-pb'
|
|
@@ -15,17 +15,19 @@ import { CustomProgressEvent } from 'progress-events'
|
|
|
15
15
|
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
|
|
16
16
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
17
17
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
18
|
+
import { ByteRangeContext } from './utils/byte-range-context.js'
|
|
18
19
|
import { dagCborToSafeJSON } from './utils/dag-cbor-to-safe-json.js'
|
|
19
20
|
import { getContentDispositionFilename } from './utils/get-content-disposition-filename.js'
|
|
20
21
|
import { getETag } from './utils/get-e-tag.js'
|
|
21
22
|
import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterable.js'
|
|
22
23
|
import { tarStream } from './utils/get-tar-stream.js'
|
|
23
24
|
import { parseResource } from './utils/parse-resource.js'
|
|
24
|
-
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse } from './utils/responses.js'
|
|
25
|
+
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse } from './utils/responses.js'
|
|
25
26
|
import { selectOutputType, queryFormatToAcceptHeader } from './utils/select-output-type.js'
|
|
26
27
|
import { walkPath } from './utils/walk-path.js'
|
|
27
28
|
import type { CIDDetail, ContentTypeParser, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
28
29
|
import type { RequestFormatShorthand } from './types.js'
|
|
30
|
+
import type { ParsedUrlStringResults } from './utils/parse-url-string'
|
|
29
31
|
import type { Helia } from '@helia/interface'
|
|
30
32
|
import type { AbortOptions, Logger, PeerId } from '@libp2p/interface'
|
|
31
33
|
import type { DNSResolver } from '@multiformats/dns/resolvers'
|
|
@@ -275,17 +277,19 @@ export class VerifiedFetch {
|
|
|
275
277
|
let terminalElement: UnixFSEntry | undefined
|
|
276
278
|
let ipfsRoots: CID[] | undefined
|
|
277
279
|
let redirected = false
|
|
280
|
+
const byteRangeContext = new ByteRangeContext(this.helia.logger, options?.headers)
|
|
278
281
|
|
|
279
282
|
try {
|
|
280
283
|
const pathDetails = await walkPath(this.helia.blockstore, `${cid.toString()}/${path}`, options)
|
|
281
284
|
ipfsRoots = pathDetails.ipfsRoots
|
|
282
285
|
terminalElement = pathDetails.terminalElement
|
|
283
286
|
} catch (err) {
|
|
284
|
-
this.log.error('
|
|
287
|
+
this.log.error('error walking path %s', path, err)
|
|
288
|
+
|
|
289
|
+
return badGatewayResponse('Error walking path')
|
|
285
290
|
}
|
|
286
291
|
|
|
287
292
|
let resolvedCID = terminalElement?.cid ?? cid
|
|
288
|
-
let stat: UnixFSStats
|
|
289
293
|
if (terminalElement?.type === 'directory') {
|
|
290
294
|
const dirCid = terminalElement.cid
|
|
291
295
|
|
|
@@ -307,7 +311,7 @@ export class VerifiedFetch {
|
|
|
307
311
|
const rootFilePath = 'index.html'
|
|
308
312
|
try {
|
|
309
313
|
this.log.trace('found directory at %c/%s, looking for index.html', cid, path)
|
|
310
|
-
stat = await this.unixfs.stat(dirCid, {
|
|
314
|
+
const stat = await this.unixfs.stat(dirCid, {
|
|
311
315
|
path: rootFilePath,
|
|
312
316
|
signal: options?.signal,
|
|
313
317
|
onProgress: options?.onProgress
|
|
@@ -323,30 +327,56 @@ export class VerifiedFetch {
|
|
|
323
327
|
}
|
|
324
328
|
}
|
|
325
329
|
|
|
330
|
+
// we have a validRangeRequest & terminalElement is a file, we know the size and should set it
|
|
331
|
+
if (byteRangeContext.isRangeRequest && byteRangeContext.isValidRangeRequest && terminalElement.type === 'file') {
|
|
332
|
+
byteRangeContext.setFileSize(terminalElement.unixfs.fileSize())
|
|
333
|
+
|
|
334
|
+
this.log.trace('fileSize for rangeRequest %d', byteRangeContext.getFileSize())
|
|
335
|
+
}
|
|
336
|
+
const offset = byteRangeContext.offset
|
|
337
|
+
const length = byteRangeContext.length
|
|
338
|
+
this.log.trace('calling unixfs.cat for %c/%s with offset=%o & length=%o', resolvedCID, path, offset, length)
|
|
326
339
|
const asyncIter = this.unixfs.cat(resolvedCID, {
|
|
327
340
|
signal: options?.signal,
|
|
328
|
-
onProgress: options?.onProgress
|
|
341
|
+
onProgress: options?.onProgress,
|
|
342
|
+
offset,
|
|
343
|
+
length
|
|
329
344
|
})
|
|
330
345
|
this.log('got async iterator for %c/%s', cid, path)
|
|
331
346
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
347
|
+
try {
|
|
348
|
+
const { stream, firstChunk } = await getStreamFromAsyncIterable(asyncIter, path ?? '', this.helia.logger, {
|
|
349
|
+
onProgress: options?.onProgress
|
|
350
|
+
})
|
|
351
|
+
byteRangeContext.setBody(stream)
|
|
352
|
+
// if not a valid range request, okRangeRequest will call okResponse
|
|
353
|
+
const response = okRangeResponse(resource, byteRangeContext.getBody(), { byteRangeContext, log: this.log }, {
|
|
354
|
+
redirected
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
await this.setContentType(firstChunk, path, response)
|
|
358
|
+
|
|
359
|
+
if (ipfsRoots != null) {
|
|
360
|
+
response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
|
|
361
|
+
}
|
|
339
362
|
|
|
340
|
-
|
|
341
|
-
|
|
363
|
+
return response
|
|
364
|
+
} catch (err: any) {
|
|
365
|
+
this.log.error('error streaming %c/%s', cid, path, err)
|
|
366
|
+
if (byteRangeContext.isRangeRequest && err.code === 'ERR_INVALID_PARAMS') {
|
|
367
|
+
return badRangeResponse(resource)
|
|
368
|
+
}
|
|
369
|
+
return badGatewayResponse('Unable to stream content')
|
|
342
370
|
}
|
|
343
|
-
|
|
344
|
-
return response
|
|
345
371
|
}
|
|
346
372
|
|
|
347
373
|
private async handleRaw ({ resource, cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
374
|
+
const byteRangeContext = new ByteRangeContext(this.helia.logger, options?.headers)
|
|
348
375
|
const result = await this.helia.blockstore.get(cid, options)
|
|
349
|
-
|
|
376
|
+
byteRangeContext.setBody(result)
|
|
377
|
+
const response = okRangeResponse(resource, byteRangeContext.getBody(), { byteRangeContext, log: this.log }, {
|
|
378
|
+
redirected: false
|
|
379
|
+
})
|
|
350
380
|
|
|
351
381
|
// if the user has specified an `Accept` header that corresponds to a raw
|
|
352
382
|
// type, honour that header, so for example they don't request
|
|
@@ -380,10 +410,10 @@ export class VerifiedFetch {
|
|
|
380
410
|
contentType = parsed
|
|
381
411
|
}
|
|
382
412
|
} catch (err) {
|
|
383
|
-
this.log.error('
|
|
413
|
+
this.log.error('error parsing content type', err)
|
|
384
414
|
}
|
|
385
415
|
}
|
|
386
|
-
|
|
416
|
+
this.log.trace('setting content type to "%s"', contentType)
|
|
387
417
|
response.headers.set('content-type', contentType)
|
|
388
418
|
}
|
|
389
419
|
|
|
@@ -408,7 +438,19 @@ export class VerifiedFetch {
|
|
|
408
438
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { resource }))
|
|
409
439
|
|
|
410
440
|
// resolve the CID/path from the requested resource
|
|
411
|
-
|
|
441
|
+
let cid: ParsedUrlStringResults['cid']
|
|
442
|
+
let path: ParsedUrlStringResults['path']
|
|
443
|
+
let query: ParsedUrlStringResults['query']
|
|
444
|
+
try {
|
|
445
|
+
const result = await parseResource(resource, { ipns: this.ipns, logger: this.helia.logger }, options)
|
|
446
|
+
cid = result.cid
|
|
447
|
+
path = result.path
|
|
448
|
+
query = result.query
|
|
449
|
+
} catch (err) {
|
|
450
|
+
this.log.error('error parsing resource %s', resource, err)
|
|
451
|
+
|
|
452
|
+
return badRequestResponse('Invalid resource')
|
|
453
|
+
}
|
|
412
454
|
|
|
413
455
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', { cid, path }))
|
|
414
456
|
|
|
@@ -461,12 +503,14 @@ export class VerifiedFetch {
|
|
|
461
503
|
query.filename = query.filename ?? `${cid.toString()}.tar`
|
|
462
504
|
response = await this.handleTar(handlerArgs)
|
|
463
505
|
} else {
|
|
506
|
+
this.log.trace('finding handler for cid code "%s" and output type "%s"', cid.code, accept)
|
|
464
507
|
// derive the handler from the CID type
|
|
465
508
|
const codecHandler = this.codecHandlers[cid.code]
|
|
466
509
|
|
|
467
510
|
if (codecHandler == null) {
|
|
468
511
|
return notSupportedResponse(`Support for codec with code ${cid.code} is not yet implemented. Please open an issue at https://github.com/ipfs/helia/issues/new`)
|
|
469
512
|
}
|
|
513
|
+
this.log.trace('calling handler "%s"', codecHandler.name)
|
|
470
514
|
|
|
471
515
|
response = await codecHandler.call(this, handlerArgs)
|
|
472
516
|
}
|