firebase-tools
firebase-tools copied to clipboard
Functions emulator path doesn't match real executor path
[REQUIRED] Environment info
firebase-tools: 9.2.2
Platform: WSL v2 Debian
[REQUIRED] Test case
import * as functions from "firebase-functions";
import express from "express";
const app = express();
app.post("/api/test", async (req, res) => {
console.log("hooray");
});
export const api = functions.https.onRequest(app);
[REQUIRED] Steps to reproduce
Run the above test case, visit .../api/test.
[REQUIRED] Expected behavior
.../api/test should work.
[REQUIRED] Actual behavior
If I visit this in prod functions it works, however on the emulator it requires that I visit .../api/api/test.
Oh turns out this might not be because of the emulator but rather because I'm using cloud functions behind firebase hosting. In other words it seems like the emulator behavior is correct and there's a bug in firebase hosting somewhere.
@kevmo314 can you provide some more information? Specifically seeing your firebase.json and the HTTP request you're trying to make would help.
Here is my firebase.json
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": {
"predeploy": "npm --prefix \"$RESOURCE_DIR\" install && npm --prefix \"$RESOURCE_DIR\" run build"
},
"hosting": {
"public": "build",
"predeploy": "npm install && npm run build",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "/auth/**",
"function": "auth"
},
{
"source": "/api/**",
"function": "api"
},
{
"source": "**",
"destination": "/index.html"
}
]
}
}
I'm trying to hit GET domain.com/api/foo, it only works with the above express js route config. If I hit the cloud functions endpoint directly, I need to do GET .../api/api/foo.
@kevmo314 can you give the full URL you're hitting? You need to use the host/port for the hosting emulator if you want those redirects from firebase.json to work.
It would be clearest if you could show me two full HTTP requests with curl against the emulators. One that works and one that does not.
Hey @kevmo314. We need more information to resolve this issue but there hasn't been an update in 7 weekdays. I'm marking the issue as stale and if there are no new updates in the next 3 days I will close it automatically.
If you have more information that will help us get to the bottom of this, just add a comment!
Been fighting with this myself, here's a small example:
Firebase.json:
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
],
"source": "functions"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [ {
"source": "/app/**",
"function": "app"
} ]
},
"storage": {
"rules": "storage.rules"
},
"emulators": {
"functions": {
"port": 5001
},
"hosting": {
"port": 5000
},
"ui": {
"enabled": true,
"port": 8081
}
}
}
starting emulators (to see port mapping)
┌───────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├───────────┼────────────────┼─────────────────────────────────┤
│ Functions │ localhost:5001 │ http://localhost:8081/functions │
├───────────┼────────────────┼─────────────────────────────────┤
│ Firestore │ localhost:8080 │ http://localhost:8081/firestore │
├───────────┼────────────────┼─────────────────────────────────┤
│ Hosting │ localhost:5000 │ n/a │
└───────────┴────────────────┴─────────────────────────────────┘
A function that simply logs the incoming path back to caller:
import * as functions from 'firebase-functions';
import express from 'express';
const app = express();
app.use(function(request, response) {
response.send(request.path);
});
exports.app = functions.https.onRequest(app);
A curl against the function endpoint:
❯ curl http://localhost:5001/rssr-8d5f4/us-central1/app/hello
/hello%
A curl against hosting (should rewrite to function emulator):
❯ curl localhost:5000/app/hello
/app/hello%
Generate this rewrite log:
[hosting] Rewriting /app/hello to http://localhost:5001/rssr-8d5f4/us-central1/app for local Function app
As you can see, in the function emulator the path for the request is just /hello, all the leading stuff is removed. In the hosting emulator the function name is included, so the route handler has to somehow ignore it to work in both consistently.
Is there a possibility to use like the various regex 'negative lookahead/lookbehind' kind of stuff to strip it out in the rewrite rule?
Thanks! Yeah that's the behavior I was seeing as well.
I'm having the same issue:
I have a function that serves an express server:
router.post('/', (req, res) => {
res.sendStatus(200);
});
The rewrite I have:
{
"source": "functions/billing",
"function": "billing"
},
When hitting up the URL from the emulator:
http://127.0.0.1:8202/[project-name]/us-central1/billing hits the function and gets a response since no rewrites are being used. And we are directly sending request to the function URL at /.
When hitting function from a hosting deployment URL:
https://test-deployment-url.web.app/functions/billing the rewrite will actually only replace the basename of the url and will end up hitting:
https://billing-[randomstring]-uc.a.run.app/functions/billing which is the actual function url + the route.
The express server that's set up in the function won't work since it expects / instead of /functions/billing.
So the rewrite points to the function URL (https://billing-[randomstring]-uc.a.run.app) but it also adds the route again after pointing to it. I think this behavior is useful when you are using one app function that listens to ** and needs to receive route information. However, in our use case, we have a route before the ** and our expectation is to just receive the route after ** when the function is hit.
That's why you are only getting a response when you hit /api/api/test. If your rewrite was:
{
"source": "/api2/**",
"function": "api"
},
You would have to hit /api2/api/test for the function to work.
A quick workaround I found was:
app.use('(/functions/billing)?/', router);
This will ignore the /functions/billing if it exists and result in expected behavior.