Skip to content

Cache can add duplicate if-none-match, if-modified-since & varied headers (case-sensitivity) #4103

@alxndrsn

Description

@alxndrsn

Bug Description

The code at

let headers = {
...opts.headers,
'if-modified-since': new Date(result.cachedAt).toUTCString()
}
if (result.etag) {
headers['if-none-match'] = result.etag
}
if (result.vary) {
headers = {
...headers,
...result.vary
}
}
will overwrite lower-cased user-defined headers, but duplicate non-lower-cased headers.

Reproducible By

import { Agent, fetch, interceptors } from 'undici';
import express from 'express';

const app = express();
app.get('/', (req, res) => {
  res.set('Cache-Control', 'max-age=1');
  res.set('ETag', '"asdf1234"');
  res.send({ requestHeaders:req.headers });
});

const dispatcher = new Agent().compose(interceptors.cache({
  cacheByDefault: Number.MAX_SAFE_INTEGER,
  type: 'private',
}));

let prevEtag;

const s = app.listen(4444, async () => {
  for(let i=1; i<5; ++i) {
    const headers = {};
    if(i == 2) headers['If-None-Match'] = prevEtag;
    if(i == 3) headers['if-none-match'] = prevEtag;

    const res = await fetch('http://localhost:4444', { dispatcher, headers });
  
    prevEtag = res.headers.get('etag');
  
    console.log(i, 'res.body:', (await res.json()).requestHeaders['if-none-match']);

    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  s.close();
});

Output:

1 res.body: undefined
2 res.body: "asdf1234", "asdf1234"
3 res.body: "asdf1234"
4 res.body: "asdf1234"

Expected Behavior

Either:

  1. cache code defers to user-defined header values, or
  2. cache code consistently overwrites user-defined header values

Logs & Screenshots

Environment

$ echo "node: $(node --version)"; echo "undici: $(jq -r .version ./node_modules/undici/package.json)"; echo "express: $(jq -r .version ./node_modules/express/package.json)"
node: v22.14.0
undici: 7.5.0
express: 4.21.2

Additional context

There are some other issues relating to cache & case-sensitivity of headers:

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions