Repository: firebase/firebase-tools (ts)

Issue:
Firebase functions emulator ignores region
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->

<!--
Thank you for contributing to the Firebase community!

Think you found a bug?
=======================
Yeah, we're definitely not perfect! Please use this template and include a minimal repro when opening the issue. If you know how to solve the issue, please create a Pull Request, and we'd be happy to review it!

Have a feature request?
========================
Great, we love hearing how we can improve our products! However, do not use this template to submit a feature request. Please submit your feature requests to: https://firebase.google.com/support/contact/bugs-features/

Have a usage question?
=======================
We get lots of those and we love helping you, but GitHub is not the best place for them and they will be closed. Please take a look at the guide first: https://firebase.google.com/docs/cli/

If the official documentation doesn't help, try asking through our official support channel: https://firebase.google.com/support/

Additional locations to check for solutions or assistance from the community:
- Stack Overflow: https://stackoverflow.com/
- Firebase Slack Community: https://firebase.community/

*Please avoid duplicate posting across multiple channels!*
-->

### [REQUIRED] Environment info

<!-- What version of the Firebase CLI (`firebase-tools`) are you using? Note that your issue may already be fixed in the latest versions. The latest version can be found at https://github.com/firebase/firebase-tools/releases -->

<!-- Output of `firebase --version` -->

**firebase-tools:**
9.2.0
<!-- e.g. macOS, Windows, Ubuntu -->

**Platform:**
macOS
### [REQUIRED] Test case

<!-- Provide a minimal, complete, and verifiable example (http://stackoverflow.com/help/mcve) -->
`export const api = region('europe-west3').https.onRequest(server);`

When starting the emulator, the emulator correctly outputs the following URL:

`✔  functions[api]: http function initialized (http://localhost:5000/**[some-project]**/europe-west3/api).`

However, when visiting that URL, a 404 is given. Going to the following URL works however:

`http://localhost:5000/**[some-project]**/us-central1/api`

### [REQUIRED] Steps to reproduce
See above
<!-- Provide the steps needed to reproduce the issue with the above test case. -->

### [REQUIRED] Expected behavior
Hitting the `europe-west3` URL working like the `us-central1` version does.
<!-- What is the expected behavior? -->

### [REQUIRED] Actual behavior
The `europe-west3` version hits a 404, while `us-central1` works.
<!-- Run the command with --debug flag, and include the logs below. -->

Clound Functions Emulator Not handling query parameters for functions deployed locally. 
When deploying functions locally using the emulator, any time a request is to a function and the URL used to make that request has query parameters, the response is HTTP 400 error. 

The following function was used:
```
export const helloWorld = functions.https.onRequest((req,res) => {
    console.log(req.query);
    res.send("Hello World");
});
```

When requesting this function using the url:
http://localhost:5001/{projectName}/{location}/helloWorld?id=test

I get a 400 error and in the emulator log the following happens:
function[helloWorld]
Beginning execution of "helloWorld"
14:52:13
W
function[helloWorld]
Your function timed out after ~60s. To configure this timeout, see
      https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.
14:52:13
I
function[helloWorld]
C:\Users\Ansaar Dollie\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:640
14:52:13
I
function[helloWorld]
            throw new Error("Function timed out.");
14:52:13
I
function[helloWorld]
            ^
14:52:13
I
function[helloWorld]

14:52:13
I
function[helloWorld]
Error: Function timed out.
14:52:13
I
function[helloWorld]
    at Timeout._onTimeout (C:\Users\Ansaar Dollie\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:640:19)
14:52:13
I
function[helloWorld]
    at listOnTimeout (internal/timers.js:554:17)
14:52:13
I
function[helloWorld]
    at processTimers (internal/timers.js:497:7)

Firebase Tools version: 9.2.0

Pull request description:
Fix query params and regional routing in emulated HTTP functions
<!--

Thank you for contributing to the Firebase community! Please fill out the form below.

Run the linter and test suite
==============================
Run `npm test` to make sure your changes compile properly and the tests all pass on your local machine. We've hooked up this repo with continuous integration to double check those things for you.

-->

### Description

Fixes #3031
Fixes #3032

### Scenarios Tested

```js
const functions = require("firebase-functions");
const express = require('express');

const server = express();
server.get('/', (req, res) => {
    res.send('lol!');
});

exports.helloWorld = functions.https.onRequest((req,res) => {
    res.json(req.query);
});

exports.api = functions.region('europe-west3').https.onRequest(server);
```

For #3032

```
$ http "http://localhost:5001/fir-dumpster/us-central1/helloWorld"
HTTP/1.1 200 OK
connection: keep-alive
content-length: 2
content-type: application/json; charset=utf-8
date: Thu, 14 Jan 2021 14:19:19 GMT
etag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"
keep-alive: timeout=5
x-powered-by: Express

{}
```

```
$ http "http://localhost:5001/fir-dumpster/us-central1/helloWorld?id=ok"
HTTP/1.1 200 OK
connection: keep-alive
content-length: 11
content-type: application/json; charset=utf-8
date: Thu, 14 Jan 2021 14:19:09 GMT
etag: W/"b-O3GjgUzPF1NwRnSW+tWJECMBW0k"
keep-alive: timeout=5
x-powered-by: Express

{
    "id": "ok"
}
```

```
$ http "http://localhost:5001/fir-dumpster/us-central1/helloWorld/?id=ok"
HTTP/1.1 200 OK
connection: keep-alive
content-length: 11
content-type: application/json; charset=utf-8
date: Thu, 14 Jan 2021 14:19:02 GMT
etag: W/"b-O3GjgUzPF1NwRnSW+tWJECMBW0k"
keep-alive: timeout=5
x-powered-by: Express

{
    "id": "ok"
}
```

```
$ http "http://localhost:5001/fir-dumpster/us-central1/helloWorld/?foo=bar&baz=quz"
HTTP/1.1 200 OK
connection: keep-alive
content-length: 25
content-type: application/json; charset=utf-8
date: Thu, 14 Jan 2021 14:18:48 GMT
etag: W/"19-aJ5/6qdD+JUaYGoi+SY23wQdu+Y"
keep-alive: timeout=5
x-powered-by: Express

{
    "baz": "quz",
    "foo": "bar"
}
```

For #3031

```
$ http http://localhost:5001/fir-dumpster/europe-west3/api
HTTP/1.1 200 OK
connection: keep-alive
content-length: 4
content-type: text/html; charset=utf-8
date: Thu, 14 Jan 2021 15:25:01 GMT
etag: W/"4-aGtZBCd1zhmF98IwfKTshS0CjO4"
keep-alive: timeout=5
x-powered-by: Express

lol!
```

### Sample Commands

N/A

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fd29f5cb..a946d7689 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,2 +1,4 @@
 - Catches errors while updating authorized domains when deleting channels, printing a warning instead of failing.
 - Fixes issue where `host` header was being incorrectly set when proxying to Cloud Run or Cloud Functions for Firebase from the Hosting emulator. (#3012)
+- Fixes issue where emulated HTTP functions would crash when the URL contained query parameters (#3032)
+- Fixes issue with routing to emulated HTTP functions in regions outside of `us-central1` (#3031)
diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts
index c0b0018c1..4a8d9739e 100644
--- a/src/emulator/functionsEmulator.ts
+++ b/src/emulator/functionsEmulator.ts
@@ -1068,8 +1068,8 @@ export class FunctionsEmulator implements EmulatorInstance {
     // req.url = /:projectId/:region/:trigger_name/*
     const url = new URL(`${req.protocol}://${req.hostname}${req.url}`);
     const path = `${url.pathname}${url.search}`.replace(
-      `/${this.args.projectId}/us-central1/${triggerId}`,
-      ""
+      new RegExp(`\/${this.args.projectId}\/[^\/]*\/${triggerId}\/?`),
+      "/"
     );
 
     // We do this instead of just 302'ing because many HTTP clients don't respect 302s so it may


Test patch:
diff --git a/src/test/emulators/functionsEmulator.spec.ts b/src/test/emulators/functionsEmulator.spec.ts
index 7779d4c8a..64b23446f 100644
--- a/src/test/emulators/functionsEmulator.spec.ts
+++ b/src/test/emulators/functionsEmulator.spec.ts
@@ -227,6 +227,34 @@ describe("FunctionsEmulator-Hub", () => {
       });
   }).timeout(TIMEOUT_LONG);
 
+  it("should return the correct url, baseUrl, originalUrl with query params", async () => {
+    useFunctions(() => {
+      require("firebase-admin").initializeApp();
+      return {
+        function_id: require("firebase-functions").https.onRequest(
+          (req: express.Request, res: express.Response) => {
+            res.json({
+              url: req.url,
+              baseUrl: req.baseUrl,
+              originalUrl: req.originalUrl,
+              query: req.query,
+            });
+          }
+        ),
+      };
+    });
+
+    await supertest(functionsEmulator.createHubServer())
+      .get("/fake-project-id/us-central1/function_id?a=1&b=2")
+      .expect(200)
+      .then((res) => {
+        expect(res.body.url).to.eq("/?a=1&b=2");
+        expect(res.body.baseUrl).to.eq("");
+        expect(res.body.originalUrl).to.eq("/?a=1&b=2");
+        expect(res.body.query).to.deep.eq({ a: "1", b: "2" });
+      });
+  }).timeout(TIMEOUT_LONG);
+
   it("should return the correct url, baseUrl, originalUrl for a subroute", async () => {
     useFunctions(() => {
       require("firebase-admin").initializeApp();
@@ -253,6 +281,32 @@ describe("FunctionsEmulator-Hub", () => {
       });
   }).timeout(TIMEOUT_LONG);
 
+  it("should return the correct url, baseUrl, originalUrl for any region", async () => {
+    useFunctions(() => {
+      require("firebase-admin").initializeApp();
+      return {
+        function_id: require("firebase-functions")
+          .region("europe-west3")
+          .https.onRequest((req: express.Request, res: express.Response) => {
+            res.json({
+              url: req.url,
+              baseUrl: req.baseUrl,
+              originalUrl: req.originalUrl,
+            });
+          }),
+      };
+    });
+
+    await supertest(functionsEmulator.createHubServer())
+      .get("/fake-project-id/europe-west3/function_id")
+      .expect(200)
+      .then((res) => {
+        expect(res.body.url).to.eq("/");
+        expect(res.body.baseUrl).to.eq("");
+        expect(res.body.originalUrl).to.eq("/");
+      });
+  }).timeout(TIMEOUT_LONG);
+
   it("should route request body", async () => {
     useFunctions(() => {
       require("firebase-admin").initializeApp();