Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 3ded576

Browse files
author
Alan Shaw
authored
fix: streaming cat over http api (#1760)
`/api/v0/cat` calls `ipfs.cat`, but `ipfs.cat` returns a buffer of file output. This changes `/api/v0/cat` to actually stream output by calling `ipfs.catPullStream` instead. It uses `pull-pushable` in order to catch an initial error in the stream before it starts flowing and instead return a plain JSON response with an appropriate HTTP status. This isn't ideal as it means there's no backpressure - if the consumer doesn't consume fast enough then data will start to get buffered into memory. However this is significantly better than buffering _everything_ into memory before replying. License: MIT Signed-off-by: Alan Shaw <[email protected]>
1 parent 6bfa4dd commit 3ded576

File tree

2 files changed

+49
-27
lines changed

2 files changed

+49
-27
lines changed

src/core/components/files-regular.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,21 +232,28 @@ module.exports = function (self) {
232232

233233
pull(
234234
exporter(ipfsPath, self._ipld, options),
235+
pull.filter(filterFile),
236+
pull.take(1),
235237
pull.collect((err, files) => {
236-
if (err) { return d.abort(err) }
237-
if (files && files.length > 1) {
238-
files = files.filter(filterFile)
238+
if (err) {
239+
return d.abort(err)
239240
}
240-
if (!files || !files.length) {
241+
242+
if (!files.length) {
241243
return d.abort(new Error('No such file'))
242244
}
243245

244246
const file = files[0]
245-
const content = file.content
246-
if (!content && file.type === 'dir') {
247+
248+
if (!file.content && file.type === 'dir') {
247249
return d.abort(new Error('this dag node is a directory'))
248250
}
249-
d.resolve(content)
251+
252+
if (!file.content) {
253+
return d.abort(new Error('this dag node has no content'))
254+
}
255+
256+
d.resolve(file.content)
250257
})
251258
)
252259

src/http/api/resources/files-regular.js

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const toStream = require('pull-stream-to-stream')
1414
const abortable = require('pull-abortable')
1515
const Joi = require('joi')
1616
const ndjson = require('pull-ndjson')
17+
const { PassThrough } = require('readable-stream')
1718

1819
exports = module.exports
1920

@@ -79,27 +80,41 @@ exports.cat = {
7980
const options = request.pre.args.options
8081
const ipfs = request.server.app.ipfs
8182

82-
ipfs.cat(key, options, (err, stream) => {
83-
if (err) {
84-
log.error(err)
85-
if (err.message === 'No such file') {
86-
reply({ Message: 'No such file', Code: 0, Type: 'error' }).code(500)
87-
} else {
88-
reply({ Message: 'Failed to cat file: ' + err, Code: 0, Type: 'error' }).code(500)
89-
}
90-
return
91-
}
83+
let pusher
84+
let started = false
9285

93-
// hapi is not very clever and throws if no
94-
// - _read method
95-
// - _readableState object
96-
// are there :(
97-
if (!stream._read) {
98-
stream._read = () => {}
99-
stream._readableState = {}
100-
}
101-
return reply(stream).header('X-Stream-Output', '1')
102-
})
86+
pull(
87+
ipfs.catPullStream(key, options),
88+
pull.drain(
89+
chunk => {
90+
if (!started) {
91+
started = true
92+
pusher = pushable()
93+
reply(toStream.source(pusher).pipe(new PassThrough()))
94+
.header('X-Stream-Output', '1')
95+
}
96+
pusher.push(chunk)
97+
},
98+
err => {
99+
if (err) {
100+
log.error(err)
101+
102+
// We already started flowing, abort the stream
103+
if (started) {
104+
return pusher.end(err)
105+
}
106+
107+
const msg = err.message === 'No such file'
108+
? err.message
109+
: 'Failed to cat file: ' + err
110+
111+
return reply({ Message: msg, Code: 0, Type: 'error' }).code(500)
112+
}
113+
114+
pusher.end()
115+
}
116+
)
117+
)
103118
}
104119
}
105120

0 commit comments

Comments
 (0)