Firebase Functions With Express Failed to decode param %CO - node.js

There is a solution to this error without the use of Firebase here when using app.listen(8080) however this does not work while serving in cloud functions with exports.app = functions.https.onRequest(app)
Here is a simple reproduction code
const app = express();
app.get('**', (req, res) => res.send('working'));
app.use((err, req, res, next) => res.redirect('/404'));
exports.app = functions.https.onRequest(app) // doesnt work
// app.listen(5000) // works
How do you go about catching this error in firebase functions? I would like the redirect to work.
firebase test: firebase serve --only functions
express serve: node index.js
URL to test: http://localhost:5000/%CO
Note that the additional %CO is the one that cannot be decoded by express. This error is caught while serving with the express method but not with the firebase functions method.
As this seems like a bug, I have also created an issue here on github incase I find no workaround on it.

Try changing the name of app in exports.
exports.newApp = functions.https.onRequest(app)

According to the documentation, the correct way of using an Express app with Firebase Functions is to pass the application to a Function like:
// Expose Express API as a single Cloud Function:
exports.app = functions.https.onRequest(app);
Listening to a port for requests does not apply when running through Firebase Functions. As for how to catch errors when passing your app to a function, it’s done in the same way as the question you referenced as far as I have reviewed. You can catch errors by using Express middleware while using Cloud Functions.
Moreover, implementing redirects with Firebase Functions is explained in this related question, which makes use of the documentation to configure how to redirect by modifying the firebase.json file.

Related

Rerouting Firebase Hosting to Express Cloud Functions

I can't seem to configure routes to connect firebase hosting to my cloud function express app. I tried to setup as shown here but the behaviour seems to be different. I can't seem to figure out what I am doing wrong. Please send help, I'm going insane.
index.js
const functions = require("firebase-functions");
const app = require('./app');
exports.api = functions.https.onRequest(app);
app.js
const app = express();
// ...
app.use(cors());
app.use('/params', paramsRoutes);
module.exports = app;
firebase.json
{
"hosting": {
// ...
"rewrites": [
{
"source": "/api/**",
"function": "api"
},
// ...
]
}
}
There is appropriate documentation that lists your requirement of hosting the Cloud Functions in Firebase Hosting using a custom domain.
Make sure that you have the latest version of the Firebase CLI and
that you've initialized Firebase Hosting.
Initialize Cloud Functions by running the following command from the
root of your project directory: firebase init functions
Also as per documentationwe can use rewrites to serve requests for a specific firebase hosting URL to a function. To do that you need to use "/api". You don't have to add the entire URL because you are redirecting from firebase hosting. So, when you add "/api" you are indicating to redirect requests going to "PROJECT_ID.web.app/api and PROJECT_ID.firebaseapp.com/api" to the function you specified.
If you want redirect every single URL to your host to an express app in Cloud Functions, you will need to do the following:
Make sure there is no index.html in your public hosting folder
(otherwise it will always be served with the path /).
Your Express routes need to exactly match the incoming URL, not just
the wildcard part.
You can read more in the docs or follow this medium post for more details.
Check these links for similar implementation:
How to write redirect rules for Firebase hosting and express
How to redirect all server requests to a function in firebase

How to disable GCF URL query string parsing

I have a simple node.js cloud function that I need the un-parsed original URL within. In the request object, we have request.url, request.originalURL but both of these seem to be parsed upstream of the function into key=parameter pairs. For example, calling the function from firebase hosting with the url https://www.example.com/index.html?var1&var2&var3 would yield an output of "/index.html?var1=&var2=&var3=" for either request attribute. I can remove the "=" characters, but it gets more complex. For example, if var1="123,456" I now have "/index.html?123%2C456=&var2=&var3=" which requires me to un-parse, and also decode the URL.
The real show stopper, is that for some reason var1 can end up after var2 and var3. I.E. "/index.html?var2&var3&var1". This doesn't happen with the local emulator, but does on the deployed version. Since I'm not using key=value pairs, I rely on the variables being in the same order. I can't change the structure without a lot of work on the front end app.
When I run the function locally with firebase emulator, i can use request.headers["x-original-url"] but this is not available when deployed to GCF.
In the express documentation, it shows that the query parser can be turned off by app.disable("query parser") or app.set("query parser", false), but neither of these have an effect on the url locally or in the cloud.
Here is a snippet to recreate the issue:
const functions = require("firebase-functions");
const express = require('express');
const app = express();
app.disable('query parser');
// also tried
// app.set('query parser', false);
app.get("*", async (req, res) => {
functions.logger.log(req.url, req.originalUrl);
return res.status(200).send(""); //
});
exports.ssr = functions.https.onRequest(app);
I think either I'm missing something in the documentation, or there is a feature to disable the express parser missing from google cloud functions.

How to access endpoint from express router in Cloud Functions Shell

(Note: I'm using javascript, not typescript in my Functions)
My Firebase Project has a single 'oauth' function, which has a series of endpoints created through express app/routers.
I don't understand how to run the functions at these endpoints from the Cloud Functions Shell to debug them locally.
Here is my index.js
const twitter = require("./oauth/twitter");
const app = express();
app.use("/signin/twitter", twitter.router);
exports.oauth = functions.https.onRequest(app);
My actual endpoints are in a twitter.js file (and others for other providers)
router.get("/authorize", (req, res) => {...});
router.get("/authorize_callback", (req, res) => {...});
router.get("/deauthorize", (req, res) => {...});
If I run 'firebase functions:shell' in my terminal, it only shows the 'oauth' function.
I would like to access a function such as 'oauth/signin/twitter/authorize' just like I do in the browser after deploying, but I have no idea how to!
Is this possible?
I believe this is the documentation you are looking for. Essentially, you can invoke those express-like routes by calling the method (get, post, etc.) within the Cloud Function in the shell like such: functionName.get('/test')

How to access firebase hosting file from firebase function

I have a simple firebase function it has to send a file in response to the request the file is in /app/one.html
my project directory structure
blog
|____app
|____index.html
|____one.html
|____functions
|____index.js
I have to access one.html and send it from senFile()
index.js
const functions = require('firebase-functions');
exports.blog = functions.https.onRequest((req, res) => {
res.status(200).sendFile('app/one.html'); #here I need something to do
});
As far as I know firebase deploy for functions deploys only the contents of functions folder. So if you move/copy your app folder to inside of functions folder you can achieve what you are trying to do.
blog
|____functions
|____app
|____index.html
|____one.html
|____index.js
After you changed the paths correctly you can send the file like an express app.
res.sendFile(path.join(__dirname + 'app/one.html'));

why is my express API not responding?

Background
I am testing a simple Hello World app using NodeJs v7 and express in cloud9.
I am trying to make my example work but I am failing.
Problem
All my cloud9 configurations are fine so that is not the problem. The problem is my app. When i debug, the route "api/v1/HolaBananas" never gets called and I don't know why!
Even worst, when i make the request, the browser just hangs, like it is waiting for an answer from the server that will never come!
Code
The index.js has the initialization and launch code. It is important for me that I keep this separate from api.js for modular reasons.
index.js
"use strict";
const express = require("express");
const app = express();
app.use("/api/v1", require("./api.js"));
app.listen(process.env.PORT);
console.log(`Server listening on port ${process.env.PORT}!`);
The api.js simply contains the routes and what they are supposed to do.
api.js
"use strict";
const express = require("express");
module.exports = function() {
const api = express.Router();
api.get("/HolaBananas", function(req, res){
res.send("hello bananas!");
});
return api;
};
Question
I am sure I am not using api.get in the right way, but I truly want to separate my initialization and launch code from the api.
How can I fix my code so it works?
Note
I am following the course
https://www.edx.org/course/introduction-mongodb-using-mean-stack-mongodbx-m101x-0
You can fix it by two following ways
var api = require("./api.js")();
app.use("/api/v1", require("./api.js"));
as API.js return a function reference. so you need to call that function to access route.
Or You need to modify your api.js as follows if you don't want to change index.js
"use strict";
const express = require("express");
const api = express.Router();
api.get("/HolaBananas", function(req, res){
res.send("hello bananas!");
});
module.exports = api;
Solution
While Vikash Sharma's solution would work, I figured it out by following the recommendation of Jayant Patil and to read this other question:
https://codereview.stackexchange.com/questions/51614/exporting-routes-in-node-js-express-4
Turns out we had the same issue, but the given answer to that question also allows me to encapsulate the api file completely inside a function, preserving its scope.
Still, kudos++ for trying!
There is one subtle thing about it: You have to invoke the function that you export in your api.js and use the router object that is returned from that function, so here is what you should do:
You have to replace this line:
app.use("/api/v1", require("./api.js"));
with this:
app.use("/api/v1", require("./api.js")());

Resources