从另一个 Cloud Functions 调用 Cloud Functions

IT技术 javascript node.js firebase google-cloud-platform google-cloud-functions
2021-03-08 06:46:35

我正在使用 Cloud Functions 调用免费 Spark 层上的另一个 Cloud Functions 函数。

有没有一种特殊的方式来调用另一个 Cloud Functions 函数?或者你只是使用标准的 http 请求?

我试过像这样直接调用另一个函数:

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))
})

但我得到了错误

FetchError:请求 https://us-central1-functions- ****.cloudfunctions.net/validate 失败,原因:getaddrinfo ENOTFOUND us-central1-functions-*****.cloudfunctions.net us-central1-functions -*****.cloudfunctions.net:443

这听起来像是 firebase 阻止了连接,尽管它是谷歌拥有的,因此它不应该被锁定

Spark 计划仅允许对 Google 拥有的服务的出站网络请求。

如何使用 Cloud Functions 调用另一个 Cloud Functions 函数?

6个回答

您无需经历通过全新的 HTTPS 调用来调用某些共享功能的麻烦。您可以简单地将代码的常见部分抽象为一个常规的 javascript 函数,该函数由任一函数调用。例如,您可以像这样修改模板 helloWorld 函数:

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!");
}

这两个函数将做完全相同的事情,但具有不同的端点。

如果有人故意想要创建另一个幂等云函数实例,方法是从自身调用它以将长进程分发为块以避免超时,该怎么办?
2021-04-27 06:46:35
@MohammedMaaz 如果您有新问题,您应该将其与您拥有的代码分开发布,但这些代码无法按您预期的方式工作。
2021-05-01 06:46:35
谢谢道格。这回答了我的第一个也是主要的问题。你能告诉我为什么我不能对 *.cloudfunctions.net 进行 http 调用吗?我被称为另一个云函数的原因实际上是为了模拟对我们在非谷歌服务上托管的 API 的外部 API 调用。
2021-05-04 06:46:35
如果您出于任何原因确实需要“步进”函数(假设您正在执行一系列第三方 HTTP 请求)。我建议发布到Cloud Pub/Sub,这可能会异步触发Cloud Pub/Sub 功能HTTP 触发器是同步的,会超时,并且不“持久”。
2021-05-11 06:46:35
*.cloudfunctions.net 目前未列入免费 Spark 层的网络访问白名单。白名单一般是为了防止随意滥用非谷歌服务,您可以将 *.cloudfunctions.net 视为非谷歌服务(因为像您这样的开发人员实际上共享整个空间来提供自己的服务)。
2021-05-15 06:46:35

要回答这个问题,你可以做一个 https 请求来调用另一个云函数:

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 }),
    })
}

(请注意,我们使用 npm 包“node-fetch”作为我们的 fetch 实现。)

然后简单地调用它:

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

这样做是有正当理由的。我们用它每分钟 ping 我们的搜索云功能并保持它运行。这大大降低了每年几美元的响应延迟。

效果很好,从触发器调用它来更新另一个项目。不使用快递。
2021-05-07 06:46:35
如果您使用的是 express 应用程序,这将不起作用,因为 express 会被锁定,直到 fetch 返回,这会导致 fetch 被锁定,因为 express 永远不会回答。
2021-05-09 06:46:35

通过包含授权令牌,可以通过 HTTP 调用另一个 Google Cloud Function。它需要一个主要的 HTTP 请求来计算令牌,然后在调用要运行的实际 Google Cloud 函数时使用该请求。

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.');
  }
};

2021 年 10 月更新:您不需要在本地开发环境中执行此操作,感谢 Aman James 澄清这一点

好吧,我无法使用身份验证google-auth-library,但您的代码运行良好。我唯一更改的是将元数据服务器 URL 更新为http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=,这是记录在cloud.google.com/functions/docs/securing/...cloud.google.com/compute/docs/storing-retrieving-metadata 的内容
2021-04-25 06:46:35
@JohnnyOshika 我不久前写了这个,所以我不太记得为什么我这样做而不是使用google-auth-library. 我认为我的思考过程是“这种方式行得通,所以让我们暂时让它行之有效,以后我们可以让它变得更好。” 我认为使用google-auth-library肯定是首选方式,而不是依赖端点。我更新了我的答案以包含新的 URL,谢谢!
2021-05-02 06:46:35
如何使用此方法向函数发送数据?
2021-05-02 06:46:35
这太好了,谢谢!为什么选择使用计算元数据服务器而不是google-auth-library
2021-05-03 06:46:35
我尝试使用google-auth-library,但它不适用于Google 提供的示例经过一番调查,我发现样本不正确。已经提交了一个问题
2021-05-09 06:46:35

尽管问题标签和其他答案与javascript有关,但我想分享python示例,因为它反映了问题中提到的标题和身份验证方面。

Google Cloud Function 提供了REST API 接口,包括可以在另一个 Cloud Function 中使用的调用方法。尽管文档中提到使用 Google 提供的客户端库,但 Python 上的 Cloud Function 仍然没有。

相反,您需要使用通用的 Google API 客户端库。[这是python一个]。3

使用这种方法的主要困难可能是对身份验证过程的理解。通常,您需要提供两件事来构建客户端服务: 凭据作用域

获取凭据的最简单方法是在应用程序默认凭据 (ADC) 库上进行中继。有关此的正确文档是:

  1. https://cloud.google.com/docs/authentication/production
  2. https://github.com/googleapis/google-api-python-client/blob/master/docs/auth.md

获取范围的位置是每个 REST API 函数文档页面。像,OAuth 范围:https://www.googleapis.com/auth/cloud-platform

调用 'hello-world' 云函数的完整代码示例如下。运行前:

  1. 在您的项目中在 GCP 上创建默认 Cloud Functions。
  • 保留并注意要使用的默认服务帐户
  • 保留默认正文。
  1. 请注意project_id函数名称您部署函数的位置
  2. 如果您将在 Cloud Function 环境之外调用函数(例如本地),请根据上述文档设置环境变量 GOOGLE_APPLICATION_CREDENTIALS
  3. 如果您要从另一个 Cloud Functions 实际调用,则根本不需要配置凭据。
from googleapiclient.discovery import build
from googleapiclient.discovery_cache.base import Cache
import google.auth

import pprint as pp

def get_cloud_function_api_service():
    class MemoryCache(Cache):
        _CACHE = {}

        def get(self, url):
            return MemoryCache._CACHE.get(url)

        def set(self, url, content):
            MemoryCache._CACHE[url] = content

    scopes = ['https://www.googleapis.com/auth/cloud-platform']

    # If the environment variable GOOGLE_APPLICATION_CREDENTIALS is set,
    # ADC uses the service account file that the variable points to.
    #
    # If the environment variable GOOGLE_APPLICATION_CREDENTIALS isn't set,
    # ADC uses the default service account that Compute Engine, Google Kubernetes Engine, App Engine, Cloud Run,
    # and Cloud Functions provide
    #
    # see more on https://cloud.google.com/docs/authentication/production
    credentials, project_id = google.auth.default(scopes)

    service = build('cloudfunctions', 'v1', credentials=credentials, cache=MemoryCache())
    return service


google_api_service = get_cloud_function_api_service()
name = 'projects/{project_id}/locations/us-central1/functions/function-1'
body = {
    'data': '{ "message": "It is awesome, you are develop on Stack Overflow language!"}' # json passed as a string
}
result_call = google_api_service.projects().locations().functions().call(name=name, body=body).execute()
pp.pprint(result_call)
# expected out out is:
# {'executionId': '3h4c8cb1kwe2', 'result': 'It is awesome, you are develop on Stack Overflow language!'}

这些建议似乎不再有效。

为了让它对我有用,我使用 httpsCallable 从客户端进行调用并将请求导入邮递员。还有一些其他指向https://firebase.google.com/docs/functions/callable-reference 的链接很有帮助。但是确定信息的可用位置需要一些弄清楚。

我在这里写下了所有内容,因为它需要一些解释和一些示例。

https://www.tiftonpartners.com/post/call-google-cloud-function-from-another-cloud-function

这是“url”的内联版本可能会过期。

这“应该”有效,它没有经过测试,而是基于我为自己的应用程序编写和测试的内容。

module.exports = function(name,context) {
    const {protocol,headers} = context.rawRequest;
    const host = headers['x-forwardedfor-host'] || headers.host;
    // there will be two different paths for
    // production and development
    const url = `${protocol}://${host}/${name}`;
    const method = 'post';    
    const auth = headers.authorization;
    
    return (...rest) => {
        const data = JSON.stringify({data:rest});
        const config = {
            method, url, data,
            headers: {
               'Content-Type': 'application/json',
               'Authorization': auth,
               'Connection': 'keep-alive',
               'Pragma': 'no-cache,
               'Cache-control': 'no-cache',
            }
        };
        try {
            const {data:{result}} = await axios(config);
            return result;        
        } catch(e) {
            throw e;
        }
    }
}

这就是您调用此函数的方式。

const crud = httpsCallable('crud',context);
return await crud('read',...data);

您从谷歌云入口点获得的上下文是最重要的部分,它包含对云函数进行后续调用所需的 JWT 令牌(在我的示例中是它的 crud)

要定义另一个 httpsCallable 端点,您将编写如下导出语句

exports.crud = functions.https.onCall(async (data, context) => {})

它应该像魔术一样工作。

希望这会有所帮助。