Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP functions path prefix differences between emulated and deployed functions #1312

Closed
christiannaths opened this issue May 17, 2019 · 9 comments

Comments

@christiannaths
Copy link

christiannaths commented May 17, 2019

For Posterity

This stems from a conversation #1279

@christiannaths commented

As far as I can tell, there is now no way to get the actual request path when running functions in the emulator.

For example, I need to generate an oauth callback url for the google OAuth2Client. I managed this before by using a combination of request.protocol, request.headers.host, and request.path. After this change–given that request.path no longer reflects the actual path, and the actual path is not stored anywhere–I'm at a loss as to how I can accomplish this without some hackery.

@abeisgoat commented

@christiannaths Thanks for your input, this is a great question and if I'm understanding correctly, you aren't saying that the old emulator handled this well, just that the new one (with the change to match the old one) now breaks you?

If this is the case, then I think this will be a prime candidate to introduce into emulators:start so serve will have the same bug as the old emulator and emulators:start will make the prefix available in some way (along with some other IS_FIREBASE_EMULATOR type env.)

That being said, since the original issue that @tmcf reported is now fixed, @christiannaths can you open a new issue with your thoughts? Feel free to just copy/paste what you said here, I just want to keep things organized.

[REQUIRED] Environment info

firebase-tools: 6.9.2

Platform: macOS

[REQUIRED] Test case

Public repo: https://github.com/christiannaths/fbfn-path-prefix-example
Deployed function: https://us-central1-christiannaths-com.cloudfunctions.net/fnEcho

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { pick, omitBy } = require("lodash");

admin.initializeApp();

exports.echoFn = functions.https.onRequest((req, res) => {
  const request = pick(req, [
    "domain",
    "headers",
    "url",
    "method",
    "baseUrl",
    "originalUrl",
    "params"
  ]);

  const env = omitBy(process.env, (value, key) => key.startsWith("npm_"));

  res.send({ request, env });
});

[REQUIRED] Steps to reproduce

Run above test function in a new project, once locally in the emulator, and once deployed. Observe the response from each scenario. (see Actual behavior below for a sample)

[REQUIRED] Expected behavior

I expect the paths to be exactly the same in the emulator as they are once deployed, or at least be able to reason about the differences.

I see three ways to solve this:

  1. Do away with the prefix entirely (best)
  2. Single out and expose just the function path prefix somewhere, if it exists.
  3. Let me know that the function is being run in the emulator (worst)

To be perfectly honest, I strongly feel that having a prefix in the emulator at all is unexpected behavior, though I do understand the behaviour exists for legacy support.

As a suggestion, perhaps the newer firebase emulators:start could serve the functions without the prefix, and legacy behaviour could be supported with firebase serve.

[REQUIRED] Actual behavior

Emulated

functions: HTTP trigger initialized at http://localhost:5001/christiannaths-com/us-central1/fnEcho
response
{
  "request": {
    "domain": null,
    "headers": {
      "host": "localhost:5001",
      "connection": "keep-alive",
      "cache-control": "max-age=0",
      "upgrade-insecure-requests": "1",
      "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36",
      "dnt": "1",
      "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
      "accept-encoding": "gzip, deflate, br",
      "accept-language": "en-US,en;q=0.9"
    },
    "url": "/christiannaths-com/us-central1/fnEcho",
    "method": "GET",
    "baseUrl": "",
    "originalUrl": "/christiannaths-com/us-central1/fnEcho",
    "params": {
      "0": "christiannaths-com/us-central1/fnEcho"
    }
  },
  "env": {
    "...note": "A few non-relevant env vars have been removed",
    "NODE": "/Users/christiannaths/.n/bin/node",
    "INIT_CWD": "/Users/christiannaths/Desktop/firebase-functions-path-prefix-example/functions",
    "TMPDIR": "/var/folders/d3/nflw9g251rb3v09s0ymcc3v80000gn/T/",
    "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.UAAe7tnG6D/Listeners",
    "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x52",
    "_": "/usr/local/bin/firebase",
    "PWD": "/Users/christiannaths/Desktop/firebase-functions-path-prefix-example/functions",
    "GCLOUD_PROJECT": "christiannaths-com",
    "FIREBASE_CONFIG": "{\"databaseURL\":\"https://christiannaths-com.firebaseio.com\",\"storageBucket\":\"christiannaths-com.appspot.com\",\"projectId\":\"christiannaths-com\"}"
  }
}

Deployed

Function URL (fnEcho): https://us-central1-christiannaths-com.cloudfunctions.net/fnEcho
response
{
  "request": {
    "domain": null,
    "headers": {
      "host": "us-central1-christiannaths-com.cloudfunctions.net",
      "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36",
      "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
      "accept-encoding": "gzip, deflate, br",
      "accept-language": "en-US,en;q=0.9",
      "cache-control": "no-cache",
      "dnt": "1",
      "forwarded": "for=\"187.237.217.18\";proto=https",
      "function-execution-id": "zx1mppz9po8n",
      "pragma": "no-cache",
      "upgrade-insecure-requests": "1",
      "x-appengine-api-ticket": "[redacted]",
      "x-appengine-city": "[redacted]",
      "x-appengine-citylatlong": "[redacted]",
      "x-appengine-country": "[redacted]",
      "x-appengine-default-version-hostname": "[redacted]",
      "x-appengine-https": "[redacted]",
      "x-appengine-region": "[redacted]",
      "x-appengine-request-log-id": "[redacted]",
      "x-appengine-user-ip": "[redacted]",
      "x-cloud-trace-context": "[redacted]",
      "x-forwarded-for": "[redacted]",
      "x-forwarded-proto": "https",
      "connection": "close"
    },
    "url": "/",
    "method": "GET",
    "baseUrl": "",
    "originalUrl": "/",
    "params": {
      "0": ""
    }
  },
  "env": {
    "X_GOOGLE_FUNCTION_TIMEOUT_SEC": "60",
    "NO_UPDATE_NOTIFIER": "true",
    "X_GOOGLE_FUNCTION_MEMORY_MB": "256",
    "FUNCTION_TIMEOUT_SEC": "60",
    "FUNCTION_MEMORY_MB": "256",
    "X_GOOGLE_LOAD_ON_START": "false",
    "X_GOOGLE_FUNCTION_TRIGGER_TYPE": "HTTP_TRIGGER",
    "PORT": "8080",
    "ENTRY_POINT": "fnEcho",
    "HOME": "/tmp",
    "X_GOOGLE_SUPERVISOR_HOSTNAME": "169.254.8.129",
    "FUNCTION_TRIGGER_TYPE": "HTTP_TRIGGER",
    "X_GOOGLE_GCLOUD_PROJECT": "christiannaths-com",
    "X_GOOGLE_FUNCTION_NAME": "fnEcho",
    "FUNCTION_NAME": "fnEcho",
    "SUPERVISOR_INTERNAL_PORT": "8081",
    "X_GOOGLE_GCP_PROJECT": "christiannaths-com",
    "X_GOOGLE_FUNCTION_REGION": "us-central1",
    "X_GOOGLE_ENTRY_POINT": "fnEcho",
    "FUNCTION_REGION": "us-central1",
    "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "X_GOOGLE_WORKER_PORT": "8091",
    "CODE_LOCATION": "/srv",
    "WORKER_PORT": "8091",
    "SUPERVISOR_HOSTNAME": "169.254.8.129",
    "DEBIAN_FRONTEND": "noninteractive",
    "X_GOOGLE_FUNCTION_IDENTITY": "christiannaths-com@appspot.gserviceaccount.com",
    "X_GOOGLE_CONTAINER_LOGGING_ENABLED": "false",
    "GCLOUD_PROJECT": "christiannaths-com",
    "FUNCTION_IDENTITY": "christiannaths-com@appspot.gserviceaccount.com",
    "X_GOOGLE_CODE_LOCATION": "/srv",
    "PWD": "/srv",
    "GCP_PROJECT": "christiannaths-com",
    "X_GOOGLE_SUPERVISOR_INTERNAL_PORT": "8081",
    "X_GOOGLE_FUNCTION_VERSION": "1",
    "X_GOOGLE_NEW_FUNCTION_SIGNATURE": "true",
    "NODE_ENV": "production",
    "FIREBASE_CONFIG": "{\"projectId\":\"christiannaths-com\",\"databaseURL\":\"https://christiannaths-com.firebaseio.com\",\"storageBucket\":\"christiannaths-com.appspot.com\",\"cloudResourceLocation\":\"us-central\"}"
  }
}
@abeisgoat abeisgoat self-assigned this May 17, 2019
@abeisgoat
Copy link
Contributor

Thanks for this thorough bug.

This will require a bit of a discussion to figure out the best route forward. I'll speak with the other emulator engineers next week and see if we can decide how to move forward.

@tmcf
Copy link

tmcf commented May 18, 2019

The fix for #1279 in release 6.10.0 solved my immediate problem, but it would be great to be able to have access to the mount point of the functions.

If deployed firebase functions will always have their own subdomain and no extra path prefix https://[fb-region]-[fb-project].cloudfunctions.net/[firebase-function-name] then I would be in favour of the emulator following the same behaviour with emulators:start, leaving firebase serve as legacy as @christiannaths suggests.

If there is any possibility that deployed functions might have a different path prefix in some future firebase release, https://[fb-project].cloudfunctions.net/[fb-region]/[fn-version]/[firebase-function-name] as an arbitrary example, then programatic access should be provided.

Perhaps the express app that invokes the request, could be a sub-app that is mounted at the prefix point. Then the prefix path would be available as expected by express handlers in req.app.mountpath (The req.app.mountpath value is currently '/' in both the live deploy and emulated 6.10.0).

@abeisgoat
Copy link
Contributor

Fix has been merged into next branch so it'll go out with the firebase-tools@7.0.0 release

@liezl200
Copy link

liezl200 commented Dec 6, 2019

@abeisgoat Has this been resolved? For me, emulators:start and firebase serve are still adding the path prefix and I have to use the environment variable workaround suggested in #1279

@samtstern
Copy link
Contributor

@liezl200 there are two parts:

  • The localhost URL still includes the path prefix. This is not changed.
  • It is now the case that request.path and request.url return the proper, un-prefixed values just like they do in production.

Can you explain a little more about your use case? Ideally by filing a new issue since discussing on a closed issue is easy to miss.

@liezl200
Copy link

liezl200 commented Dec 14, 2019

@samtstern Ahh, thanks for the response. I thought that path prefix on localhost was removed for Firebase functions when using the emulator. It works as you describe.

My use case is: I want to build a redirect URI for a button based on my current host (to automatically handle production vs development). The button href uses an external API that I don't have control over, which requires me to pass the redirect_uri as a query parameter. I use express routing, so I have a route app.get('/redirect' ...) which I want to use as the redirect_uri.

In development, I can't just have my frontend HTML pass "localhost:5001/redirect" as redirect_uri, it has to use "localhost:5001/path/prefix/redirect". In production I need to use "firebase.app/redirect". It's easy enough to use an environment variable to handle production vs. development environments but it does add some extra complexity in my frontend code.

Anyway, since you say it's expected, no need to open a new issue.

@samtstern
Copy link
Contributor

@liezl200 thanks that makes sense! I think we should be able to make this work more seamlessly, but I will have to investigate. I am somewhat terrible at express :-)

PRs always welcome if you think you know the fix! But emulators code is fairly complex so I probably have to do this one myself (or @abeisgoat might know what to do)

@spectremal
Copy link

So this was never fixed and the problem is exacerbated in Functions V2.

Currently, when you deploy a function your url looks like this:

https://functionname-leffhkkdww-uc.a.run.app

When you check the function in Google Cloud Console, it looks like this:

https://us-central1-project-name-123ab.cloudfunctions.net/functionName

And when you deploy with the emulator, it looks like this:

http://127.0.0.1:5001/project-name-123ab/us-central1/functionName

So to work around this my team and I have to write a layer on top of firebase and maintain it as Firebase gets worse and worse to use. This bug was never solved.

@benronkin
Copy link

So this was never fixed and the problem is exacerbated in Functions V2.

Currently, when you deploy a function your url looks like this:

https://functionname-leffhkkdww-uc.a.run.app

When you check the function in Google Cloud Console, it looks like this:

https://us-central1-project-name-123ab.cloudfunctions.net/functionName

And when you deploy with the emulator, it looks like this:

http://127.0.0.1:5001/project-name-123ab/us-central1/functionName

So to work around this my team and I have to write a layer on top of firebase and maintain it as Firebase gets worse and worse to use. This bug was never solved.

Agreed, this is not ideal. And for some reason, in my case, Firebase isn't loading a .env file so I have to make the reconciliation directly in the code instead of using a config file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants