【问题标题】:Firebase Cloud function hanging after receiving 204 response codeFirebase Cloud 功能在收到 204 响应码后挂起
【发布时间】:2020-04-28 03:40:25
【问题描述】:

我正在编写一个云函数来启动或停止我正在使用的游戏面板的服务器。除非请求完成,否则一切正常,它永远不会触发导致 Firebase 挂起的“数据”、“结束”或“关闭”事件。有没有其他人遇到过这个?我用谷歌搜索“收到 204 响应后节点 http 挂起”,但没有找到任何相关信息。

import * as functions from "firebase-functions";
import * as http from "https";
import * as qs from "querystring";

export const commandServer = functions.https.onRequest((request, response) => {
  if (request.body.command !== "start" && request.body.command !== "stop") {
    response.status(400).send("Error, missing information.");
  }

  console.log(request.body.origin);

  const apiKey = "<apiKey>";
  const panelServerId = "<panelId>";

  const data = qs.stringify({
    signal: request.body.command === "start" ? "start" : "stop",
  });

  const options = {
    protocol: "https:",
    hostname: "<server url>",
    port: 443,
    path: `/api/client/servers/${panelServerId}/power?`,
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      "Content-Length": data.length,
      Authorization: `Bearer ${apiKey}`,
    },
  };

  const req = http.request(options, (res) => {
    console.log(`statusCode: ${res.statusCode}`);
    res.on("data", () => {
      response.status(200).send(`Successfuly sent ${data} command`);
    });
  });

  req.on("error", (error) => {
    console.log("error: ", error);
    response.status(500).send("Oops, something went wrong!");
  });

  req.write(data);
  req.end();
});

【问题讨论】:

  • 我希望你正在制定计划,因为 Firebase 不允许第三方从免费帐户中的功能调用。
  • @Sandeep 我已经这样做了,我必须早点弄清楚。
  • 好的。可以尝试使用 fetch / axios 。我过去曾尝试过

标签: javascript node.js firebase google-cloud-functions


【解决方案1】:

我认为您应该使用返回 Promises 的库而不是 https 库,例如 axios(另请参阅 samthecodingman 在他的评论中提到的其他选项)。

使用 axios 进行以下修改应该可以解决问题(未经测试):

import * as functions from "firebase-functions";
import * as axios from "axios";
import * as qs from "querystring";


export const commandServer = functions.https.onRequest((request, response) => {
    if (request.body.command !== "start" && request.body.command !== "stop") {
        response.status(400).send("Error, missing information.");
    }

    console.log(request.body.origin);

    const apiKey = "<apiKey>";
    const panelServerId = "<panelId>";

    const data = qs.stringify({
        signal: request.body.command === "start" ? "start" : "stop",
    });

    const options = {
        url: "<server url>",   //Assign the absolute url with protocol, host, port, path, etc... See the doc https://github.com/axios/axios#request-config
        method: "post",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            "Content-Length": data.length,
            Authorization: `Bearer ${apiKey}`,
        },
    };

    axios.request(options)
        .then(res => {
            console.log(`statusCode: ${res.statusCode}`);
            response.status(200).send(`Successfuly sent ${data} command`);
        })
        .catch(err => {
            console.log("error: ", error);
            response.status(500).send("Oops, something went wrong!");        
        })

});

【讨论】:

  • request-promise 已弃用。 axios、gaxios 和 node-fetch 是合适的替代方案。
  • 需要一个 HTTP 库对于单一目的的方法来说似乎相当繁重。
  • 嗯,您需要等待异步 HTTP POST 操作完成,然后再发回响应,通常我们使用 Promise 来控制它。我建议您观看 Firebase 视频系列中有关“JavaScript Promises”的 3 个视频:firebase.google.com/docs/functions/video-series
【解决方案2】:

204 No Content 响应没有任何内容,因此不会触发任何 "data" 事件。相反,您应该监听"end" 事件。

此外,当返回的数据流入时,可能会触发多个"data" 事件。每个流式传输的数据块在使用之前应与其他块组合 - 例如将每个块附加到一个字符串,一个文件或通过数据管道传递。

这是一个基于http.get(...) 文档的更正示例:

const req = http.request(options, (res) => {
  console.log(`statusCode: ${res.statusCode}`);

  res.setEncoding('utf8');
  let rawData = '';
  res.on('data', (chunk) => { rawData += chunk; });
  res.on('end', () => {
    if (!res.complete) {
      // connection lost before complete response was received
      response.status(500).send('The connection to the server was terminated while the command was still being sent.');
      return;
    }

    if (res.statusCode < 200 || res.statusCode >= 300) {
      // unexpected status code
      response.status(500).send('Server responded unexpectedly.');
      return;
    }

    if (rawData === '') {
      // no body recieved
      response.status(200).send(`Successfuly sent ${data} command and got a ${res.statusCode} response without a body`);
      return;
    }

    // try to parse body as JSON
    try {
      const parsedData = JSON.parse(rawData);
      console.log(`Server gave a ${res.statusCode} response. JSON body: `, parsedData);
      response.status(200).send(`Successfuly sent ${data} command and got a ${res.statusCode} response with data: ${JSON.stringify(parsedData)}`);
    } catch (error) {
      // JSON parse error
      console.error(`Failed to parse response body as JSON`, { statusCode: res.statusCode, error, rawData });
      response.status(500).send(`Successfuly sent ${data} command and got a ${res.statusCode} response, but the response body could not be parsed as JSON.`);
    }
  });
});

但是正如their answer 中提供的@Renaud,使用为您处理细节的第三方请求库通常更简单。建议选择一个围绕 Promises 构建的库,因为它们在 Cloud Functions 中运行良好。避免使用基本 http 和 https 库的另一个原因是,处理多个名为 req/request 和 res/response 的对象可能会在查看代码时导致混淆。

【讨论】:

  • 我尝试了“数据”、“结束”和“关闭”事件,但没有成功。他们都挂了,就好像永远不会触发“结束”事件一样。
  • 你会推荐哪个库?我想要一些轻量级的东西,因为我没有做任何复杂的事情。
  • 您是否在顶部获得了“statusCode”日志?要尝试的一些事情:Content-Length 可能应该是 Buffer.byteLength(data)。确保同时使用 close 事件和 end 事件处理程序,因为如果连接意外关闭,将不会调用 end。
  • 按从轻到重的顺序(在撰写本文时):this answer、node-fetch、gaxios、request-promise-native(已弃用)、request-promise(已弃用)、axios
  • 感谢您的回复。我会试一试,看看什么效果最好,然后报告。
猜你喜欢
  • 1970-01-01
  • 2021-01-18
  • 2018-09-30
  • 1970-01-01
  • 2018-09-17
  • 1970-01-01
  • 2018-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多