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
Show file tree
Hide file tree
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
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}"`)
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) => {
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