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

Commit

Permalink
feat(gateway): X-Ipfs-Path, Etag, Cache-Control, Suborigin
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
lidel authored and Alan Shaw committed Oct 15, 2018
1 parent 9e410c6 commit e8d8d67
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 3 deletions.
27 changes: 27 additions & 0 deletions src/http/gateway/resources/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const toPull = require('stream-to-pull-stream')
const fileType = require('file-type')
const mime = require('mime-types')
const Stream = require('readable-stream')
const CID = require('cids')

const { resolver } = require('ipfs-http-response')
const PathUtils = require('../utils/path')
Expand Down Expand Up @@ -41,6 +42,7 @@ module.exports = {
ref: `/ipfs/${request.params.cid}`
})
},

handler: (request, reply) => {
const ref = request.pre.args.ref
const ipfs = request.server.app.ipfs
Expand Down Expand Up @@ -120,6 +122,15 @@ module.exports = {

let response = reply(stream2).hold()

// Etag maps directly to an identifier for a specific version of a resource
// TODO: change to .cid.toBaseEncodedString() after switch to new js-ipfs-http-response
response.header('Etag', `"${data.multihash}"`)

// Set headers specific to the immutable namespace
if (ref.startsWith('/ipfs/')) {
response.header('Cache-Control', 'public, max-age=29030400, immutable')
}

pull(
toPull.source(stream),
pull.through((chunk) => {
Expand Down Expand Up @@ -148,5 +159,21 @@ module.exports = {
)
}
})
},

afterHandler: (request, reply) => {
const response = request.response
if (response.statusCode === 200) {
const ref = request.pre.args.ref
response.header('X-Ipfs-Path', ref)
if (ref.startsWith('/ipfs/')) {
const rootCid = ref.split('/')[2]
const ipfsOrigin = new CID(rootCid).toV1().toBaseEncodedString('base32')
response.header('Suborigin', 'ipfs000' + ipfsOrigin)
}
// TODO: we don't have case-insensitive solution for /ipns/ yet (https://github.com/ipfs/go-ipfs/issues/5287)
}
reply.continue()
}

}
5 changes: 4 additions & 1 deletion src/http/gateway/routes/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ module.exports = (server) => {
pre: [
{ method: resources.gateway.checkCID, assign: 'args' }
],
handler: resources.gateway.handler
handler: resources.gateway.handler,
ext: {
onPostHandler: { method: resources.gateway.afterHandler }
}
}
})
}
56 changes: 54 additions & 2 deletions test/gateway/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('HTTP Gateway', function () {
(cb) => {
const expectedMultihash = 'QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o'

http.api.node.files.add(Buffer.from('hello world' + '\n'), (err, res) => {
http.api.node.files.add(Buffer.from('hello world' + '\n'), {cidVersion: 0}, (err, res) => {
expect(err).to.not.exist()
const file = res[0]
expect(file.path).to.equal(expectedMultihash)
Expand Down Expand Up @@ -144,6 +144,10 @@ describe('HTTP Gateway', function () {
}, (res) => {
expect(res.statusCode).to.equal(400)
expect(res.result.Message).to.be.a('string')
expect(res.headers['cache-control']).to.equal('no-cache')
expect(res.headers['etag']).to.equal(undefined)
expect(res.headers['x-ipfs-path']).to.equal(undefined)
expect(res.headers['suborigin']).to.equal(undefined)
done()
})
})
Expand All @@ -155,21 +159,49 @@ describe('HTTP Gateway', function () {
}, (res) => {
expect(res.statusCode).to.equal(400)
expect(res.result.Message).to.be.a('string')
expect(res.headers['cache-control']).to.equal('no-cache')
expect(res.headers['etag']).to.equal(undefined)
expect(res.headers['x-ipfs-path']).to.equal(undefined)
expect(res.headers['suborigin']).to.equal(undefined)
done()
})
})

it('valid hash', (done) => {
it('valid CIDv0', (done) => {
gateway.inject({
method: 'GET',
url: '/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o'
}, (res) => {
expect(res.statusCode).to.equal(200)
expect(res.rawPayload).to.eql(Buffer.from('hello world' + '\n'))
expect(res.payload).to.equal('hello world' + '\n')
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
expect(res.headers['etag']).to.equal('"QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o"')
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')
expect(res.headers['suborigin']).to.equal('ipfs000bafybeicg2rebjoofv4kbyovkw7af3rpiitvnl6i7ckcywaq6xjcxnc2mby')

done()
})
})

/* TODO when support for CIDv1 lands
it('valid CIDv1', (done) => {
gateway.inject({
method: 'GET',
url: '/ipfs/TO-DO'
}, (res) => {
expect(res.statusCode).to.equal(200)
expect(res.rawPayload).to.eql(Buffer.from('hello world' + '\n'))
expect(res.payload).to.equal('hello world' + '\n')
expect(res.headers['etag']).to.equal(TO-DO)
expect(res.headers['x-ipfs-path']).to.equal(TO-DO)
expect(res.headers['suborigin']).to.equal(TO-DO)
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
done()
})
})
*/

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

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

// check if the cat picture is in the payload as a way to check
// if this is an index of this directory
Expand All @@ -256,6 +296,11 @@ describe('HTTP Gateway', function () {
url: '/ipfs/' + dir
}, (res) => {
expect(res.statusCode).to.equal(200)
expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir)
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
expect(res.headers['etag']).to.equal('"Qma6665X5k3zti8nKy7gmXK2BndNDSkgmANpV6k3FUjUeg"')
expect(res.headers['suborigin']).to.equal('ipfs000bafybeigccfheqv7upr4k64bkg5b5wiwelunyn2l2rbirmm43m34lcpuqqe')
expect(res.rawPayload).to.deep.equal(directoryContent['index.html'])
done()
})
Expand All @@ -269,6 +314,11 @@ describe('HTTP Gateway', function () {
url: '/ipfs/' + dir
}, (res) => {
expect(res.statusCode).to.equal(200)
expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir)
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
expect(res.headers['etag']).to.equal('"QmUBKGqJWiJYMrNed4bKsbo1nGYGmY418WCc2HgcwRvmHc"')
expect(res.headers['suborigin']).to.equal('ipfs000bafybeigccfheqv7upr4k64bkg5b5wiwelunyn2l2rbirmm43m34lcpuqqe')
expect(res.rawPayload).to.deep.equal(directoryContent['nested-folder/nested.html'])
done()
})
Expand All @@ -283,6 +333,7 @@ describe('HTTP Gateway', function () {
}, (res) => {
expect(res.statusCode).to.equal(301)
expect(res.headers['location']).to.equal('/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/')
expect(res.headers['x-ipfs-path']).to.equal(undefined)
done()
})
})
Expand All @@ -296,6 +347,7 @@ describe('HTTP Gateway', function () {
}, (res) => {
expect(res.statusCode).to.equal(302)
expect(res.headers['location']).to.equal('/ipfs/QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi/index.html')
expect(res.headers['x-ipfs-path']).to.equal(undefined)
done()
})
})
Expand Down

0 comments on commit e8d8d67

Please sign in to comment.