Функция Firebase Cloud зависает после получения 204 кода ответа - PullRequest
0 голосов
/ 28 апреля 2020

Я пишу облачную функцию для запуска или остановки сервера для используемой игровой панели. Все работает, за исключением случаев, когда запрос завершается, он никогда не вызывает события «data», «end» или «closed», которые приводят к зависанию Firebase. Кто-нибудь еще сталкивался с этим раньше? Я погуглил «Узел http висит после получения ответа 204», но не нашел ничего релевантного.

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

Ответы [ 2 ]

1 голос
/ 28 апреля 2020
Ответ

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

Но поскольку @ Renaud предоставил в свой ответ , часто проще использовать стороннюю библиотеку запросов, которая обрабатывает данные за вас. Рекомендуется выбирать библиотеку, построенную на Promises, поскольку они хорошо работают в облачных функциях. Другая причина, по которой следует избегать использования базовых библиотек http и https, заключается в том, что обработка нескольких объектов, называемых req / request и res / response, может привести к путанице при просмотре кода.

1 голос
/ 28 апреля 2020

Я думаю, что вместо библиотеки https вы должны использовать библиотеку, которая возвращает Promises, например ax ios (см. Также другие параметры, упомянутые samthecodingman в его комментарии).

Следующие модификации с топором ios должны помочь (не проверено):

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

});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...