firebase / firebase-tools

The Firebase Command Line Tools
MIT License
4.03k stars 950 forks source link

Firebase hosting emulator should be able to rewrite to cloud run service on localhost #1676

Open vdjurdjevic opened 5 years ago

vdjurdjevic commented 5 years ago

Firebase hosting emulator rewrites work like a charm with firebase functions since we have functions emulator. However, rewrites to a cloud run service work only if service is deployed to the cloud. Now, since cloud run is super awesome and flexible, testing it locally is a matter of running docker image locally (with docker, docker-compose or even Kubernetes Minikube). This approach is much more flexible and a better choice than firebase functions for me.

My use case is server-side rendering. I want to be able to test locally which requests trigger rewrite to server-side rendering instance deployed to cloud run. That's possible if I use the firebase function.

Is there any way to make rewrites config work with generic URL and to be controlled with environment variable? For example, I would like to be able to configure rewrite to http://localhost:5555 when running firebase hosting emulator, and https://mycloudrunserviceurl.com when in production (or staging). This way it wouldn't matter where server-side rendering instance is deployed (Cloud Run, Firebase function, GKE, AppEngine).

staugaard commented 4 years ago

This would be so nice!

samtstern commented 4 years ago

Thanks for the ping @staugaard we're just starting to think about this. Our approach will probably be something like this:

firebase.json

{
 "hosting": {
    // ...
    "rewrites": [ {
      "source": "**",
      "run": {
        "serviceId": "helloworld",
        "region": "us-central1"
      }
    }]
  },
  "emulators": {
     "run": [{
        "serviceId": "helloworld",
        "host": "localhost",
        "port": 5555
      }]
   }
}

What do you think? Is that flexible enough? You'd be responsible for running the service on localhost:5555 and we'd just call it when appropriate.

Some other things we could consider:

staugaard commented 4 years ago

Yeah I really like that approach. I don't think I would personally use the cmd feature.

Could we actually do something similar with functions too? That would allow usage of functions written in languages other than nodejs. Then you can run the functions on the side and just redirect/proxy to them.

samtstern commented 4 years ago

@staugaard that would be a pretty big re architecture for the Functions emulator, which is currently a supervisor for a swarm of node processes. I don't think we have enough Firebase developers using CF in other languages to justify that investment at the moment.

Our position on non-JS serverless is pretty much "use Cloud Run" and we intend to invest in Cloud Run tooling for Firebase developers as the platform matures, especially once it's able to trigger from a wider range of events (like Firestore/RTDB changes).

dinvlad commented 4 years ago

As a workaround, we've been using Webpack DevServer to proxy all requests to Firebase hosting and any other APIs that we use either locally or remotely:

// your framework's Webpack DevServer settings (ours are in vue.config.js)
const project = require('./.firebaserc').projects.default;
const hosting = require('./firebase').hosting;

const runProxy = {};
hosting.rewrites
  .filter(({ source, run }) => source !== '**' && run)
  .forEach(({ source }) => {
    runProxy[`^${source}/`] = {
      target: `https://${project}.web.app/${source}`,
    };
  });

module.exports = {
  // ...
  devServer: {
    // ...
    proxy: {
      '^/__/': {
        target: 'http://localhost:5000', // default hosting emulator port
      },
      '^/api/': {
        target: 'http://localhost:5001', // some other api
      },
      ...runProxy,
    },
  }
}

This works great to enable other features like hot-reloading also ;)

cbdeveloper commented 4 years ago

Any news on this feature? Just started converting my ssrApp cloud function to Cloud Run in order to avoid cold starts.

I was assuming the emulator (I'm using Cloud Code for VSCode) integration would be possible.

Also, what would be the workaround to develop Firebase backend code on Cloud Run before this feature is released? I didn't understand how the proxy suggested by @dinvlad works. Any help is appreciated.

Thanks

RayMeibergen commented 2 years ago

hi! is this issue working? love to have this option!

{
 "hosting": {
    // ...
    "rewrites": [ {
      "source": "**",
      "run": {
        "serviceId": "helloworld",
        "region": "us-central1"
      }
    }]
  },
  "emulators": {
     "run": [{
        "serviceId": "helloworld",
        "host": "localhost",
        "port": 5555
      }]
   }
}
gugahoi commented 2 years ago

Just thought I'd ping here as well. There are definite ways to have everything working nicely but it seems the number of processes required on my end to get my environment going keeps increasing. If emulated cloud run could be incorporated in the firebase config that would be amazing! Might also simplify needing to manage firebase configuration in my cloud run service :D

yuchenshi commented 2 years ago

@inlined Any thoughts?

inlined commented 2 years ago

We're looking into deeper investments with Cloud Run, but we're spread very thin and I don't suspect a Cloud Run emulator is on the horizon any time soon. I have two consolations for you:

  1. Cloud Run is on our long term roadmap. We will eventually have better tooling and we hope it'll blow you away
  2. Cloud Functions gen 2 is on our short term roadmap. This will help people who are leaving GCF for Run since GCF gen 2 is on Run and supports concurrent executions.
DibyodyutiMondal commented 1 year ago

TLDR: Even if cloud run is on the long term roadmap, simply allowing the hosting emulator to proxy certain paths to any port on localhost that we require would be a great (and hopefully easier and faster) way to start, since cloud run could potentially be emulated using our local docker (as per the cloud run documentation).

In the meantime, for development, the best method is to use something else to serve your front-end files and whatever it is you use for that, add a proxy to that which points to your local cloud run backend.


Long Version

For me, this feature is rather an important one.

Imagine a scenario where we want to build a website that includes multiple sub-domains (like google). Example:

  1. auth.domain.com // only for signing in
  2. site1.domain.com // some stuff
  3. site2.domain.com // some other stuff
  4. ...

There are 2 ways to implement cross-sub-domain auth using firebase authentication that I know of - using cookies and using iframes. And iframes are rather difficult to work with, especially if we want to build PWAs with offline support. I have tried both and for me, from development perspective, cookies were much, much easier to work with.

cross-domain cookies (by carefully setting the domain attribute of the cookie to domain.com) become even more easy to work with if the requests themselves are not cross-domain - think path rewrites by firebase hosting to cloud functions or cloud run. Since the host part of the url does not change, most of the CORS shenanigans that could mess with our cookies, do not come into play, and we can use 'SameSite=strict' on our cookie(s) without any second thoughts worrying about whether or not the cookie will come through. +1 Security; +1 Dev Experience

Therefore, firebase hosting's ability to path rewrite is essential in this scenario. I'd say, all we need for (firebase hosting + cloud run) in development, is the ability to proxy to some other port on localhost, since cloud run can be emulated on docker. Or, since cloud run accepts plain source code at deploy time, we can even run the source code directly without worrying about builds (Ex: I use nodejs for my project, so I use nodemon for local developement)

I do think that @RayMeibergen's comment (https://github.com/firebase/firebase-tools/issues/1676#issuecomment-984354160) looks like a great way that the general shape of the config for such a proxy feature for the hosting emulator could look like.

In the meantime, I have had to stop using the firebase hosting emulator for development. @dinvlad's approach of using some other way to serve the files for the front-end and to proxy the necessary backend requests from that, has had great success in my case. Please bear in mind that different front-end frameworks provide different methods for proxying in development (if at all), so we have to do our homework. (In my case, I use angular, so for those using angular: https://angular.io/guide/build#proxying-to-a-backend-server)

Also, depending on what our hosting config is, behaviour may differ from the hosting emulator. My experience has been that, it took a deeper investment than expected to create an exact alternative to the hosting emulator, but it's easy and worth it.

garrett-wombat commented 1 year ago

Im having a related issuer with the 2nd Gen firebase functions. With a hosting configuration like this

{
 "hosting": {
    // ...
    "rewrites": [ {
      "source": "!/@(api)/**",
      "destination": "/index.html"
     },{
      "source": "api/",
      "run": {
        "serviceId": "helloworld",
      }
    }]
  },
}

where helloworld is my 2nd gen clound function.

This works great when deploying into a cloud environment. The Issue i'm seeing is that when running the emulator the function call helloworld (through the hosting path) are now reaching out into the cloud to execute. Should the emulated hosting environment be looking for emulator 2nd gen functions before making a request to the default project instance?

If this should be a new issue let me know.

inlined commented 1 year ago

@garrett-wombat You can use the "functions" override to target a 2nd gen (Firebase) cloud function.

All: My hope is that the impact of this bug is significantly reduced with the introduction of Web Frameworks for Firebase Hosting. With this you can build a full stack web application in many popular frameworks natively in Firebase Hosting (it will also spin up a 2nd gen Cloud Function if necessary). It works in the emulator and is getting even better at I/O (May 10).

tylerpalmerdev commented 1 year ago

This is exciting @inlined. We use Firebase heavily for our small application (~2 features, <100 users) and love the DX: emulator suite, hosting preview URLs, remote config, etc. Still, we've been considering replacing Cloud Functions with a more traditional API service on Cloud Run for long-term flexibility & scale. Maybe Web Frameworks could make this moot?

How does Firebase + Web Frameworks stack up against competitors like Vercel or AWS Amplify that have supported Next.js for awhile?

DibyodyutiMondal commented 1 year ago

My hope is that the impact of this bug is significantly reduced with the introduction of Web Frameworks for Firebase Hosting.

@inlined

As much as I like the introduction of the web-frameworks feature, the fact remains that are cases where we explicitly to choose Cloud Run over Cloud Functions (even though we know that gen 2 functions run on top of Cloud Run). At the point in time when the OP created this issue, the choice was because SSR support was missing. But this choice may be caused by any number of system design choices and/or restrictions. In that scenario, relying solely on the web-frameworks features becomes a no-go.

Cloud Run supports already exists, right. But it's only available in the cloud, not the emulator. There's no way to point to a working local backend instance during development.

Although pointing to a specific localhost port during development is not a difficult problem to overcome at all, it can only be done by not using the hosting emulator - which, in the case of angular, I can't use anyway, because I don't expect ng serve to work with the web-frameworks feature.

inlined commented 1 year ago

@tylerpalmerdev We're just getting started. Web Frameworks has some features that other providers don't, such as zero-parameter initialization of the Firebase SDKs client- and server-side, automatic synchronization of Firebase Auth across client- and server-side contexts, the ability to customize RAM vs CPU, and longer timeouts. On the other hand, we're still building the infrastructure to support more complex features like ISR.

@DibyodyutiMondal Fair enough. We're currently spending near 100% of our focus on graduating the post-experimental version of web frameworks, so I've previously shunted on this problem as out of budget. I might be able to come up with some options that are cheap enough to squeeze into the schedule.

CaptainCodeman commented 5 months ago

Given that "functions" are supported in the emulator, maybe an option is to have the function be a proxy that can direct requests to a local docker instance, to simulate cloud run? A bit hacky, but could work.