Search code examples
firebasegoogle-cloud-functionsfirebase-hostingfirebase-tools

Firebase emulator CDN caching


How can the firebase emulator (hosting) be configured to respect the cache-control header that is set when a response comes from a Firebase function?

Example (python)

from firebase_functions import https_fn
from firebase_admin import initialize_app

initialize_app()

HTTPS_CACHING_HEADER = {'Cache-Control': 'public, max-age=300, s-maxage=300'}


@https_fn.on_request(region='europe-north1')
def foo(req: https_fn.Request) -> https_fn.Response:
  print("**function was executed**")
  return https_fn.Response(headers=HTTPS_CACHING_HEADER, response="some content")

With given configuration (firebase.json)

{
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": [
        "venv",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log"
      ]
    }
  ],
  "emulators": {
    "functions": {
      "port": 5001
    },
    "hosting": {
      "port": 8888
    },
    "ui": {
      "enabled": true
    },
    "singleProjectMode": true
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/foo",
        "function": {
          "functionId": "foo",
          "region": "europe-north1",
          "pinTag": true
        }
      }
    ]
  }
}

What makes me think this isn't working?

Using the following request multiple times with curl

$ curl http://127.0.0.1:8888/foo -v                                                                                                                    7 ↵
*   Trying 127.0.0.1:8888...
* Connected to 127.0.0.1 (127.0.0.1) port 8888
> GET /foo HTTP/1.1
> Host: 127.0.0.1:8888
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< x-powered-by: Express
< server: gunicorn
< date: Sun, 25 Feb 2024 18:57:37 GMT
< connection: keep-alive
< cache-control: public, max-age=300, s-maxage=300
< content-type: text/html; charset=utf-8
< content-length: 4507
< vary: Accept-Encoding, Authorization, Cookie

the following logs are produced; subsequent calls to the hosted endpoint are triggering the function, which I would expect to not be triggered as the cache-control header is returned in the response.

i  hosting: 127.0.0.1 - - [25/Feb/2024:18:47:59 +0000] "GET /foo HTTP/1.1" 200 4507 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
[hosting] Rewriting /foo to http://127.0.0.1:5001/project-x123/europe-north1/foo for local Function europe-north1/foo
i  functions: Beginning execution of "europe-north1-foo"
>  **function was executed**
i  functions: Finished "europe-north1-foo" in 28.365847ms

i  hosting: 127.0.0.1 - - [25/Feb/2024:18:48:03 +0000] "GET /foo HTTP/1.1" 200 4507 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
[hosting] Rewriting /foo to http://127.0.0.1:5001/project-x123/europe-north1/foo for local Function europe-north1/foo
i  functions: Beginning execution of "europe-north1-foo"
>  **function was executed**
i  functions: Finished "europe-north1-foo" in 11.306171ms

I expected that firebase hosting would adhere to the cache control headers and not call the firebase cloud function when using the emulator.


Solution

  • How can the firebase emulator (hosting) be configured to respect the cache-control header that is set when a response comes from a Firebase function?

    Actually, it does. You can see the header right there in the response that you show in your question. The header is certainly being sent to the client.

    The issue is that the emulator does not actually have a CDN attached to it that remembers the response and serves it from cache. That would be overkill for a local emulator. The emulator is just an approximation of the production service, which is far more complicated than the emulator.

    Keep in mind also that the production CDN for Firebase Hosting is actually Fastly, as described in the documentation. Fastly is doing the hard work of caching in production.

    If you feel that adding an emulated CDN is something worth adding to the emulator (it probably isn't worth Firebase's effort to implement a CDN for testing and development purposes), file an issue on GitHub for the emulator describing what doesn't match.