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

Commit e5a5f5d

Browse files
committed
Adds "files add" http route.
1 parent d0d2d34 commit e5a5f5d

File tree

6 files changed

+264
-0
lines changed

6 files changed

+264
-0
lines changed

src/http-api/resources/files.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use strict'
2+
3+
const bs58 = require('bs58')
4+
const ndjson = require('ndjson')
5+
const Readable = require('stream').Readable
6+
const multipart = require('ipfs-multipart')
7+
const debug = require('debug')
8+
const log = debug('http-api:files')
9+
log.error = debug('http-api:files:error')
10+
11+
exports = module.exports
12+
13+
exports.add = {
14+
handler: (request, reply) => {
15+
if (!request.payload) {
16+
return reply('Array, Buffer, or String is required').code(400).takeover()
17+
}
18+
19+
const parser = multipart.reqParser(request.payload)
20+
21+
var file = false
22+
var written = 0
23+
24+
var serialize = ndjson.serialize()
25+
// hapi doesn't permit object streams: http://hapijs.com/api#replyerr-result
26+
serialize._readableState.objectMode = false
27+
28+
var fileAdder = request.server.app.ipfs.files.add()
29+
30+
fileAdder.on('data', (file) => {
31+
serialize.write({
32+
Name: file.path,
33+
Hash: bs58.encode(file.multihash).toString()
34+
})
35+
written++
36+
})
37+
38+
fileAdder.on('end', () => {
39+
if (written === 0 && file) {
40+
return reply({
41+
Message: 'Failed to add files',
42+
Code: 0
43+
}).code(500)
44+
} else {
45+
serialize.end()
46+
return reply(serialize)
47+
.header('x-chunked-output', '1')
48+
.header('content-type', 'application/json')
49+
}
50+
})
51+
52+
parser.on('file', (fileName, fileStream) => {
53+
var rs = new Readable()
54+
var init = false
55+
rs._read = () => {
56+
if (init) {
57+
return
58+
}
59+
init = true
60+
}
61+
fileStream.on('data', (data) => {
62+
rs.push(data)
63+
file = true
64+
})
65+
fileStream.on('end', () => {
66+
rs.push(null)
67+
var filePair = {
68+
path: fileName,
69+
stream: rs
70+
}
71+
fileAdder.write(filePair)
72+
})
73+
})
74+
75+
parser.on('end', () => {
76+
if (!file) {
77+
return reply("File argument 'data' is required").code(400).takeover()
78+
}
79+
fileAdder.end()
80+
})
81+
}
82+
}

src/http-api/resources/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ exports.config = require('./config')
99
exports.block = require('./block')
1010
exports.swarm = require('./swarm')
1111
exports.bitswap = require('./bitswap')
12+
exports.files = require('./files')

src/http-api/routes/files.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
const resources = require('./../resources')
4+
5+
module.exports = (server) => {
6+
const api = server.select('API')
7+
8+
api.route({
9+
method: '*',
10+
path: '/api/v0/add',
11+
config: {
12+
payload: {
13+
parse: false,
14+
output: 'stream'
15+
},
16+
handler: resources.files.add.handler
17+
}
18+
})
19+
}

src/http-api/routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = (server) => {
88
require('./object')(server)
99
// require('./repo')(server)
1010
require('./config')(server)
11+
require('./files')(server)
1112
require('./swarm')(server)
1213
require('./bitswap')(server)
1314
}

test/http-api-tests/test-files.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const expect = require('chai').expect
5+
const APIctl = require('ipfs-api')
6+
const fs = require('fs')
7+
const FormData = require('form-data')
8+
const streamToPromise = require('stream-to-promise')
9+
const Readable = require('stream').Readable
10+
const http = require('http')
11+
12+
function singleFileServer (filename) {
13+
return http.createServer(function (req, res) {
14+
fs.createReadStream(filename).pipe(res)
15+
})
16+
}
17+
18+
module.exports = (httpAPI) => {
19+
describe('files', () => {
20+
describe('api', () => {
21+
let api
22+
23+
it('api', () => {
24+
api = httpAPI.server.select('API')
25+
})
26+
27+
describe('/files/add', () => {
28+
it('returns 400 if no tuple is provided', (done) => {
29+
const form = new FormData()
30+
const headers = form.getHeaders()
31+
32+
streamToPromise(form).then((payload) => {
33+
api.inject({
34+
method: 'POST',
35+
url: '/api/v0/add',
36+
headers: headers,
37+
payload: payload
38+
}, (res) => {
39+
expect(res.statusCode).to.equal(400)
40+
done()
41+
})
42+
})
43+
})
44+
45+
it('adds a file', (done) => {
46+
const form = new FormData()
47+
const filePath = 'test/test-data/node.json'
48+
form.append('file', fs.createReadStream(filePath))
49+
const headers = form.getHeaders()
50+
51+
streamToPromise(form).then((payload) => {
52+
api.inject({
53+
method: 'POST',
54+
url: '/api/v0/add',
55+
headers: headers,
56+
payload: payload
57+
}, (res) => {
58+
expect(res.statusCode).to.equal(200)
59+
done()
60+
})
61+
})
62+
})
63+
64+
it('adds multiple files', (done) => {
65+
const form = new FormData()
66+
const filePath = 'test/test-data/hello'
67+
const filePath2 = 'test/test-data/otherconfig'
68+
form.append('file', fs.createReadStream(filePath))
69+
form.append('file', fs.createReadStream(filePath2))
70+
const headers = form.getHeaders()
71+
72+
streamToPromise(form).then((payload) => {
73+
api.inject({
74+
method: 'POST',
75+
url: '/api/v0/add',
76+
headers: headers,
77+
payload: payload
78+
}, (res) => {
79+
expect(res.statusCode).to.equal(200)
80+
done()
81+
})
82+
})
83+
})
84+
})
85+
})
86+
87+
describe('using js-ipfs-api', () => {
88+
var ctl
89+
90+
it('start IPFS API ctl', (done) => {
91+
ctl = APIctl('/ip4/127.0.0.1/tcp/6001')
92+
done()
93+
})
94+
95+
describe('ipfs.add', () => {
96+
it('adds two files under a chunk Size', (done) => {
97+
const rs = new Readable()
98+
const rs2 = new Readable()
99+
var files = []
100+
const buffered = fs.readFileSync('test/test-data/hello')
101+
const buffered2 = fs.readFileSync('test/test-data/otherconfig')
102+
rs.push(buffered)
103+
rs.push(null)
104+
rs2.push(buffered2)
105+
rs2.push(null)
106+
const filePair = {path: 'hello', content: rs}
107+
const filePair2 = {path: 'otherconfig', content: rs2}
108+
files.push(filePair)
109+
files.push(filePair2)
110+
111+
ctl.add(files, (err, res) => {
112+
expect(err).to.not.exist
113+
expect(res[0].Name).to.equal('hello')
114+
expect(res[0].Hash).to.equal('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')
115+
expect(res[1].Name).to.equal('otherconfig')
116+
expect(res[1].Hash).to.equal('QmayedZNznnEbHtyfjeQvvt29opSLjYjLtLqwfwSWq28ds')
117+
done()
118+
})
119+
})
120+
121+
it('adds a large file > a chunk', (done) => {
122+
const rs = new Readable()
123+
var files = []
124+
const buffered = fs.readFileSync('test/test-data/1.2MiB.txt')
125+
rs.push(buffered)
126+
rs.push(null)
127+
const filePair = {path: '1.2MiB.txt', content: rs}
128+
files.push(filePair)
129+
130+
ctl.add(filePair, (err, res) => {
131+
expect(err).to.not.exist
132+
expect(res[0].Name).to.equal('1.2MiB.txt')
133+
expect(res[0].Hash).to.equal('QmW7BDxEbGqxxSYVtn3peNPQgdDXbWkoQ6J1EFYAEuQV3Q')
134+
done()
135+
})
136+
})
137+
138+
it('adds a buffer', (done) => {
139+
const buffer = new Buffer('hello world')
140+
ctl.add(buffer, (err, res) => {
141+
expect(err).to.not.exist
142+
expect(res[0].Hash).to.equal('Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD')
143+
done()
144+
})
145+
})
146+
147+
it('adds a url', (done) => {
148+
var server = singleFileServer('test/test-data/1.2MiB.txt')
149+
server.listen(2913, function () {
150+
ctl.add('http://localhost:2913/', (err, res) => {
151+
expect(err).to.not.exist
152+
const added = res[0] != null ? res[0] : res
153+
expect(added).to.have.a.property('Hash', 'QmW7BDxEbGqxxSYVtn3peNPQgdDXbWkoQ6J1EFYAEuQV3Q')
154+
done()
155+
})
156+
})
157+
})
158+
})
159+
})
160+
})
161+
}

test/test-data/1.2MiB.txt

1.2 MB
Binary file not shown.

0 commit comments

Comments
 (0)