I am using a Cloud Function to call another Cloud Function on the free spark tier.

Is there a special way to call another Cloud Function? Or do you just use a standard http request?

I have tried calling the other function directly like so:

exports.purchaseTicket = functions.https.onRequest((req, res) => {    
  fetch('https://us-central1-functions-****.cloudfunctions.net/validate')
    .then(response => response.json())
    .then(json => res.status(201).json(json))
})

But I get the error

FetchError: request to https://us-central1-functions-****.cloudfunctions.net/validate failed, reason: getaddrinfo ENOTFOUND us-central1-functions-*****.cloudfunctions.net us-central1-functions-*****.cloudfunctions.net:443

Which sounds like firebase is blocking the connection, despite it being a google owned, and therefore it shouldn't be locked

the Spark plan only allows outbound network requests to Google owned services.

How can I make use a Cloud Function to call another Cloud Function?


Solution 1:

You don't need to go through the trouble of invoking some shared functionality via a whole new HTTPS call. You can simply abstract away the common bits of code into a regular javascript function that gets called by either one. For example, you could modify the template helloWorld function like this:

var functions = require('firebase-functions');

exports.helloWorld = functions.https.onRequest((request, response) => {
  common(response)
})

exports.helloWorld2 = functions.https.onRequest((request, response) => {
  common(response)
})

function common(response) {
  response.send("Hello from a regular old function!");
}

These two functions will do exactly the same thing, but with different endpoints.

Solution 2:

To answer the question, you can do an https request to call another cloud function:

export const callCloudFunction = async (functionName: string, data: {} = {}) => {
    let url = `https://us-central1-${config.firebase.projectId}.cloudfunctions.net/${functionName}`
    await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ data }),
    })
}

(Note we are using the npm package 'node-fetch' as our fetch implementation.)

And then simply call it:

callCloudFunction('search', { query: 'yo' })

There are legitimate reasons to do this. We used this to ping our search cloud function every minute and keep it running. This greatly lowers response latency for a few dollars a year.

Solution 3:

It's possible to invoke another Google Cloud Function over HTTP by including an authorization token. It requires a primary HTTP request to calculate the token, which you then use when you call the actual Google Cloud Function that you want to run.

https://cloud.google.com/functions/docs/securing/authenticating#function-to-function

const {get} = require('axios');

// TODO(developer): set these values
const REGION = 'us-central1';
const PROJECT_ID = 'my-project-id';
const RECEIVING_FUNCTION = 'myFunction';

// Constants for setting up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const functionURL = `https://${REGION}-${PROJECT_ID}.cloudfunctions.net/${RECEIVING_FUNCTION}`;
const metadataServerURL =
  'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenUrl = metadataServerURL + functionURL;

exports.callingFunction = async (req, res) => {
  // Fetch the token
  const tokenResponse = await get(tokenUrl, {
    headers: {
      'Metadata-Flavor': 'Google',
    },
  });
  const token = tokenResponse.data;

  // Provide the token in the request to the receiving function
  try {
    const functionResponse = await get(functionURL, {
      headers: {Authorization: `bearer ${token}`},
    });
    res.status(200).send(functionResponse.data);
  } catch (err) {
    console.error(err);
    res.status(500).send('An error occurred! See logs for more details.');
  }
};

October 2021 Update: You should not need to do this from a local development environment, thank you Aman James for clarifying this