Flask Docker 容器 SocketIO 问题

Posted

技术标签:

【中文标题】Flask Docker 容器 SocketIO 问题【英文标题】:Flask Docker container SocketIO Issues 【发布时间】:2021-03-19 05:07:40 【问题描述】:

我有一个使用 SocketIO 从 Postgres 实时获取数据的 Flask 应用程序。

当我在本地运行时,该应用程序运行良好。

当我使用 docker-compose 来托管我的 Flask 应用程序时,就会出现这个问题。我的 JS 客户端和烧瓶服务器托管在同一个应用程序和同一个容器上。

我在JS中的socketio是这样的:

var socket = io().connect(window.location.protocol + '//' + document.domain + ':' + location.port);

Dockerfile:

# Using python 3.7 in Alpine
FROM python:3.6.5-stretch

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
ADD . /app

RUN apt-get update -y && apt-get upgrade -y

# Install the dependencies from requirements
RUN pip install -r requirements.txt

# Tell the port number the container should expose
EXPOSE 8083

# Run the command
ENTRYPOINT ["./entry.sh"]

entry.sh:

#!/bin/sh
gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -b :8083 -w 5 run:app

我的 docker-compose 是这样的:

version: "3.8"

services:
  fortweet:
    container_name: fortweet
    build: ./
    env_file:
      - secret.env
    networks:
      - plutusnet
    ports:
      - 8083:8083
    restart: always

networks:
  plutusnet:
    name: plutus_network
    driver: bridge

我也尝试过使用var socket = io.connect('http://public_ip_of_website:8083'),但我的套接字连接仍然无法正常工作。

它应该如何正常工作是当我在本地运行它并单击某个按钮时,它会在我的 JS 中执行此功能:

$("#tweets-live-start").click(function()
    if (is_streaming == true)
      alert("A stream is already running")
    else
      $.ajax(
        type: "POST",
        url : "/admin/startstream",
        data: url : "print \"hello\"",
        contentType: 'application/json;charset=UTF-8'
      );
    
  );

当我的服务器收到问候时,它会启动一条推文流并通过套接字发出它们。然后我的套接字会这样捕获它们:

// Listens for tweets
socket.on('stream-results', function(results)

  // Insert tweets in divs
  $('#live-tweet-container').prepend(`
  <div class="row justify-content-md-center mt-3">
    <div class="col-md-2">
        <img    src="$results.profile_pic !== "" ? results.profile_pic : "/static/icons/profile-pic.png"" class="mx-auto d-block rounded"  >
    </div>
    <div class="col-md-8 my-auto">
      <div><b>$results.author</b></div>
      <div>$results.message</div>
    </div>
  </div>
  `);
);

但是当我在 docker 上运行它时,什么都没有发生。

当我检查我的浏览器 JS 控制台时,它似乎正在轮询一个错误的请求,我不知道为什么:

index.js:83 GET http://th3pl4gu3.com:8083/socket.io/?EIO=3&transport=polling&t=NPYYrxr 400 (BAD REQUEST)

这是我的 docker ps 以获取更多信息:

46446efeb472   mervin16/fortweet:dev   "/bin/bash entry.sh"  About a minute ago   Up About a minute   0.0.0.0:8083->8083/tcp   fortweet

12b2bff36af0   postgres    "docker-entrypoint.s…"   2 hours ago          Up 2 hours          0.0.0.0:5432->5432/tcp   plutus

我不认为这是一个可访问性问题,因为我从每个容器和每个容器都尝试了几次 telnet 测试。

我检查了 docker 容器的日志,结果如下:

fortweet    | [2020-12-26 15:18:55 +0000] [8] [INFO] Starting gunicorn 20.0.4
fortweet    | [2020-12-26 15:18:55 +0000] [8] [INFO] Listening at: http://0.0.0.0:8083 (8)
fortweet    | [2020-12-26 15:18:55 +0000] [8] [INFO] Using worker: geventwebsocket.gunicorn.workers.GeventWebSocketWorker
fortweet    | [2020-12-26 15:18:55 +0000] [11] [INFO] Booting worker with pid: 11
fortweet    | [2020-12-26 15:18:55 +0000] [12] [INFO] Booting worker with pid: 12
fortweet    | [2020-12-26 15:18:55 +0000] [13] [INFO] Booting worker with pid: 13
fortweet    | [2020-12-26 15:18:55 +0000] [14] [INFO] Booting worker with pid: 14
fortweet    | [2020-12-26 15:18:55 +0000] [15] [INFO] Booting worker with pid: 15
fortweet    | The client is using an unsupported version of the Socket.IO or Engine.IO protocols (further occurrences of this error will be logged with level INFO)
fortweet    | The client is using an unsupported version of the Socket.IO or Engine.IO protocols (further occurrences of this error will be logged with level INFO)
fortweet    | 172.16.0.1 - - [2020-12-26 15:19:57] "POST /admin/startstream HTTP/1.1" 204 170 0.023672
fortweet    | The client is using an unsupported version of the Socket.IO or Engine.IO protocols (further occurrences of this error will be logged with level INFO)
fortweet    | The client is using an unsupported version of the Socket.IO or Engine.IO protocols (further occurrences of this error will be logged with level INFO)
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:20] "GET /socket.io/?EIO=3&transport=polling&t=1608996021267-7 HTTP/1.1" 400 195 0.001418
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:20] "GET /socket.io/?EIO=3&transport=polling&t=1608996021267-7 HTTP/1.1" 400 195 0.001418
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:21] "GET /socket.io/?EIO=3&transport=polling&t=1608996021395-8 HTTP/1.1" 400 195 0.001625
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:21] "GET /socket.io/?EIO=3&transport=polling&t=1608996021395-8 HTTP/1.1" 400 195 0.001625
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:26] "GET /socket.io/?EIO=3&transport=polling&t=1608996026417-9 HTTP/1.1" 400 195 0.001367
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:26] "GET /socket.io/?EIO=3&transport=polling&t=1608996026417-9 HTTP/1.1" 400 195 0.001367
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:26] "GET /socket.io/?EIO=3&transport=polling&t=1608996027270-8 HTTP/1.1" 400 195 0.003811
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:26] "GET /socket.io/?EIO=3&transport=polling&t=1608996027270-8 HTTP/1.1" 400 195 0.003811
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:34] "POST /admin/startstream HTTP/1.1" 204 170 0.015831
fortweet    | 172.16.0.1 - - [2020-12-26 15:20:36] "GET /socket.io/?EIO=3&transport=polling&t=1608996036486-11 HTTP/1.1" 400 195 0.001096

仅供参考,plutus 容器只是我的 Web 应用程序连接到的 Postgres 数据库。

谁能帮帮我?

【问题讨论】:

有人可以帮帮我吗? 尝试连接时遇到什么样的错误? http://fortweet:8083 应该适用于容器到容器的交互,http://public_ip_of_website:8083 应该适用于外部到容器。您能给我们回溯或错误消息吗? 当我在本地运行它并单击某个按钮时,它会向我的服务器发送数据,然后我的服务器会做出响应。但是在 docker 容器上什么也没有发生。我用套接字应该如何工作来更新我的问题。请帮帮我 您能否打开您的开发工具控制台,找到失败的请求并将其添加到您的问题中?或服务器端日志(如果有)。 (铬:developers.google.com/web/tools/chrome-devtools/open,火狐:developer.mozilla.org/en-US/docs/Tools/Network_Monitor) 也只是吹毛求疵,但你为什么要使用 #!/bin/sh shebang 作为你明确调用以在 bash 中执行的脚本? 【参考方案1】:

TL;DR - 您在客户端和服务器之间使用了不兼容的 socketIO 版本。检查下表并确保您使用的是适当的 python 和 javascript 版本。

就像容器日志所说,您在客户端和服务器之间使用了不兼容的 SocketIO 版本。 SocketIO 和 EngineIO 协议已经过多次修改,它们是 not all backward compatible,因此您必须确保在客户端和服务器端使用可以相互通信的协议的适当实现。

我怀疑当您在本地而不是在 Docker 容器中运行应用程序时它工作的原因是因为容器的 requirements.txt 中的依赖项引用了较旧的、不兼容的 python 实现版本。根据您的描述,本地安装的socketIO的python实现似乎是一个较新的版本,因此在客户端与较新的JS版本兼容,并且连接没有问题(或者反之亦然...... .较旧的客户端,较新的服务器)。

如果您在客户端使用本机 javascript Socket.IO version 3.0+(它实现了 SocketIO Protocol v5 和 EngineIO Protocol v4),那么您需要确保在客户端使用适当版本的 Python 实现服务器端。您没有具体说明,但我假设您使用的是 Flask-SocketIO,它本身是 python-socketio 的包装,是 SocketIO 协议的实际 Python 实现。

检查您在 Javascript 中使用的 SocketIO 客户端版本。然后根据下表 (source) 检查您的 requirements.txt 并确保 python-socketio 版本兼容:

JS SocketIO Version SocketIO Protocol EngineIO Protocol python-socketio
0.9.x 1, 2 1, 2 Not supported
1.x and 2.x 3, 4 3 4.x
3.x 5 4 5.x

您很可能在 python 端使用 JS 版本 3.x 和 4.x 版本(不兼容)。确保您使用的是Flask-SocketIOv5.x、python-socketiov5.x 和python-engineiov4.x,并且您的 JS 客户端是 3.x。这应该可以解决您的问题。

如果这在您的本地环境中正常工作,那么您可以简单地运行 pip freeze &gt; requirements.txt 并将其用于您的 docker 构建。这个requirements.txt 文件将具有正确的依赖关系,因为当您在本地运行时它显然可以工作。

或者,如果您想确保您拥有所有内容的最新版本,您可以运行pip install --upgrade flask-socketio。这将安装最新版本的 Flask-SocketIO 和最新的依赖项(包括 python-socketio 和 python-engineio)。然后重新生成你的 requirements.txt 文件并在你的 docker build 中使用它。

【讨论】:

你是对的!正是你说的!套接字工作但效率不高。有些消息被捕获,有些则没有。无论如何,既然主要问题已经解决,我可以更好地解决这个问题。谢谢老哥【参考方案2】:

400 (BAD REQUEST) 表示 Your Browser+JS 和 Flask 应用程序之间存在通信。

我怀疑 Flask 应用程序和 Postgres 之间存在问题。

您的 Postgres 必须与您的应用程序服务器(您在 docker-compose 中称为“fortweet”的服务)在同一个网络中。另外,您已经给它一个主机名,以便应用程序服务器可以在内部解析它。

version: "3.8"

services:
  postgres:                               #    <=== same name here 
    image: postgres/postgres:11
    networks:
      - plutusnet                      #  <== same network

  fortweet:
    container_name: fortweet
    build: ./
    env_file:
      - secret.env
    networks:
      - plutusnet                        #  <=== same network
    links:
      - db:postgres                      #    <== than here
    ports:
      - 8083:8083
    restart: always

networks:
  plutusnet:
    name: plutus_network
    driver: bridge`

然后必须将应用配置为使用“postgres:5432”连接到数据库。

试一试告诉我们。

【讨论】:

与数据库的连接有效,因为当我的应用程序启动时,它会为数据库创建一个默认来宾用户。而且我的推文是通过实时连接发出的,而无需通过数据库。当你说浏览器和JS之间的通信不起作用时,我认为你是对的,但我不知道为什么。这在本地运行良好,但在 Docker 上却不行。

以上是关于Flask Docker 容器 SocketIO 问题的主要内容,如果未能解决你的问题,请参考以下文章

Flask 和 Flask-SocketIO [重复]

使用 Flask-socketio 和 socketIO 客户端

Flask-socketio,向另一个命名空间发出事件

Flask + RabbitMQ + SocketIO - 转发消息

尽管使用了 socketio.run(),但“Flask-SocketIO 正在 Werkzeug 下运行”

[翻译] flask-SocketIO