Socket.io无法通过Cloudflare / Nginx断开连接

时间:2019-03-24 15:05:57

标签: javascript node.js nginx socket.io cloudflare

我有一个小型Web应用程序,与Express.js一起提供,并通过Socket.io连接到后端。为了公开,我使用Nginx作为反向代理,然后在最前面使用Cloudflare。该应用在关闭或重新加载时依赖于disconnect事件触发,以跟踪在线用户。通过Nginx和Cloudflare时,断开事件永远不会在后端触发。在本地开发时会这样做。

这是我的Nginx配置文件:

server {
    listen 80;
    server_name colab.gq;
    server_name www.colab.gq;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header X-Forwarded-For $remote_addr;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/colab.gq/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/colab.gq/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

这是我的服务器端代码的一部分:

io.on('connection', (socket) => {
  // Stuff here

  socket.on('disconnect', () => {
    console.log('disconnected!')
    // Stuff that relies on the disconnect event
  })
})

当用户关闭选项卡或重新加载页面时,断开事件应触发,但在通过Nginx和Cloudflare传递连接时则不会触发。预先感谢您的帮助!

更新:似乎在重新加载/关闭断开事件最终注册几秒钟之后。

2 个答案:

答案 0 :(得分:1)

您必须像这样在nginx配置中向您的套接字io路径添加升级标头,

    location ~* \.io {
      .. your configuration

      proxy_pass http://localhost:3000;
      proxy_redirect off;

      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }

由于您要求扩展答案,因此您可能已经知道的第一件事就是socket.io是一个在后台使用Websockets协议的协议(因此两者是相同的)。作为标准,Websockets协议和HTTP协议都在同一端口80或443上侦听。默认协议是HTTP,如果用户要使用Websockets协议,则他/她必须将升级请求从HTTP发送到WS协议,并且存在一些密钥身份验证和步骤。

这就是为什么需要将它们放入nginx配置中的原因。

如果需要有关协议升级机制的更多信息,请参考this

即使我认为这并非this问题的完全重复,但我仍然有义务为@Paulo表示赞赏,即使没有被接受也能提供完美的答案。

答案 1 :(得分:1)

可能发生的是两件事。

1st:Socket.io超时时间很长(因此需要几秒钟),并且在声明已断开连接之前希望重新连接。检查是否存在reconnect_attempt或reconnecting事件(请注意,我已经有一段时间没有使用过socket.io了,所以在时间安排上我可能是错误的)

2nd:您没有告诉socket.io在关闭浏览器窗口时断开连接。

我建议向您的JavaScript添加一个事件侦听器,该事件侦听器将告诉您的服务器在关闭窗口时它已断开连接。

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {IContact } from 'src/app/models';

@Injectable({
  providedIn: 'root',
})

export class MyService {

 private  baseUrl : string = '......api Url.....';

 constructor(private http: HttpClient) {
}

public  updateContact(Id : string): Observable<object> {
  const apiUrl: string = `${this.baseUrl}/contacts/${Id}`;

  return this.http.put(apiUrl, contact);
}

}