WebSocket with Python + uWSGI

Realize WebSocket with Python3.5 + uWSGI

Since uWSGI 1.9, WebSocket is supported as described in here. It seemed easy to do, so when I actually tried to run the sample, it failed many times, and after investigating and noticing it, I spent more than a whole day. Originally, I tried to post the contents together with "Non-blocking with Python + uWSGI", but it could not be realized by the same method, so it also serves as a memorandum. I decided to post.

Environment

Below, execute in the environment where Python3.5 and Docker are installed. It was built in a Mac (macOS Sierra) environment.

Directory structure

The directory structure is as follows.

スクリーンショット 2017-07-04 0.54.59.png

nginx.conf and nginx.repo under nginx-python / conf are "Create an environment of Python + uWSGI + Nginx with Docker" Is the same, so it is omitted. The basic usage of the docker-compose and docker commands is also described there.

Create CRT and KEY for SSL certificate

install openssl

Created in Mac environment. First, install ʻopenssl with the brew` command.

$ brew install openssl

If you want to use the latest ʻopenssl command installed with brew, add the following to the last line of .bashrc`.

~/.bashrc


export PATH=$(brew --prefix openssl)/bin:$PATH

After adding, execute the following.

$ source ~/.bashrc

Creating a certificate

I need to answer some questions when creating csr, but all defaulted (only enter key pressed).

$ openssl genrsa -out server.key 2048
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

Place the created CRT and KEY files under the ʻapp` directory (in this example, there is CSR, but it is not necessary).

Sample code

Server side

After the handshake with the client is completed, it waits for the data to be received and sends the received data to the client as it is.

app/websocket.py


import uwsgi


def application(env, start_response):
    uwsgi.websocket_handshake(env['HTTP_SEC_WEBSOCKET_KEY'], env.get('HTTP_ORIGIN', ''))
    msg = uwsgi.websocket_recv()
    print("receive: %s" % msg)
    uwsgi.websocket_send(msg)
    print("end")

Client side

A sample that connects to the server, sends data, and waits for a response when the button is pressed.

app/client/index.htm


<!DOCTYPE HTML>
<html>
  <head>
    <script type="text/javascript">
      function WebSocketTest()
      {
        if ("WebSocket" in window) {
          alert("WebSocket is supported by your Browser!");
          var ws = new WebSocket("wss://127.0.0.1:8443/ws/");
          ws.onopen = function() {
            ws.send("Hello from client");
            alert("Message is sent...");
          };
          ws.onmessage = function (evt) {
            var received_msg = evt.data;
            alert("Message is received...");
          };
          ws.onclose = function() {
            alert("Connection is closed...");
          };
        } else {
          alert("WebSocket NOT supported by your Browser!");
        }
      }
    </script>
  </head>
  <body>
    <input type="button" onClick="WebSocketTest();" value="WebSocket Test">
  </body>
</html>

Various configuration files

docker-compose.yml Forward port number 80 to 8180 and 443 to 8443.

docker-compose.yml


version: "2"
services:
  # nginx
  nginx-python:
    build: ./nginx-python
    ports:
      - "8180:80"
      - "8443:443"
    volumes:
      - ./app/:/var/www/html/app/
    environment:
      TZ: "Asia/Tokyo"

Dockerfile For ʻuWSGI, git clone is done and built. Otherwise, SSL connection was not possible. Also, here, it is assumed that ʻasyncio is used and non-blocking mode is also supported, but WebSocket can be used even if non-blocking mode is not supported. In that case, you can safely delete CFLAGS ="-I / usr / include / python3.5m "UWSGI_PROFILE =" asyncio ".

nginx-python/Dockerfile


FROM centos:6.8

ADD ./conf/nginx.repo /etc/yum.repos.d/

# nginx & python
RUN yum localinstall -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
RUN yum install -y https://centos6.iuscommunity.org/ius-release.rpm
RUN yum install -y nginx-1.10.1
RUN yum install -y make gcc
RUN yum install -y libxml2-devel
RUN yum install -y python35u python35u-libs python35u-devel python35u-pip
RUN yum clean all

RUN yum install -y git
RUN yum install -y openssl-devel

RUN ln -s /usr/bin/python3.5 /usr/bin/python3 && \
    unlink /usr/bin/python && \
    ln -s /usr/bin/python3 /usr/bin/python && \
    ln -s /usr/bin/pip3.5 /usr/bin/pip && \
    sed -i -e 's/python/python2.6/' /usr/bin/yum

RUN pip install greenlet && \
    cd /root  && \
    git clone https://github.com/unbit/uwsgi.git && \
    cd uwsgi && \
    CFLAGS="-I/usr/include/python3.5m" UWSGI_PROFILE="asyncio" python uwsgiconfig.py --build

# setting nginx
COPY conf/nginx.conf /etc/nginx/nginx.conf
ADD conf/default.conf /etc/nginx/conf.d/default.conf
RUN usermod -u 1000 nginx

EXPOSE 80
EXPOSE 443

ADD ./conf/start.sh /tmp/start.sh

CMD /bin/sh /tmp/start.sh

I replaced the contents of / usr / bin / yum with the sed command on the way, but when I link to Python 3.5 with the python command, yum cannot be used.

nginx config file

I just couldn't find a way to use it with the ws protocol without a framework. Since it is the wss protocol, the reverse proxy is set to access with https.

nginx-python/conf/default.conf


upstream websocket {
    server localhost:9090;
}

server {
    listen              443;
    server_name         _;
    ssl                 on;
    ssl_certificate     /var/www/html/app/server.crt;
    ssl_certificate_key /var/www/html/app/server.key;

    index index.html index.htm;
    charset utf-8;

    root /var/www/html/app/client;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location /ws/ {
        proxy_pass https://websocket/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location = /favicon.ico {
        empty_gif;
    }
}

Startup batch

If you build ʻuWSGI to not use non-blocking mode, you don't need the --asyncio 10 --greenlet` option (if it is specified, you will get an error). Also, it is assumed that you will access via HTTPS connection.

nginx-python/conf/start.sh


#!/bin/sh

/etc/init.d/nginx start
cd /var/www/html/app
chmod -R 777 .
 /root/uwsgi/uwsgi --asyncio 10 --greenlet --logto uwsgi.log --https :9090,server.crt,server.key --http-websockets --wsgi-file websocket.py

demo

Build and start with docker-compose up --build. After starting normally, access https://127.0.0.1:8443.

スクリーンショット 2017-07-04 1.22.48.png

When you click the button, alerts will be displayed one after another each time you press "OK" as shown below.

スクリーンショット 2017-07-04 1.23.04.png スクリーンショット 2017-07-04 1.23.14.png スクリーンショット 2017-07-04 1.23.27.png スクリーンショット 2017-07-04 1.23.35.png

In addition, the following is output to the log on the server side (here, /var/www/html/app/uwsgi.log).

receive: b'Hello from client'
end

Other

I haven't realized what I originally wanted to do without blocking, such as handling socket reception as a separate task, so I would like to investigate not only ʻasyncio but also geventandgreenlet. .. It hurts that there is not much information about ʻuWSGI's non-blocking mode and WebSocket.

Recommended Posts

WebSocket with Python + uWSGI
Non-blocking with Python + uWSGI
FizzBuzz with Python3
Scraping with Python
Statistics with python
Scraping with Python
Python with Go
Twilio with Python
Play with 2016-Python
Tested with Python
with syntax (Python)
Bingo with python
Excel with Python
Microcomputer with Python
Cast with python
Create Python + uWSGI + Nginx environment with Docker
Serial communication with Python
Zip, unzip with python
Django 1.11 started with Python3.6
Primality test with Python
Python with eclipse + PyDev.
Socket communication with Python
Data analysis with python 2
Scraping with Python (preparation)
Try scraping with Python.
Learning Python with ChemTHEATER 03
Sequential search with Python
"Object-oriented" learning with python
Run Python with VBA
Handling yaml with python
Solve AtCoder 167 with python
Serial communication with python
[Python] Use JSON with Python
Learning Python with ChemTHEATER 05-1
Learn Python with ChemTHEATER
1.1 Getting Started with Python
Binarization with OpenCV / Python
3. 3. AI programming with Python
Kernel Method with Python
Scraping with Python + PhantomJS
Posting tweets with python
Drive WebDriver with python
Use mecab with Python3
Voice analysis with python
Think yaml with python
Operate Kinesis with Python
Getting Started with Python
Use DynamoDB with Python
Zundko getter with python
Hello World with nginx + uwsgi + python on EC2
Handle Excel with python
Ohm's Law with Python
Primality test with python
Run Blender with python
Solve Sudoku with Python
Python starting with Windows 7
Create Nginx + uWSGI + Python (Django) environment with docker
Heatmap with Python + matplotlib
Multi-process asynchronously with python
Python programming with Atom
Use Python 3.8 with Anaconda