0
目标:在我的 Angular 应用程序中使用服务器发送的事件和 Express 后端

问题:服务器发送的事件没有到达客户端

后端代码
router.get('/options/:a/:b/:c', async (req, res) => {
    console.log('options endpoint called..', req.params.a,req.params.b,req.params.c);

    // ---- SERVER SENT EVENTS ---- //
   
    // Setting Headers
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader("Access-Control-Allow-Origin", "*");   
    res.setHeader('Connection', 'keep-alive');
    res.flushHeaders(); // flush the headers to establish SSE with client
   
   for(let i=0;i<10;i++){
        console.log("should now send this response...banane")
        res.write('banane!')
    }
});
客户端代码
export class NetworkingService {

  BASE_URL;
  OPTIONS = 'options'
  searchResultSubject: Subject<SearchResults>;


  constructor(private http: HttpClient, private ls: LoggingService, private _zone: NgZone) { // shortened }
  getOptions(a: string, b: string, c: string) {
    this.ls.log("get options endpoint about to be triggered")
    this.getServerSentEvent(this.BASE_URL + this.OPTIONS + "/" + a + "/" + b + "/" + c).subscribe(resp => {
      this.ls.log("got from server: ", resp)
     });
    return this.searchResultSubject;
  }


 getServerSentEvent(url: string): Observable<any> {
    console.log('creating an eventsource...')
    return new Observable(observer => {
      const eventSource = this.getEventSource(url);
      eventSource.onopen = event => {
        console.log('opening connection', eventSource.readyState)
      };
      eventSource.onmessage = event => {
        console.log('event from server..')
        this._zone.run(() => {
          observer.next(event);
        });
      };
      eventSource.onerror = error => {
        console.log('error from server..')
        this._zone.run(() => {
          observer.error(error);
        });
      };
    });
  }

  private getEventSource(url: string): EventSource {
    return new EventSource(url);
  }
}
服务器端日志输出(如预期)
options endpoint called.. A B C
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
客户端日志输出(与预期不同)
get options endpoint about to be triggered
creating an eventsource...
opening connection 1

……然后什么都没有。

我尝试了什么?

我看不出我与这些有何不同:so-questiontutorialtutorial 在 Dev Tools 的 Networks 选项卡中,我看到一个 Status 200,键入带有正确标题的 eventsource 行条目。但只有一个!

我认为我犯了一个非常明显的错误,因为它几乎可以正常工作,并且从示例中看起来很简单。我的 Angular 是 10.1.6 而 express 是 4.17.1 我是直接与 ngZone 交互的新手,是否存在潜在错误?

即使我按照此处的建议注释掉压缩库或使用 res.flush(),问题仍然存在。

4

3 回答 3

3

我遇到了同样的问题,我没有得到客户端的响应。经过多次更改后,它似乎正在工作。

首先我设置标题:

response.setHeader('Cache-Control', 'no-cache');
response.setHeader('Content-Type', 'text/event-stream');
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Connection', 'keep alive');
response.setHeader('X-Accel-Buffering', 'no');
response.flushHeaders(); // flush headers to establish SSE with client

如果您使用的是 compress 中间件,则必须在向客户端发送数据时,将

response.flush();

例子

response.write(`event: ${event}\ndata: ${data}\n\n`);
response.flush();

似乎客户端从生成的事件中接收到消息,也就是说,如果您向客户端发送以下响应

response.write(`event: some_event\ndata: some_data\n\n`);

客户端应该有类似这样的代码:

const eventSource = new EventSource(url);

eventSource.addEventListener('some_event', listener => {
  console.log(listener);
  ...
}, false);

我希望能有所帮助

于 2021-06-08T04:57:04.703 回答
0

对于在搜索类似问题后登陆这里的任何人,我们的问题是我们的 node.js 应用程序托管在 Azure Windows 上,并且它在后台使用 IIS。处理程序 iisnode 具有缓冲响应的默认设置,这就是为什么在客户端从未收到来自我们服务器的响应的原因。我们所要做的就是根据这个 Microsoft 指南编辑 web.config 文件

https://docs.microsoft.com/en-us/azure/app-service/app-service-web-nodejs-best-practices-and-troubleshoot-guide

注意flushResponse部分

于 2022-02-17T16:11:20.750 回答
0

对我有用的方法与 daacdev 所说的相似,但并不完全。

使用的服务器端:

res.setHeader('Cache-Control', 'no-cache, no-transform'); // Notice the no-transform! 
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // Flush the headers to establish SSE with client

// Do some stuff...
// Like sending an event with data
res.write('event: fooEvent\n'); // Note the 1 newline after the event name
res.write(`data: ${JSON.stringify({ foo: 'bar' })}\n\n`); // Note the 2 newlines after the data

// And when you're done, end it
res.end();

客户端,我们有事件类型的事件监听器:

const eventSource = new EventSource('/api/foo');

eventSource.addEventListener('fooEvent', event => {
    console.log('Got fooEvent: ', event.data); // event.data has your data
    eventSource.close(); // Close the eventSource if the client is done
});

您可以完全省略事件类型,仅在res.write(). 在这种情况下,您的客户端会侦听如下事件类型的消息:

eventSource.onmessage = event => {
    console.log('Got some data: ', event.data); // event.data has your data
    eventSource.close(); // Close the eventSource if the client is done

值得注意的是:

  • 除了“no-cache”之外,Cache-Control 标头还需要“no-transform”。在我这样做之前,客户端只会在服务器结束整个事情后才能收到消息
  • 我不需要在每次写入后刷新
  • 您不能发送事件类型。它总是需要数据来跟随它。因此,如果您只是想发送消息,请执行以下操作:
res.write('event: doubleProcesses\n');
res.write('data: doubleProcesses\n\n');
  • 必须遵守定义的结构:可选的“事件”(后跟冒号、名称和 1 个换行符)和必需的“数据”(后跟冒号,您的数据作为字符串和 2 个换行符)。你不能简单地把任何东西放在你的res.write.

最后一点是肯定你错过了什么。

这里的文档:

于 2021-11-22T00:22:10.590 回答