在 ReactJs 中使用 EventSource 使用事件流时获得 net::ERR_INCOMPLETE_CHUNKED_ENCODING 200

Getting net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 when consuming event-stream using EventSource in ReactJs

我有一个非常简单的节点服务,它公开了一个旨在使用服务器发送事件 (SSE) 连接的端点和一个非常基本的 ReactJs 客户端,通过 EventSource.onmessage.

使用它

首先,当我在 updateAmountState (Chrome Dev) 中设置调试点时,我看不到它被调用。

其次,我得到 net::ERR_INCOMPLETE_CHUNKED_ENCODING 200(好的)。根据https://github.com/aspnet/KestrelHttpServer/issues/1858"ERR_INCOMPLETE_CHUNKED_ENCODING in chrome usually means that an uncaught exception was thrown from the application in the middle of writing to the response body"。然后我检查了服务器端,看是否发现任何错误。好吧,我在 setTimeout(() => {... 的 server.js 的几个地方设置了断点,我定期看到它 运行 。我希望每行到 运行 一次只有。所以看起来前端正在尝试永久调用后端并出现一些错误。

整个应用程序,包括 ReactJs 中的前端和 NodeJs 中的服务器都可以在 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;

我在 chrome 上看到的异常:

所以我的直接问题是:为什么我得到 ERR_INCOMPLETE_CHUNKED_ENCODING 200?我在后端或前端缺少什么吗?

一些提示可能对我有帮助:

  1. 既然我根本没有使用 websocket,为什么我看到 websocket 处于 oending 状态?我知道基本区别(websocket 是双向的,从前到后和从后到前,是一种不同的协议,而 SSE 运行 通过 http 并且只是从后到前)。但我根本无意使用 websocket。 (请参阅下方打印屏幕中的蓝线)

  2. 为什么我看到0字节和236字节的事件源都失败了。我知道事件源正是我在编写 "this.eventSource = new EventSource("coins"); 时尝试使用的。 (请参阅下面打印屏幕中的读取行)

  3. 至少对我来说很奇怪,有时当我终止服务时,我可以看到 updateAmountState 方法被调用。

  4. 如果在浏览器中调用 localhost:5000/coins,我可以看到服务器响应响应(两个 json 字符串)。我可以假设我对服务器进行了正确编码,而错误是前端独有的吗?

我自己不是 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/。希望有用!

这里是您问题的答案。

  1. 您看到的 websocket 运行 与您在此处发布的代码无关。它可能与您在应用中使用的另一个 NPM 包有关。您可以通过查看网络请求中的 headers 来找出它的来源。
  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 方法被调用。如果您没有始终如一地看到它,它可能不是主要问题,但它可能有助于清理 setTimeouts 以防服务器在它们完成之前停止。您可以通过将它们声明为变量然后将变量名称传递给关闭事件处理程序中的 clearTimeout 来完成此操作,类似于我在上面 #2 中的示例中对间隔所做的操作。
  2. 您的代码设置正确,您看到的错误是由于 Chrome 浏览器超时造成的。如果您想阻止错误发生,请使用上面答案 #2 中的代码。