Основи Python и Django. / Бібліотека socketio. / Використовуємо веб-сокети з бібліотекою socketio.

Використовуємо веб-сокети з бібліотекою socketio.

Промислові інструменти для вебсокетів http://www.emqx.io/ centrifugo.

Створимо образ для веб-сервера Dockerfile-nginx.

FROM nginx:latest
RUN mkdir /app
COPY ./default.conf /etc/nginx/conf.d/default.conf

Конфігурація віртуального хоста default.conf.

server { 
 listen 80;
 server_name localhost;

  location / {
    root /app;
    try_files $uri /index.html;
  }

}

Створимо образ для сокет-серверу Dockerfile-socketio.

FROM python:3.6
ENV PYTHONUNBUFFERED 1
RUN mkdir /app
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
RUN apt update
RUN pip install -r requirements.txt
CMD python server.py

Створимо збирач контейнерів docker-compose.yaml.

version: '3.5'
services: 
    socketio:
        build: 
            context: .
            dockerfile: Dockerfile-socketio
        volumes:
            - .:/app
        container_name: socket-io-server
        ports:
            - 8050:5000

    socketio-nginx:
        build: 
            context: .
            dockerfile: Dockerfile-nginx
        volumes:
            - .:/app
        ports:
            - 8088:80
        container_name: socket-io-nginx

Встановимо клієнтські залежності.

npm init
npm install jquery socket.io @types/socket.io --save

Створимо index.html

<!DOCTYPE html>
<html>
    <head><title>Socket io</title>
        <script src="/node_modules/socket.io/client-dist/socket.io.min.js"></script>
        <script src="/node_modules/jquery/dist/jquery.min.js"></script>
    </head>
    <body>
        <h1>Test socketio</h1>

        <button id="button">Test</button>

        <script>
           const socket = io('ws://localhost:8050', {transports:['websocket']});  
           socket.on('connect', () => {
                socket.on('message', msg => {
                    console.log(msg);
                })
           });

           $('#button').on('click',() => {
               socket.emit('test_message',{data: 'ping'});
           });

        </script>
        </body>
</html>

Створимо простий сокет-сервер server.py.

import eventlet
import socketio
sio = socketio.Server(cors_allowed_origins='*',async_mode='eventlet')
app = socketio.WSGIApp(sio)

@sio.event
def connect(sid, environ):
    print('connect ', sid)

@sio.event
def test_message(sid, data):
    print('message ', data)
    sio.emit('message', {'data': 'pong'}, broadcast=True, include_self=True)
    # sio.emit('my event', {'data': 'foobar'}, room=user_sid)

@sio.event
def disconnect(sid):
    print('disconnect ', sid)

if __name__ == '__main__':
    eventlet.wsgi.server(eventlet.listen(('', 5000)), app)

Можливості бібліотеки

Простір імен.

При коннект можна вказувати неймспейс.

ws://example.com:8000/chat

І потім із ним працювати.

@sio.event(namespace='/chat')
def my_custom_event(sid, data):
    pass

Надсилання повідомлення певному клієнту

 sio.emit('my event', {'data': 'foobar'}, room=user_sid)

Додавання з’єднань до кімнати

@sio.event
def begin_chat(sid):
   sio.enter_room(sid, 'chat_users')

 @sio.event
 def exit_chat(sid):
     sio.leave_room(sid, 'chat_users')

Після чого стає можливим відправлення повідомлення до кімнати.

sio.emit('my reply', data, room='chat_users', skip_sid=sid)

Одна сполука може бути додана до багатьох кімнат.

Сесія користувача.

Можна зберігати специфічні дані користувача для кожного з’єднання.

@sio.event
def connect(sid, environ):
    sio.save_session(sid, {'username': 'Dima'})

@sio.event
def message(sid, data):
    session = sio.get_session(sid)

Брати дані сесії можна і так:

    with sio.session(sid) as session:
        session['username'] = username

Взаємодія із сторонніми процесами.

Часто виникає необхідність передати дані сокет-серверу з інших програм або процесів.

І тому можна використовувати посередника як БД Redis.

 pip install redis

Створимо новий контейнер.

version: '3.5'
services: 
    socketio-server:
        ...
        depends_on:
            - "socketio-redis"

    ...


    socketio-redis:
        image: "redis:alpine"
        ports:
            - "6999:6379"

Задамо менеджер повідомлень під час створення об’єкта програми сервера.

import eventlet
eventlet.monkey_patch()
mgr = socketio.RedisManager('redis://socketio-redis:6379/0')
sio = socketio.Server(cors_allowed_origins='*',async_mode='eventlet',client_manager=mgr)

socketio-redis - у докері можна користуватися іменами контейнерів для позначень їх хостів

Створимо переодичну розсилку серверного часу окремому файлі timer.py.

import socketio
import time
from datetime import datetime
mgr = socketio.RedisManager('redis://localhost:6999/0', write_only=True)

while True:
    time.sleep(2)
    now = datetime.now()
    current_time = now.strftime("%H:%M:%S")
    mgr.emit('message', data={'time': current_time})
    print(current_time)

Зробимо оновлення на клієнта.

    <div id="time"></div>

    <script>
       const socket = io('ws://localhost:8050', {transports:['websocket']});

       socket.on('connect', () => {

            socket.on('message', msg => {

                $('#time').html(msg.time);
            })
       });
     ...

Додамо новий контейнер.

....

socketio-server:
    ...
    command: ["python", "server.py"]

socketio-pinger:
    build: 
        context: .
        dockerfile: Dockerfile-socketio
    volumes:
        - .:/app
    container_name: socket-io-pinger
    depends_on:
        - "socketio-redis"
    command: ["python", "pinger.py"]

При цьому вилучивши команду запуску програми з Dockerfile-socketio і перенесемо до docker-compose.