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

feat(gateway): X-Ipfs-Path, Etag, Cache-Control, Suborigin #1537

Merged
merged 1 commit into from
Oct 15, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/http/gateway/resources/gateway.js
Original file line number Diff line number Diff line change
@@ -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')
@@ -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
@@ -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}"`)
Copy link
Member

@alanshaw alanshaw Sep 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is data.multihash already a string?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is a string already. Etag spec requires wrapping it with ""

Note: the source of this value may change after ipfs/js-ipfs-http-response#9 lands – it will add CID object as data.cid. It just needs to be a unique string, so we can use cid or a raw multihash – both are fine for this.


// 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) => {
@@ -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
@@ -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
@@ -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)
@@ -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()
})
})
@@ -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'
@@ -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')
@@ -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
@@ -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()
})
@@ -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()
})
@@ -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()
})
})
@@ -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()
})
})