ipfs_proxy.js 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. const { createProxyMiddleware } = require('http-proxy-middleware')
  2. const debug = require('debug')('joystream:ipfs-proxy')
  3. const mime = require('mime-types')
  4. /*
  5. For this proxying to work correctly, ensure IPFS HTTP Gateway is configured as a path gateway:
  6. This can be done manually with the following command:
  7. $ ipfs config --json Gateway.PublicGateways '{"localhost": null }'
  8. The implicit default config is below which is not what we want!
  9. $ ipfs config --json Gateway.PublicGateways '{
  10. "localhost": {
  11. "Paths": ["/ipfs", "/ipns"],
  12. "UseSubdomains": true
  13. }
  14. }'
  15. https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#gateway
  16. */
  17. const pathFilter = function (path, req) {
  18. // we get the full path here so it needs to match the path where
  19. // it is used by the openapi initializer
  20. return path.match('^/asset/v0') && (req.method === 'GET' || req.method === 'HEAD')
  21. }
  22. const createPathRewriter = (resolve) => {
  23. return async (_path, req) => {
  24. // we expect the handler was used in openapi/express path with an id in the path:
  25. // "/asset/v0/:id"
  26. // TODO: catch and deal with hash == undefined if content id not found
  27. const contentId = req.params.id
  28. const hash = await resolve(contentId)
  29. return `/ipfs/${hash}`
  30. }
  31. }
  32. const createResolver = (storage) => {
  33. return async (id) => await storage.resolveContentIdWithTimeout(5000, id)
  34. }
  35. const createProxy = (storage, ipfsHttpGatewayUrl) => {
  36. const pathRewrite = createPathRewriter(createResolver(storage))
  37. return createProxyMiddleware(pathFilter, {
  38. // Default path to local IPFS HTTP GATEWAY
  39. target: ipfsHttpGatewayUrl || 'http://localhost:8080/',
  40. pathRewrite,
  41. onProxyRes: function (proxRes, req, res) {
  42. /*
  43. Make sure the reverse proxy used infront of colosss (nginx/caddy) Does not duplicate
  44. these headers to prevent some browsers getting confused especially
  45. with duplicate access-control-allow-origin headers!
  46. 'accept-ranges': 'bytes',
  47. 'access-control-allow-headers': 'Content-Type, Range, User-Agent, X-Requested-With',
  48. 'access-control-allow-methods': 'GET',
  49. 'access-control-allow-origin': '*',
  50. 'access-control-expose-headers': 'Content-Range, X-Chunked-Output, X-Stream-Output',
  51. */
  52. if (proxRes.statusCode === 301) {
  53. // capture redirect when IPFS HTTP Gateway is configured with 'UseDomains':true
  54. // and treat it as an error.
  55. console.error('IPFS HTTP Gateway is configured for "UseSubdomains". Killing stream')
  56. res.status(500).end()
  57. proxRes.destroy()
  58. } else {
  59. // Handle downloading as attachment /asset/v0/:id?download
  60. if (req.query.download) {
  61. const contentId = req.params.id
  62. const contentType = proxRes.headers['content-type']
  63. const ext = mime.extension(contentType) || 'bin'
  64. const fileName = `${contentId}.${ext}`
  65. proxRes.headers['Content-Disposition'] = `attachment; filename=${fileName}`
  66. }
  67. }
  68. },
  69. })
  70. }
  71. module.exports = {
  72. createProxy,
  73. }