'How to alter the headers of a Request?

Is it possible to alter the headers of the Request object that is received by the fetch event?

Two attempts:

  1. Modify existing headers:

    self.addEventListener('fetch', function (event) {
      event.request.headers.set("foo", "bar");
      event.respondWith(fetch(event.request));
    });
    

    Fails with Failed to execute 'set' on 'Headers': Headers are immutable.

  2. Create new Request object:

    self.addEventListener('fetch', function (event) {
      var req = new Request(event.request, {
        headers: { "foo": "bar" }
      });
      event.respondWith(fetch(req));
    });
    

    Fails with Failed to construct 'Request': Cannot construct a Request with a Request whose mode is 'navigate' and a non-empty RequestInit.

(See also How to alter the headers of a Response?)



Solution 1:[1]

Creating a new request object works as long as you set all the options:

// request is event.request sent by browser here 
var req = new Request(request.url, {
    method: request.method,
    headers: request.headers,
    mode: 'same-origin', // need to set this properly
    credentials: request.credentials,
    redirect: 'manual'   // let browser handle redirects
});

You cannot use the original mode if it is navigate (that's why you were getting an exception) and you probably want to pass redirection back to browser to let it change its URL instead of letting fetch handle it.

Make sure you don't set body on GET requests - fetch does not like it, but browsers sometimes generate GET requests with the body when responding to redirects from POST requests. fetch does not like it.

Solution 2:[2]

You can create a new request based on the original one and override the headers:

new Request(originalRequest, {
  headers: {
    ...originalRequest.headers,
    foo: 'bar'
  }
})

See also: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request

Solution 3:[3]

Have you tried with a solution similar to the one in the question you mention (How to alter the headers of a Response?)?

In the Service Worker Cookbook, we're manually copying Request objects to store them in IndexedDB (https://serviceworke.rs/request-deferrer_service-worker_doc.html). It's for a different reason (we wanted to store them in a Cache, but we can't store POST requests because of https://github.com/slightlyoff/ServiceWorker/issues/693), but it should be applicable for what you want to do as well.

// Serialize is a little bit convolved due to headers is not a simple object.
function serialize(request) {
  var headers = {};
  // `for(... of ...)` is ES6 notation but current browsers supporting SW, support this
  // notation as well and this is the only way of retrieving all the headers.
  for (var entry of request.headers.entries()) {
    headers[entry[0]] = entry[1];
  }
  var serialized = {
    url: request.url,
    headers: headers,
    method: request.method,
    mode: request.mode,
    credentials: request.credentials,
    cache: request.cache,
    redirect: request.redirect,
    referrer: request.referrer
  };



  // Only if method is not `GET` or `HEAD` is the request allowed to have body.
  if (request.method !== 'GET' && request.method !== 'HEAD') {
    return request.clone().text().then(function(body) {
      serialized.body = body;
      return Promise.resolve(serialized);
    });
  }
  return Promise.resolve(serialized);
}

// Compared, deserialize is pretty simple.
function deserialize(data) {
  return Promise.resolve(new Request(data.url, data));
}

Solution 4:[4]

If future readers have a need to also delete keys in the immutable Request/Response headers and also want high fidelity to the immutable headers, you can effectively clone the Header object:

const mutableHeaders = new Headers();
immutableheaders.forEach((value, key, parent) => mutableHeaders.set(key, value));

mutableHeaders.delete('content-encoding');
mutableHeaders.delete('vary');
mutableHeaders['host'] = 'example.com';
// etc.

You can then create a new Request and pass in your mutableHeaders.

This is preferred to the accepted answer because if you have the need to proxy a Request, you don't want to manually specify every possible header while including the Cloudflare, AWS, Azure, Google, etc. custom CDN headers.


Background Info

The reason why the headers are immutable or read-only in a Request is because:

interface Request extends Body {
  readonly cache: RequestCache;
  readonly credentials: RequestCredentials;
  readonly destination: RequestDestination;
  readonly headers: Headers;
  readonly integrity: string;
    ...

The interface for Headers is:

interface Headers {
    append(name: string, value: string): void;
    delete(name: string): void;
    get(name: string): string | null;
    has(name: string): boolean;
    set(name: string, value: string): void;
    forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any): void;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 pirxpilot
Solution 2 Brad
Solution 3 Community
Solution 4 Drakes