Preserve original response's headers from server while fetching from another url

I am trying to conditionally proxy post calls to an external api server depending on the url path.

Basically I am trying to mimic the Ngnix’s proxy_pass feature.

I am able to get the response with the body, status, etc. being correct and coming from the server as expected.

What is not expected are the headers and cookies. The api server also sets cookies and headers while sending the response, but these seem either swallowed or overridden by Cloudflare. I need the original client to get those headers as well, not only the body content and status.

These are the actual headers I am getting:

{
  "access-control-allow-origin": "*", // set by me after getting the response
  "access-control-expose-headers": "*", // same as above
  "allow": "GET, POST, HEAD, OPTIONS",
  "cf-cache-status": "DYNAMIC",
  "cf-ray": "6404418a31cbfc69-ZAG",
  "cf-request-id": "0976874a620000fc694d977000000001",
  "connection": "keep-alive",
  "content-length": "140",
  "content-type": "application/json",
  "date": "Thu, 15 Apr 2021 09:48:03 GMT",
  "expect-ct": "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"",
  "server": "cloudflare",
  "vary": "Origin", // set by me after getting the response
  "x-powered-by": "Express" // same as above
}

This the main listener:

addEventListener('fetch', event => {
  try {
    var url = new URL(event.request.url)
    if (
      url.pathname.startsWith('/' + PROXYPATH + '/') ||
      url.pathname.startsWith(PROXYPATH + '/') ||
      url.pathname === '/' + PROXYPATH ||
      url.pathname === PROXYPATH
    ) {
      handleRequest(event, url)
    } else {
      event.respondWith(handleEvent(event))
    }
  } catch (e) {
    if (DEBUG) {
      console.log({ ERROR: JSON.stringify(e, null, 2) })
      return event.respondWith(
        new Response(e.message || e.toString(), {
          status: 500,
        }),
      )
    }
    event.respondWith(new Response('Internal Error', { status: 500 }))
  }
})

This is the Proxy Handler

async function handleRequest(event, url) {
  // Change URL from public URL to use the origin URL
  var originUrl = url
    .toString()
    .replace('https://' + DOMAIN, 'https://' + ORIGIN)
  event.passThroughOnException()
  event.respondWith(
    (async function() {      
      const request = new Request(originUrl, event.request)
      request.headers.set('Origin', new URL(originUrl).origin)
      let response = await fetch(originUrl, {
        headers: {
          ...Object.fromEntries(event.request.headers),
          'X-Forwarded-Host': url.hostname,
          'X-Forwarded-Proto': url.protocol,
          'X-Forwarded-For': event.request.headers.get('CF-Connecting-IP'),
          'X-Real-Ip': event.request.headers.get('CF-Connecting-IP'),
        },
        body: event.request.body,
        method: event.request.method,
        redirect: 'manual',
      })

      // even when I do just "return response" it will not send the original response headers
     // so I am trying to hack it here with no luck
      response = new Response(response.body, response)

      // Set CORS headers
      response.headers.set('Access-Control-Allow-Origin', '*')
      response.headers.set('Access-Control-Expose-Headers', '*')

      // Append to/Add Vary header so browser will cache response correctly
      response.headers.append('Vary', 'Origin')
      return response
    })(),
  )
}

Before posting here I’ve tried for a while and looked to many alternative solutions, also those that I found in the examples as well as in this forurm.

Any idea on how I can preserve the server’s original headers while fetching?

It has been my fault actually where the browser was not sending a custom header that is key to trigger the server’s additional headers :+1:

Everything is working great now.

1 Like