|
1 | | -/* global Response */ |
| 1 | +/* global Response, Blob */ |
2 | 2 |
|
3 | 3 | 'use strict' |
4 | 4 |
|
5 | | -const stream = require('stream') |
6 | | -const toBlob = require('stream-to-blob') |
7 | | - |
8 | | -const debug = require('debug') |
9 | | -const log = debug('ipfs:http:response') |
| 5 | +const toStream = require('it-to-stream') |
| 6 | +const concat = require('it-concat') |
| 7 | +const toBuffer = require('it-buffer') |
| 8 | +const log = require('debug')('ipfs:http:response') |
10 | 9 |
|
11 | 10 | const resolver = require('./resolver') |
12 | 11 | const pathUtils = require('./utils/path') |
13 | 12 | const detectContentType = require('./utils/content-type') |
14 | 13 |
|
15 | 14 | // TODO: pass path and add Etag and X-Ipfs-Path + tests |
16 | | -const header = (status = 200, statusText = 'OK', headers = {}) => ({ |
| 15 | +const getHeader = (status = 200, statusText = 'OK', headers = {}) => ({ |
17 | 16 | status, |
18 | 17 | statusText, |
19 | 18 | headers |
20 | 19 | }) |
21 | 20 |
|
22 | | -const response = async (ipfsNode, ipfsPath) => { |
23 | | - // handle hash resolve error (simple hash, test for directory now) |
24 | | - const handleResolveError = async (node, path, error) => { |
25 | | - if (error) { |
26 | | - const errorString = error.toString() |
27 | | - |
28 | | - if (errorString.includes('dag node is a directory')) { |
29 | | - try { |
30 | | - const content = await resolver.directory(node, path, error.cid) |
31 | | - // dir render |
32 | | - if (typeof content === 'string') { |
33 | | - return new Response(content, header(200, 'OK', { 'Content-Type': 'text/html' })) |
34 | | - } |
35 | | - |
36 | | - // redirect to dir entry point (index) |
37 | | - return Response.redirect(pathUtils.joinURLParts(path, content[0].Name)) |
38 | | - } catch (error) { |
39 | | - log(error) |
40 | | - return new Response(errorString, header(500, error.toString())) |
41 | | - } |
42 | | - } |
43 | | - |
44 | | - if (errorString.startsWith('Error: no link named')) { |
45 | | - return new Response(errorString, header(404, errorString)) |
46 | | - } |
| 21 | +// handle hash resolve error (simple hash, test for directory now) |
| 22 | +const handleResolveError = async (node, path, error) => { |
| 23 | + const errorString = error.toString() |
47 | 24 |
|
48 | | - if (errorString.startsWith('Error: multihash length inconsistent') || errorString.startsWith('Error: Non-base58 character')) { |
49 | | - return new Response(errorString, header(400, errorString)) |
| 25 | + if (errorString.includes('dag node is a directory')) { |
| 26 | + try { |
| 27 | + const content = await resolver.directory(node, path, error.cid) |
| 28 | + // dir render |
| 29 | + if (typeof content === 'string') { |
| 30 | + return new Response(content, getHeader(200, 'OK', { 'Content-Type': 'text/html' })) |
50 | 31 | } |
51 | 32 |
|
| 33 | + // redirect to dir entry point (index) |
| 34 | + return Response.redirect(pathUtils.joinURLParts(path, content[0].Name)) |
| 35 | + } catch (error) { |
52 | 36 | log(error) |
53 | | - return new Response(errorString, header(500, errorString)) |
| 37 | + return new Response(errorString, getHeader(500, error.toString())) |
54 | 38 | } |
55 | 39 | } |
56 | 40 |
|
| 41 | + if (errorString.startsWith('Error: no link named')) { |
| 42 | + return new Response(errorString, getHeader(404, errorString)) |
| 43 | + } |
| 44 | + |
| 45 | + if (errorString.startsWith('Error: multihash length inconsistent') || errorString.startsWith('Error: Non-base58 character')) { |
| 46 | + return new Response(errorString, getHeader(400, errorString)) |
| 47 | + } |
| 48 | + |
| 49 | + return new Response(errorString, getHeader(500, errorString)) |
| 50 | +} |
| 51 | + |
| 52 | +const getResponse = async (ipfsNode, ipfsPath) => { |
57 | 53 | // remove trailing slash for files if needed |
58 | 54 | if (ipfsPath.endsWith('/')) { |
59 | 55 | return Response.redirect(pathUtils.removeTrailingSlash(ipfsPath)) |
60 | 56 | } |
61 | 57 |
|
62 | 58 | try { |
63 | 59 | const resolvedData = await resolver.cid(ipfsNode, ipfsPath) |
| 60 | + const { source, contentType } = await detectContentType(ipfsPath, ipfsNode.cat(resolvedData.cid)) |
64 | 61 |
|
65 | | - const readableStream = ipfsNode.catReadableStream(resolvedData.cid) |
66 | | - const responseStream = new stream.PassThrough({ highWaterMark: 1 }) |
67 | | - readableStream.pipe(responseStream) |
68 | | - |
69 | | - return new Promise((resolve, reject) => { |
70 | | - readableStream.once('error', (error) => { |
71 | | - if (error) { |
72 | | - log(error) |
73 | | - return resolve(new Response(error.toString(), header(500, 'Error fetching the file'))) |
74 | | - } |
75 | | - }) |
76 | | - |
77 | | - // return only after first chunk being checked |
78 | | - let contentTypeDetected = false |
79 | | - readableStream.on('data', async (chunk) => { |
80 | | - // check mime on first chunk |
81 | | - if (contentTypeDetected) { |
82 | | - return |
83 | | - } |
84 | | - |
85 | | - contentTypeDetected = true |
86 | | - // return Response with mime type |
87 | | - const contentType = detectContentType(ipfsPath, chunk) |
88 | | - |
89 | | - if (typeof Blob === 'undefined') { |
90 | | - return contentType |
91 | | - ? resolve(new Response(responseStream, header(200, 'OK', { 'Content-Type': contentType }))) |
92 | | - : resolve(new Response(responseStream, header())) |
93 | | - } |
94 | | - |
95 | | - try { |
96 | | - const blob = await toBlob(responseStream) |
97 | | - |
98 | | - return contentType |
99 | | - ? resolve(new Response(blob, header(200, 'OK', { 'Content-Type': contentType }))) |
100 | | - : resolve(new Response(blob, header())) |
101 | | - } catch (err) { |
102 | | - return resolve(new Response(err.toString(), header(500, 'Error fetching the file'))) |
103 | | - } |
104 | | - }) |
105 | | - }) |
| 62 | + if (typeof Blob === 'undefined') { |
| 63 | + const responseStream = toStream.readable(toBuffer(source)) |
| 64 | + |
| 65 | + return contentType |
| 66 | + ? new Response(responseStream, getHeader(200, 'OK', { 'Content-Type': contentType })) |
| 67 | + : new Response(responseStream, getHeader()) |
| 68 | + } |
| 69 | + |
| 70 | + try { |
| 71 | + const data = await concat(source) |
| 72 | + const blob = new Blob([data.slice()]) |
| 73 | + |
| 74 | + return contentType |
| 75 | + ? new Response(blob, getHeader(200, 'OK', { 'Content-Type': contentType })) |
| 76 | + : new Response(blob, getHeader()) |
| 77 | + } catch (err) { |
| 78 | + return new Response(err.toString(), getHeader(500, 'Error fetching the file')) |
| 79 | + } |
106 | 80 | } catch (error) { |
107 | 81 | log(error) |
108 | 82 | return handleResolveError(ipfsNode, ipfsPath, error) |
109 | 83 | } |
110 | 84 | } |
111 | 85 |
|
112 | 86 | module.exports = { |
113 | | - getResponse: response, |
114 | | - resolver: resolver |
| 87 | + getResponse, |
| 88 | + resolver |
115 | 89 | } |
0 commit comments