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

Commit a06b5b2

Browse files
lidelAlan Shaw
authored and
Alan Shaw
committed
feat(gateway): X-Ipfs-Path, Etag, Cache-Control, Suborigin
Return the same headers as HTTP Gateway exposed by go-ipfs: - X-Ipfs-Path: requested IPFS Path of returned resource - Etag: multihash of returned payload - Cache-Control: disable cache for directory listings and errors, enable heavy caching for immutable assets from /ipfs/ namespace - Suborigin: use root CID in base32 and literal prefix to conform to current suborigin spec License: MIT Signed-off-by: Marcin Rataj <[email protected]>
1 parent 2790e6d commit a06b5b2

File tree

3 files changed

+85
-3
lines changed

3 files changed

+85
-3
lines changed

src/http/gateway/resources/gateway.js

+27
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const toPull = require('stream-to-pull-stream')
88
const fileType = require('file-type')
99
const mime = require('mime-types')
1010
const Stream = require('readable-stream')
11+
const CID = require('cids')
1112

1213
const { resolver } = require('ipfs-http-response')
1314
const PathUtils = require('../utils/path')
@@ -41,6 +42,7 @@ module.exports = {
4142
ref: `/ipfs/${request.params.cid}`
4243
})
4344
},
45+
4446
handler: (request, reply) => {
4547
const ref = request.pre.args.ref
4648
const ipfs = request.server.app.ipfs
@@ -120,6 +122,15 @@ module.exports = {
120122

121123
let response = reply(stream2).hold()
122124

125+
// Etag maps directly to an identifier for a specific version of a resource
126+
// TODO: change to .cid.toBaseEncodedString() after switch to new js-ipfs-http-response
127+
response.header('Etag', `"${data.multihash}"`)
128+
129+
// Set headers specific to the immutable namespace
130+
if (ref.startsWith('/ipfs/')) {
131+
response.header('Cache-Control', 'public, max-age=29030400, immutable')
132+
}
133+
123134
pull(
124135
toPull.source(stream),
125136
pull.through((chunk) => {
@@ -148,5 +159,21 @@ module.exports = {
148159
)
149160
}
150161
})
162+
},
163+
164+
afterHandler: (request, reply) => {
165+
const response = request.response
166+
if (response.statusCode === 200) {
167+
const ref = request.pre.args.ref
168+
response.header('X-Ipfs-Path', ref)
169+
if (ref.startsWith('/ipfs/')) {
170+
const rootCid = ref.split('/')[2]
171+
const ipfsOrigin = new CID(rootCid).toV1().toBaseEncodedString('base32')
172+
response.header('Suborigin', 'ipfs000' + ipfsOrigin)
173+
}
174+
// TODO: we don't have case-insensitive solution for /ipns/ yet (https://github.com/ipfs/go-ipfs/issues/5287)
175+
}
176+
reply.continue()
151177
}
178+
152179
}

src/http/gateway/routes/gateway.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ module.exports = (server) => {
1212
pre: [
1313
{ method: resources.gateway.checkCID, assign: 'args' }
1414
],
15-
handler: resources.gateway.handler
15+
handler: resources.gateway.handler,
16+
ext: {
17+
onPostHandler: { method: resources.gateway.afterHandler }
18+
}
1619
}
1720
})
1821
}

test/gateway/index.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('HTTP Gateway', function () {
9393
(cb) => {
9494
const expectedMultihash = 'QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o'
9595

96-
http.api.node.files.add(Buffer.from('hello world' + '\n'), (err, res) => {
96+
http.api.node.files.add(Buffer.from('hello world' + '\n'), {cidVersion: 0}, (err, res) => {
9797
expect(err).to.not.exist()
9898
const file = res[0]
9999
expect(file.path).to.equal(expectedMultihash)
@@ -144,6 +144,10 @@ describe('HTTP Gateway', function () {
144144
}, (res) => {
145145
expect(res.statusCode).to.equal(400)
146146
expect(res.result.Message).to.be.a('string')
147+
expect(res.headers['cache-control']).to.equal('no-cache')
148+
expect(res.headers['etag']).to.equal(undefined)
149+
expect(res.headers['x-ipfs-path']).to.equal(undefined)
150+
expect(res.headers['suborigin']).to.equal(undefined)
147151
done()
148152
})
149153
})
@@ -155,21 +159,49 @@ describe('HTTP Gateway', function () {
155159
}, (res) => {
156160
expect(res.statusCode).to.equal(400)
157161
expect(res.result.Message).to.be.a('string')
162+
expect(res.headers['cache-control']).to.equal('no-cache')
163+
expect(res.headers['etag']).to.equal(undefined)
164+
expect(res.headers['x-ipfs-path']).to.equal(undefined)
165+
expect(res.headers['suborigin']).to.equal(undefined)
158166
done()
159167
})
160168
})
161169

162-
it('valid hash', (done) => {
170+
it('valid CIDv0', (done) => {
163171
gateway.inject({
164172
method: 'GET',
165173
url: '/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o'
166174
}, (res) => {
167175
expect(res.statusCode).to.equal(200)
168176
expect(res.rawPayload).to.eql(Buffer.from('hello world' + '\n'))
169177
expect(res.payload).to.equal('hello world' + '\n')
178+
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
179+
expect(res.headers['etag']).to.equal('"QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o"')
180+
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')
181+
expect(res.headers['suborigin']).to.equal('ipfs000bafybeicg2rebjoofv4kbyovkw7af3rpiitvnl6i7ckcywaq6xjcxnc2mby')
182+
183+
done()
184+
})
185+
})
186+
187+
/* TODO when support for CIDv1 lands
188+
it('valid CIDv1', (done) => {
189+
gateway.inject({
190+
method: 'GET',
191+
url: '/ipfs/TO-DO'
192+
}, (res) => {
193+
expect(res.statusCode).to.equal(200)
194+
expect(res.rawPayload).to.eql(Buffer.from('hello world' + '\n'))
195+
expect(res.payload).to.equal('hello world' + '\n')
196+
expect(res.headers['etag']).to.equal(TO-DO)
197+
expect(res.headers['x-ipfs-path']).to.equal(TO-DO)
198+
expect(res.headers['suborigin']).to.equal(TO-DO)
199+
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
200+
170201
done()
171202
})
172203
})
204+
*/
173205

174206
it('stream a large file', (done) => {
175207
let bigFileHash = 'Qme79tX2bViL26vNjPsF3DP1R9rMKMvnPYJiKTTKPrXJjq'
@@ -193,6 +225,10 @@ describe('HTTP Gateway', function () {
193225
}, (res) => {
194226
expect(res.statusCode).to.equal(200)
195227
expect(res.headers['content-type']).to.equal('image/jpeg')
228+
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + kitty)
229+
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
230+
expect(res.headers['etag']).to.equal('"Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u"')
231+
expect(res.headers['suborigin']).to.equal('ipfs000bafybeidsg6t7ici2osxjkukisd5inixiunqdpq2q5jy4a2ruzdf6ewsqk4')
196232

197233
let fileSignature = fileType(res.rawPayload)
198234
expect(fileSignature.mime).to.equal('image/jpeg')
@@ -239,6 +275,10 @@ describe('HTTP Gateway', function () {
239275
}, (res) => {
240276
expect(res.statusCode).to.equal(200)
241277
expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')
278+
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir)
279+
expect(res.headers['cache-control']).to.equal('no-cache')
280+
expect(res.headers['etag']).to.equal(undefined)
281+
expect(res.headers['suborigin']).to.equal('ipfs000bafybeidsg6t7ici2osxjkukisd5inixiunqdpq2q5jy4a2ruzdf6ewsqk4')
242282

243283
// check if the cat picture is in the payload as a way to check
244284
// if this is an index of this directory
@@ -256,6 +296,11 @@ describe('HTTP Gateway', function () {
256296
url: '/ipfs/' + dir
257297
}, (res) => {
258298
expect(res.statusCode).to.equal(200)
299+
expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')
300+
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir)
301+
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
302+
expect(res.headers['etag']).to.equal('"Qma6665X5k3zti8nKy7gmXK2BndNDSkgmANpV6k3FUjUeg"')
303+
expect(res.headers['suborigin']).to.equal('ipfs000bafybeigccfheqv7upr4k64bkg5b5wiwelunyn2l2rbirmm43m34lcpuqqe')
259304
expect(res.rawPayload).to.deep.equal(directoryContent['index.html'])
260305
done()
261306
})
@@ -269,6 +314,11 @@ describe('HTTP Gateway', function () {
269314
url: '/ipfs/' + dir
270315
}, (res) => {
271316
expect(res.statusCode).to.equal(200)
317+
expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')
318+
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir)
319+
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
320+
expect(res.headers['etag']).to.equal('"QmUBKGqJWiJYMrNed4bKsbo1nGYGmY418WCc2HgcwRvmHc"')
321+
expect(res.headers['suborigin']).to.equal('ipfs000bafybeigccfheqv7upr4k64bkg5b5wiwelunyn2l2rbirmm43m34lcpuqqe')
272322
expect(res.rawPayload).to.deep.equal(directoryContent['nested-folder/nested.html'])
273323
done()
274324
})
@@ -283,6 +333,7 @@ describe('HTTP Gateway', function () {
283333
}, (res) => {
284334
expect(res.statusCode).to.equal(301)
285335
expect(res.headers['location']).to.equal('/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/')
336+
expect(res.headers['x-ipfs-path']).to.equal(undefined)
286337
done()
287338
})
288339
})
@@ -296,6 +347,7 @@ describe('HTTP Gateway', function () {
296347
}, (res) => {
297348
expect(res.statusCode).to.equal(302)
298349
expect(res.headers['location']).to.equal('/ipfs/QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi/index.html')
350+
expect(res.headers['x-ipfs-path']).to.equal(undefined)
299351
done()
300352
})
301353
})

0 commit comments

Comments
 (0)