Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Accept header in @helia/verified-fetch #438

Merged
merged 10 commits into from
Feb 22, 2024

Conversation

achingbrain
Copy link
Member

Let users get raw data back from CIDs that would otherwise trigger decoding as JSON or CBOR etc by specifying an Accept header.

const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/octet-stream'
  }
})
console.info(res.headers.get('accept')) // application/octet-stream

Make sure the content-type matches the accept header:

const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/vnd.ipld.raw'
  }
})
console.info(res.headers.get('accept')) // application/vnd.ipld.raw

Support multiple values, match the first one:

const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/vnd.ipld.raw, application/octet-stream, */*'
  }
})
console.info(res.headers.get('accept')) // application/vnd.ipld.raw

If they specify an Accept header we can't honour, one that doesn't match the eventual content type, or one returned by the content type parser return a 406:

const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/what-even-is-this'
  }
})
console.info(res.status) // 406

Change checklist

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation if necessary (this includes comments as well)
  • I have added tests that prove my fix is effective or that my feature works

Let users get raw data back from CIDs that would otherwise trigger
decoding as JSON or CBOR etc by specifying an `Accept` header.

```typescript
const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/octet-stream'
  }
})
console.info(res.headers.get('accept')) // application/octet-stream
```

Make sure the content-type matches the accept header:

```typescript
const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/vnd.ipld.raw'
  }
})
console.info(res.headers.get('accept')) // application/vnd.ipld.raw
```

Support multiple values, match the first one:

```typescript
const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/vnd.ipld.raw, application/octet-stream, */*'
  }
})
console.info(res.headers.get('accept')) // application/vnd.ipld.raw
```

If they specify an Accept header we can't honor, return a [406](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406):

```typescript
const res = await verifiedFetch(cid, {
  headers: {
    accept: 'application/what-even-is-this'
  }
})
console.info(res.status) // 406
```
@achingbrain achingbrain requested a review from a team as a code owner February 16, 2024 16:20

const resp = await verifiedFetch.fetch(cid, {
headers: {
accept: 'application/what-even-is-this, */*'
Copy link
Member

Choose a reason for hiding this comment

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

nit: we were not testing with ;q=, below adds what Firefox sends, should be enough as a regression test:

Suggested change
accept: 'application/what-even-is-this, */*'
accept: 'application/what-even-is-this,text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'

Copy link
Member

@2color 2color left a comment

Choose a reason for hiding this comment

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

Looks good.

Nice to have the challenge I brought up addressed by this. 💯

@achingbrain
Copy link
Member Author

@lidel @2color please take another look - I've added a lot more tests, support for wildcards and q-factor and also the IPLD serialized type conversions from https://specs.ipfs.tech/http-gateways/path-gateway/#accept-request-header

Copy link
Member

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

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

a few comments, but we could fix those later if necessary. this is a huge net win.

@@ -0,0 +1,117 @@
import { code as dagCborCode } from '@ipld/dag-cbor'
import { code as dagJsonCode } from '@ipld/dag-json'
import type { RequestFormatShorthand } from '../types.js'
Copy link
Member

Choose a reason for hiding this comment

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

We could move requestFormatShorthand in here as well, or merge with FORMATS

packages/verified-fetch/src/index.ts Outdated Show resolved Hide resolved
packages/verified-fetch/test/accept-header.spec.ts Outdated Show resolved Hide resolved
expect(output).to.deep.equal(obj)
})

it('should transform DAG-JSON to DAG-CBOR', async () => {
Copy link
Member

Choose a reason for hiding this comment

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

baller

Comment on lines 76 to 90
function notSupportedResponse (body?: BodyInit | null): Response {
return new Response(body, {
const response = new Response(body, {
status: 501,
statusText: 'Not Implemented'
})
response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
return response
}

function notAcceptableResponse (body?: BodyInit | null): Response {
return new Response(body, {
status: 406,
statusText: '406 Not Acceptable'
})
}
Copy link
Member

Choose a reason for hiding this comment

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

we might want to move these into their own file soon. this file is getting excessive.

packages/verified-fetch/src/verified-fetch.ts Outdated Show resolved Hide resolved
return response
}

private async handleDagCbor ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
private async handleDagCbor ({ cid, path, accept, options }: FetchHandlerFunctionArg): Promise<Response> {
Copy link
Member

Choose a reason for hiding this comment

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

we might need to break these functions out at some point as well.

packages/verified-fetch/src/verified-fetch.ts Outdated Show resolved Hide resolved
Comment on lines +297 to +299
if (ipfsRoots != null) {
response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
}
Copy link
Member

Choose a reason for hiding this comment

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

we will likely need to pull this out of handleDagPb so we can set roots for others as well

Copy link
Member

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

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

a few comments, but we could fix those later if necessary. this is a huge net win.

achingbrain and others added 4 commits February 22, 2024 09:21
Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com>
Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com>
@achingbrain achingbrain merged commit 7c3ce21 into main Feb 22, 2024
18 checks passed
@achingbrain achingbrain deleted the feat/handle-accept-header-for-raw-types branch February 22, 2024 09:56
}
})
expect(resp.status).to.equal(200)
expect(resp.headers.get('content-type')).to.equal('application/json')
Copy link
Member

Choose a reason for hiding this comment

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

This is pretty counter intuitive, but my understanding is that the order matters and that when processing */* the default mime-type is returned, which according to the map is application/json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants