pythondjangosocketsnginxpython-socketio

Socket IO is rejecting requests from Nginx proxy


I have this docker application running several containers. One of these containers is a Python application that can handle both socket io requests and normal HTTP requests. Django's ASGI handles HTTP/ASGI requests while python-socketio handles socket requests.

Since there are 4 other applications like this which must all be server via Nginx, I have to specify namespace URLs for both the socket io and ASGI applications for all the applications.

I am using the following configuration for the application:

location /app/ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://app:8000/; # docker container name is "app"
}

location /app/socket.io/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://app:8000/socket.io/; # docker container name is "app"
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
}

asgi.py

import os

import socketio
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")

django_application = get_asgi_application()

from app.async_socketio.server import sio  # noqa 402

# this allows Socket IO to handle requests by default to a
# default path of /socket.io and forwards any other requests
# to the Django application.
application = socketio.ASGIApp(sio, django_application)

# just so all my socket event handles can load
from app.async_socketio.namespace import *  # noqa 403

Entrypoint: main.py which uses Uvicorn

import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "config.asgi:application",
        host="0.0.0.0",
        reload=True,
        log_level="debug",
    )

When I make a request to connect using Postman to URL, http://localhost:8089/app/socket.io/?token=token&organisation_id=org_id, I get the following logs:

app_store_service     | DEBUG:    = connection is CONNECTING
app_store_service     | DEBUG:    < GET /socket.io/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI0MjA5MzQwLCJpYXQiOjE3MjE2MTczNDAsImp0aSI6ImRlMmQ1ZmUyZTk5ZTQ0ZWY5YTdlMTc2NjhmM2UyNTZmIiwidXNlcl9pZCI6IjA2NjdmZDI4LWZkYWMtNzI1Yi04MDAwLWNjM2U5YWE2OGEzNSJ9.zAENI3Xl0k5efwE83u__svuXDUZajv00462XerXCo2c&organisation_id=06689368-db4a-70dd-8000-c37727d378ad&EIO=4&transport=websocket HTTP/1.1
app_store_service     | DEBUG:    < upgrade: websocket
app_store_service     | DEBUG:    < connection: upgrade
app_store_service     | DEBUG:    < host: localhost
app_store_service     | DEBUG:    < x-real-ip: 172.18.0.1
app_store_service     | DEBUG:    < x-forwarded-for: 172.18.0.1
app_store_service     | DEBUG:    < x-forwarded-proto: http
app_store_service     | DEBUG:    < sec-websocket-version: 13
app_store_service     | DEBUG:    < sec-websocket-key: OR4YrxOcqdovU4hNL+xkMw==
app_store_service     | DEBUG:    < sec-websocket-extensions: permessage-deflate; client_max_window_bits
app_store_service     | INFO:     ('172.18.0.21', 48800) - "WebSocket /socket.io/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI0MjA5MzQwLCJpYXQiOjE3MjE2MTczNDAsImp0aSI6ImRlMmQ1ZmUyZTk5ZTQ0ZWY5YTdlMTc2NjhmM2UyNTZmIiwidXNlcl9pZCI6IjA2NjdmZDI4LWZkYWMtNzI1Yi04MDAwLWNjM2U5YWE2OGEzNSJ9.zAENI3Xl0k5efwE83u__svuXDUZajv00462XerXCo2c&organisation_id=06689368-db4a-70dd-8000-c37727d378ad&EIO=4&transport=websocket" [accepted]
app_store_service     | DEBUG:    > HTTP/1.1 101 Switching Protocols
app_store_service     | DEBUG:    > Upgrade: websocket
app_store_service     | DEBUG:    > Connection: Upgrade
app_store_service     | DEBUG:    > Sec-WebSocket-Accept: QhVhFHD/iSPmU+0qOzIVQgy0HRg=
app_store_service     | DEBUG:    > Sec-WebSocket-Extensions: permessage-deflate
app_store_service     | DEBUG:    > date: Mon, 22 Jul 2024 09:41:41 GMT
app_store_service     | DEBUG:    > server: uvicorn
app_store_service     | INFO:     connection open
app_store_service     | DEBUG:    = connection is OPEN
app_store_service     | DEBUG:    > TEXT '0{"sid":"POZUXfoHMZRUnheFAAAS","upgrades":[],"p...0,"pingInterval":25000}' [86 bytes]
app_store_service     | DEBUG:    < TEXT '40/app/socket.io/,' [21 bytes]
app_store_service     | DEBUG:    > TEXT '44/app/socket.io/,"Unable to connect"' [40 bytes]
app_store_service     | DEBUG:    < CLOSE 1005 (no status received [internal]) [0 bytes]
app_store_service     | DEBUG:    = connection is CLOSING
app_store_service     | DEBUG:    > CLOSE 1005 (no status received [internal]) [0 bytes]
app_store_service     | DEBUG:    x half-closing TCP connection
app_store_service     | DEBUG:    = connection is CLOSED
nginx-1                  | 172.18.0.1 - - [22/Jul/2024:09:41:41 +0000] "GET /app/socket.io/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI0MjA5MzQwLCJpYXQiOjE3MjE2MTczNDAsImp0aSI6ImRlMmQ1ZmUyZTk5ZTQ0ZWY5YTdlMTc2NjhmM2UyNTZmIiwidXNlcl9pZCI6IjA2NjdmZDI4LWZkYWMtNzI1Yi04MDAwLWNjM2U5YWE2OGEzNSJ9.zAENI3Xl0k5efwE83u__svuXDUZajv00462XerXCo2c&organisation_id=06689368-db4a-70dd-8000-c37727d378ad&EIO=4&transport=websocket HTTP/1.1" 101 128 "-" "-" "-"
app_store_service     | INFO:     connection closed

I can see the request is reaching the application but the connection gets closed before any application logic can be executed.

But if I connect directly to the application port and not through Nginx, it works without any issues.


Solution

  • The Socket.IO URL is not given in the connection URL. I'm not sure how this works with Postman, but from JavaScript you would do something like this:

    const socket = io("http://localhost:8089", {
      path: "/app/socket.io"
    });
    

    The path that you pass in the connection URL is interpreted as a namespace. Looking at your logs it seems the Socket.IO server is receiving the connection request, but it is trying to connect you to a namespace called /app/socket.io.