Получение net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 при использовании потока событий с использованием EventSource в ReactJs

У меня есть очень простая служба узла, предоставляющая конечную точку, предназначенную для использования соединения Server Send Events (SSE), и очень простой клиент ReactJs, использующий его через EventSource.onmessage.

Во-первых, когда я устанавливаю точку отладки в updateAmountState (Chrome Dev), я не вижу ее вызова.

Во-вторых, я получаю net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (ОК). Согласно https://github.com/aspnet/KestrelHttpServer/issues/1858 "ERR_INCOMPLETE_CHUNKED_ENCODING в chrome обычно означает, что неперехваченное исключение было выброшено из приложения в середине записи в тело ответа». Затем я проверил серверную часть, чтобы увидеть, не нахожу ли я какую-либо ошибку. Что ж, я установил точку останова в нескольких местах в server.js в обоих setTimeout(() => {... и я вижу, что он запускается периодически. Я ожидал, что каждая строка будет запускаться только один раз. Так что, похоже, интерфейс пытаясь постоянно вызывать бэкэнд и получая какую-то ошибку.

Все приложение, как внешнее в ReactJ, так и сервер в NodeJ, можно найти на https://github.com/jimisdrpc/hello-pocker-coins.

серверная часть:

const http = require("http");

http
  .createServer((request, response) => {
    console.log("Requested url: " + request.url);

    if (request.url.toLowerCase() === "/coins") {
      response.writeHead(200, {
        Connection: "keep-alive",
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache"
      });

      setTimeout(() => {
        response.write('data: {"player": "Player1", "amount": "90"}');
        response.write("\n\n");
      }, 3000);

      setTimeout(() => {
        response.write('data: {"player": "Player2", "amount": "95"}');
        response.write("\n\n");
      }, 6000);
    } else {
      response.writeHead(404);
      response.end();
    }
  })
  .listen(5000, () => {
    console.log("Server running at http://127.0.0.1:5000/");
  });

внешний интерфейс:

import React, { Component } from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
import { getInitialCoinsData } from "./DataProvider";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: getInitialCoinsData()
    };

    this.columns = [
      {
        Header: "Player",
        accessor: "player"
      },
      {
        Header: "Amount",
        accessor: "amount"
      }
    ];
    this.eventSource = new EventSource("coins");
  }


  componentDidMount() {
    this.eventSource.onmessage = e =>
      this.updateAmountState(JSON.parse(e.data));
  }

  updateAmountState(amountState) {
    let newData = this.state.data.map(item => {
      if (item.amount === amountState.amount) {
        item.state = amountState.state;
      }
      return item;
    });

    this.setState(Object.assign({}, { data: newData }));
  }

  render() {
    return (
      <div className="App">
        <ReactTable data={this.state.data} columns={this.columns} />
      </div>
    );
  }
}

export default App;

Исключение, которое я вижу в хроме:

введите описание изображения здесь

Итак, мой прямой вопрос: почему я получаю ERR_INCOMPLETE_CHUNKED_ENCODING 200? Я что-то упустил в бэкэнде или во внешнем интерфейсе?

Некоторые советы могут мне помочь:

  1. Почему я вижу websocket в статусе oending, если я вообще не использую websocket? Я знаю основную разницу (веб-сокет является двусторонним, спереди назад и сзади вперед, и это другой протокол, в то время как SSE работает через http и работает только сзади). Но я вообще не собираюсь использовать веб-сокет. (см. синюю линию на принтскрине ниже)

  2. Почему я вижу, что источник событий с 0 байтами и 236 байтами не прошел. Я понимаю, что источник событий — это именно то, что я пытаюсь использовать, когда закодировал «this.eventSource = new EventSource («монеты»);». (см. строку чтения на принтскрине ниже)

  3. Очень странно, по крайней мере для меня, некоторое время, когда я убивал подачу, я мог видеть вызванный метод updateAmountState.

  4. Если вызвать localhost: 5000/coins в браузере, я вижу, что сервер отвечает на ответ (обе строки json). Могу ли я предположить, что я правильно закодировал сервер, а ошибки - это что-то исключительно во внешнем интерфейсе?


person Jim C    schedule 04.06.2019    source источник


Ответы (2)


Я сам не эксперт Node.js, но похоже, что вы пропустили «Connection»: «keep-alive» и «\n» после этого, т.е.:

response es.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
});
response.write('\n');

см. https://jasonbutz.info/2018/08/server-sent-events-with-node/. Надеюсь, что это работает!

person Milad    schedule 15.10.2019

Вот ответы на ваши вопросы.

  1. websocket, который вы видите, не связан с кодом, который вы разместили здесь. Это может быть связано с другим пакетом NPM, который вы используете в своем приложении. Возможно, вы сможете выяснить, откуда он исходит, просмотрев заголовки в сетевом запросе.
  2. Наиболее вероятной причиной сбоя запросов eventsource является их тайм-аут. Браузер Chrome уничтожит неактивный поток через две минуты бездействия. Если вы хотите сохранить его живым, вам нужно добавить некоторый код для отправки чего-либо с сервера в браузер не реже одного раза в две минуты. На всякий случай лучше отправлять что-то каждую минуту. Пример того, что вам нужно, приведен ниже. Он должен делать то, что вам нужно, если вы добавите его после второго setTimeout в свой серверный код.
const intervalId = setInterval(() => {
  res.write(`data: keep connection alive\n\n`);
  res.flush();
}, 60 * 1000);

req.on('close', () => {
  // Make sure to clean up after yourself when the connection is closed
  clearInterval(intervalId);
});
  1. Я не уверен, почему вы иногда видите, что вызывается метод updateAmountState. Если вы не видите его постоянно, это, вероятно, не является серьезной проблемой, но может помочь очистить setTimeout в случае, если сервер остановится до их завершения. Вы можете сделать это, объявив их как переменные, а затем передав имена переменных в clearTimeout в обработчике события закрытия, подобно тому, как я сделал для интервала в примере в # 2 выше.
  2. Ваш код настроен правильно, и ошибка, которую вы видите, связана с тайм-аутом браузера Chrome. Используйте что-то вроде кода в ответе № 2 выше, если вы хотите, чтобы ошибки не возникали.
person NFab    schedule 10.01.2020