Handling Firebase HTTPS Functions - Returning a 200 status code Before Executing any Background Tasks

We are currently initiating a cloud function with an HTTPS request to:

  1. Provide a quick response (<10s) to the caller with a 200 Response in order to prevent additional retries from our caller.
  2. THEN carry out two other time-sensitive POST requests before concluding the function.

What are some optimal solutions to address this issue? We have explored:

  1. PubSub as a way for subscribers to complete the POST requests asynchronously after sending the 200 response.
  2. Utilizing Cloud Tasks and enqueueing them to a task queue. However, we are unsure if these tasks are impacted by the HTTPS termination, similar to our previous attempts.
  3. Implementing an Event-Based Trigger such as Cloud Firestore to write to a document with a specified ID that triggers an onCreate event to finalize the POST requests.

Additional information includes the fact that we currently experience low traffic due to being a smaller app (less than 1000 QPS), which may eliminate Pub Sub as an option. We aim to create the background task within <1-2 seconds to maintain a positive user experience (specifically for a messaging app).

Initially, our code was structured like this:

exports.example = functions
  .https
  .onRequest(async (request, response) => {
    const authHeader = request.get("authorization");
    const {statusCode, httpsResponseMessage} =
      verifyAuthHeader(authHeader, token);
    // Respond quickly to the caller within 10 seconds to avoid additional retries.
    response.status(statusCode).send(
     {
      message: httpsResponseMessage,
     }
    );
    // Perform other POST requests
  }

We also attempted the following:

exports.example = functions
  .https
  .onRequest(async (request, response) => {
    const authHeader = request.get("authorization");
    const {statusCode, httpsResponseMessage} =
      verifyAuthHeader(authHeader, token);
    // Provide a 200 response to the caller within 10 seconds to prevent further retries.
    response.write(
      JSON.stringify(
        {
          message: httpsResponseMessage
        }
      )
    );
    // Carry out additional POST requests
    res.end()
  }

However, we encountered inconsistent behavior with our code AFTER

response.status(statusCode).send()
, as mentioned in the tips & tricks section of Firebase docs. Sending a partial result with response.write did not prevent future retries from the caller.

Answer №1

It is crucial to exercise caution when handling HTTP functions and ensure that all tasks are completed before initiating a response. This requirement is outlined in The Node.js Runtime:

When dealing with asynchronous tasks involving callbacks or Promise objects, it is essential to explicitly notify the runtime once these tasks have finished executing. Various methods can achieve this, as demonstrated in the code snippets below. The key point is that your code must wait for the completion of the asynchronous task or Promise before returning to prevent premature termination of the async component.

// Correct: awaiting a Promise before sending an HTTP response
await Promise.resolve();

// Incorrect: HTTP functions should send an
// HTTP response instead of merely returning.
return Promise.resolve();

// HTTP functions should indicate completion by returning an HTTP response.
// This should only happen after all background tasks are done.
res.send(200);
res.end();

// Incorrect: this section may not run if an
// HTTP response has already been sent.
return Promise.resolve();

In practical terms, both examples presented will not function as intended since the response is triggered before completing the work described as // do other POST requests.

Based on your explanation:

  1. You require prompt responses to the caller
  2. You need to trigger two additional functions when calls are made to this function
  3. The responses from these two functions are not utilized in the original caller's response

If this scenario applies, I suggest utilizing Pub/Sub-triggered background functions and structuring the function as follows:

const { PubSub } = require('@google-cloud/pubsub')

const pubSubTopic1 = new PubSub('your-project-id').topic('the-first-topic-name')
const pubSubTopic2 = new PubSub('your-project-id').topic('the-second-topic-name')

exports.example = functions
  .https
  .onRequest(async (request, response) => {
    const authHeader = request.get("authorization");
    const {statusCode, httpsResponseMessage} = verifyAuthHeader(authHeader, token);

    // Publish messages to the Pub/Sub topics to trigger those functions
    await Promise.all([
      pubSubTopic1.publishMessage('message-1'),
      pubSubTopic2.publishMessage('message-2')
    ]);

    // Send 200 response to our caller within 10 seconds to avoid more retries.
    response.status(statusCode).send(
     {
      message: httpsResponseMessage,
     }
    );

    // Do nothing after calling .send() because the function has terminated
  }

Pub/Sub delivers fast performance with minimal latency, ensuring your functions stay warm and execute swiftly even with high concurrency levels.

Avoid using Firestore for this purpose due to cost implications associated with creating documents and triggering functions. Furthermore, excessive document creation leads to increased costs and operational complexities. Firestore triggers operate by emitting Pub/Sub messages, introducing document writes would add unnecessary latency.


Sample PubSub Function

Firebase-functions being deemed heavy and cumbersome, here's a simplified example of a PubSub-triggered function:

Assuming this function expects a JSON string as the message sent to the PubSub topic, in the earlier HTTP function example, you could publish a message similar to this:

const message1 = {
  "foo": "bar",
  "bar": "baz"
}
let statusCode = 202 // Accepted

try {
  await pubSubTopic1.publishMessage({ json: message1 })
} catch (error) {
  statusCode = 502 // Bad Gateway
} finally {
  res.status(statusCode).send({ message: httpsResponseMessage })
}

Your receiving PubSub function can be concise like so:

export const examplePubSubFunction = message => {
  const data = JSON.parse(Buffer.from(message.data, 'base64').toString())
  console.log(data.foo) // output: "bar"
  console.log(data.bar) // output: "baz"
}

To deploy the function, use the following command:

gcloud functions deploy examplePubSubFunction --runtime=nodejs18 --trigger-topic=the-first-topic-name

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Discover every conceivable combination of items that results in the total values adding up to a specific number

Wanting to achieve this using TypeScript. I am dealing with an array of objects, each containing a property named rating. Here is how the array looks: const objects = [{"name":"foo","rating":4}, {"name":"bar","rating":5}, {"name":"foobar","rating":2}] G ...

What is the best way to transition from using merge in a pipe in v5 to v6?

Currently, I am following the conversion guide found here and I am attempting to convert the merge function used in a pipe according to this guide. However, after making the changes, it does not work as expected. This is the code snippet I am using to exp ...

Tips for generating a subprocess with exec within a TypeScript Class

I am looking to automate the process of creating MRs in GitLab. When a button is clicked in my React/Typescript UI, I want to trigger command-line code execution within my Typescript class to clone a repository. However, every time I attempt to use exec, I ...

Make sure accordion items stay open even when another one is clicked

I have implemented an accordion component that currently opens and closes on click. However, I am facing an issue where clicking on one item closes another item that was previously open, which is not the behavior I desire. I'm unsure of the best appro ...

Utilizing Typescript for parsing large JSON files

I have encountered an issue while trying to parse/process a large 25 MB JSON file using Typescript. It seems that the code I have written is taking too long (and sometimes even timing out). I am not sure why this is happening or if there is a more efficien ...

Is it possible to automatically correct all import statements in a TypeScript project?

After transferring some class member variables to a separate class in another file, I realized that these variables were extensively used in the project. As a result, approximately 1000 .ts files will need their imports modified to point to the new class/f ...

Error: Module not found - TypeScript + Playwright

While working on test automation using playwright, I encountered a coding issue. During the test execution, I received the following error in test.spec.ts: Error: Cannot find module '@common/common' code: 'MODULE_NOT_FOUND' Any advice ...

The concept of `object()` does not function properly in the context of utilizing redux

Reactjs error: TypeError - Object(...) is not a function when integrating Firebase Here is the tutorial video I followed but encountered an error. Screenshot of browser showing the error Code snippet: import React from 'react'; import Rea ...

What is the best way to apply a CSS class to a ComponentRef that has been generated in Angular 5

I am attempting to dynamically add a CSS class to a component right after its creation by utilizing ViewContainerRef and ComponentFactoryResolver. My goal is to determine the class based on which other Components have already been added to myViewContainerR ...

Restricting types through property union types

I'm currently in the process of refining a type to a specific variant within a type, but I am encountering difficulties in accurately defining the correct type. At this moment, my type Item has the potential for having various types for its details. t ...

Sending a specific object and its corresponding key as parameters to a reusable component in Angular: T[K]

I am currently working on developing a generic component in Angular and Typescript version 4.4.4. The goal is to create a component where I can set both an object (obj) and specific keys (properties). Here's a simplified version of my text component: ...

I am puzzled by the Angular production build error I encountered - specifically, the ./src/app/app.module.ngfactory.js

When attempting to run ng serve --prod, I encountered the following error: $ ng build --prod error ERROR in ./src/app/app.module.ngfactory.js Module not found: Error: Can't resolve 'ngx-bootstrap/dropdown/bs-dropdown.module' in 'C:&bso ...

Problem with logging into Google account on Firebase authorization

I have been following the instructions provided on the Firebase authentication for iOS in Swift to implement Google sign-in. https://firebase.google.com/docs/auth/ios/google-signin Despite diligently following all the steps, I am unable to successfully c ...

My goal is to prevent users from using the Backspace key within the input field

Let's say we want to prevent users from using the backspace key on an input field in this scenario. In our template, we pass the $event like so: <input (input)="onInput($event)"> Meanwhile, in our app.component.ts file, the function ...

Can anyone explain why the Splice function is removing the element at index 1 instead of index 0 as I specified?

selectedItems= [5,47] if(this.selectedItems.length > 1) { this.selectedItems= this.selectedItems.splice(0,1); } I am attempting to remove the element at index 0 which is 5 but unexpectedly it deletes the element at index ...

An object in typescript has the potential to be undefined

Just starting out with Typescript and hitting a snag. Can't seem to resolve this error and struggling to find the right solution useAudio.tsx import { useEffect, useRef } from 'react'; type Options = { volume: number; playbackRate: num ...

What is the best way to retrieve a specific field from the observable data stream?

When working with observables, I often find myself using them like this: ... const id = 1337; this.service.getThing(id).subscribe( suc => doSomething(suc.name), err = doSomethingElse() ); Lately, I've been utilizing the async pipe more freque ...

The input 'Query' cannot be matched with the type '[(options?: QueryLazyOptions<Exact<{ where?:"

Using codegen, I've created custom GraphQL hooks and types. query loadUsers($where: UserFilter) { users(where: $where) { nodes { id email firstName lastName phoneNumber } totalCount } } export functio ...

ts:Accessing the state within a Redux store

In my rootReducer, I have a collection of normal reducers and slice reducers. To access the state inside these reducers, I am using the useSelector hook. Here is my store setup : const store = configureStore({reducer : rootReducer}); Main Reducer: const ...

Issue encountered with UglifyJs - Unexpected token: title (Subject)

My attempt to deploy my initial Angular application is not going smoothly. The build process fails and the error message I'm encountering states: ERROR in vendor.809dd4effe018f6b3d20.bundle.js from UglifyJs Unexpected token: name (Subject) [vendo ...