Disable / Undo Firebase Cloud Functions default request parsing
Asked Answered
H

4

5

I want to deploy a remix application to Firebase Cloud Functions, using Hosting for the static assets. The function is defined as:

const functions = require("firebase-functions");
const express = require("express");
const compression = require("compression");
const morgan = require("morgan");
const { createRequestHandler } = require("@remix-run/express");

const app = express();

app.use(compression());
app.use(morgan("tiny"));

app.all("*", createRequestHandler({ build: require("./build") }));

const api = functions.https.onRequest(app);

module.exports = {
  api,
};

As documented here the request bodies are parsed by firebase before the request is passed to the api function. But the app is expecting "untouched" requests. This results in the request body being empty inside remix.

Is there a way to disable or undo the request body parsing? I've tried req.body = req.rawBody; in a middleware without luck.

Herbert answered 4/2, 2022 at 21:9 Comment(0)
Q
3

There is no way to disable the preprocessing done on the request in Cloud Functions. This is true for both the Firebase and Google Cloud variants.

If you want full control over the code that processes the request, consider using a different product, such as Cloud Run, which gives you the ability to control the behavior of all the code in the docker image that you build and deploy.

Quadruped answered 4/2, 2022 at 21:40 Comment(0)
L
2

There is a way to make Firebase functions work with Remix form data.

You can pass the form data by adding it to context:

in function/index.js, you need to get the post payload and add it to context

  app.all("*", createRequestHandler({build: require("./build"),
    getLoadContext(req) {
      return {body: req.body || null};
    }}));

and then action in your route you can read the context:

export const action = async ({ request, context }) => {
  console.log(context)
  return {context};
}
Leanora answered 30/4, 2022 at 20:38 Comment(0)
K
1

Expanding on Jkarttunen's answer, you should define your Firebase handler function like this:

// Initialize the Firebase admin SDK app. This must only be done once, so we must do it outside our handler.
const { initializeApp } = require('firebase-admin/app');
initializeApp();

const remixExpress = require("@remix-run/express");
const functions = require("firebase-functions");

exports.api = functions.https.onRequest((req, res, next) => {
  return remixExpress.createRequestHandler({
    build: require("./build"),
    getLoadContext(req) {
      return { body: req.body || null };
    }
  })(req, res, next);
});

Then, if you want to keep your code consistent with how you'd normally handle it in Remix (using a FormData object), then somewhere in your app, define a "getFormData" function like this:

import { FormData } from "@remix-run/web-form-data";

export const getFormData = async (request: Request, context: any) => {
  const requestBody = context?.body;

  // Was the request passed to Remix with a 'body'? If so, then the request was already processed by 'bodyParser',
  // which means that we need to get the data from the 'body' (the context gets set in our Firebase handler function)
  if (requestBody) {
    const form = new FormData();

    for (const key of Object.keys(requestBody)) {
      form.append(key, requestBody[key]);
    }

    return form;
  }

  return request.formData();
};

Then your action functions can look like this:

export const action: ActionFunction = async ({ request, context }) => {
  const form = await getFormData(request, context);

  // Get the data the same way you normally would. For example:
  const firstName = form.get("firstName");
}
Kimberleykimberli answered 27/7, 2022 at 12:7 Comment(0)
S
0

While there's no way (afaik) to disable that parsing, in some cases you can get a similar effect by creating a new stream containing the same (raw) contents that the firebase-function layer originally read.

Example: (for the case of a proxy that is sending the input request to an external service)

import request from 'request';
import express from 'express';
import intoStream from "into-stream";

[...]

app.use(function(req, res) {
    const extServiceDomain = 'https://api.example.com';
    const newReq = request[req.method.toLowerCase()](extServiceDomain + req.url);

    // the firebase-function layer (which we don't control), reads through the body stream (leaving it empty), so we need to recreate it
    const reqBodyStream_recreated = intoStream(req["rawBody"]); // the firebase-function layer at least gives us the raw Buffer with the read bytes
    // TypeScript doesn't see these properties, but they're necessary
    reqBodyStream_recreated["method"] = req.method;
    reqBodyStream_recreated["headers"] = req.headers;

    reqBodyStream_recreated.pipe(newReq).pipe(res);
});

Links:

Sewage answered 12/6 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.