Django|Nginx|Uwsgi|Redis|Websocket Configuration, Use and Deployment

Keywords: Redis Django Nginx socket

Introduction

Because the project needs to include chat function, we hope to use websocket to communicate. However, dwebsocket module can not work properly under Nginx+Uwsgi deployment although it runs normally on runserver. In fact, due to the workflow of the wsgi protocol, long connections such as WebSockets are almost impossible to maintain. So we need to find a new way to realize it. Following is a description of the development environment and production environment configuration for using Redis as a django cache and websocket.

Note: Because I use root account, sudo is not required. If there is insufficient permission in the installation, Please add sudo yourself.

Write this type of blog for the first time. If there are any mistakes, please correct them.

Where to Start

The original configuration is basically in accordance with Huang Chao's blog The content of this paragraph is basically consistent with this article, intercepting the content of installing virtual environment. If you have already configured Uwsgi and Nginx, just need support for websocket, you can skip this section.

mysql installation

apt install mariadb-server mariadb-client libmariadbd-dev libmysqlclient-dev
service mysql restart
service mysql status
mysql_secure_installation
Enter current password for root:[Just enter]
Set root password?[Y/n] Y

#enter your password
Remove anonymous users?[Y/n] Y
Disallow root login remotely?[Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now?[Y/n] Y


#Thanks for using MariaDB!
service mysql restart

Install Nginx

If you run server locally, you do not need to install Nginx.

apt install nginx

Install php

apt install php php-fpm

virtual environment

apt install python3-dev
apt install python3-venv
python3 -m venv /path/to/.venv

Enter the virtual environment:

source /path/to/.venv/bin/activate

Withdrawal from the virtual environment:

deactivate

Note: Please pay attention to replacing / path/to with your own path.

Redis Installation and Configuration

Original link

Redis installation

apt-get install redis-server

After installation, use under linux:

service redis-server start

Open redis-server.

This command can be used to detect whether redis-server is working properly:

redis-cli ping
PONG

Receive PONG instructions for normal operation.

Configure redis to use unix domain sockets

This small segment setting allows redis to use unix domain sockets instead of TCP/IP. You can choose to ignore this paragraph. This results in slightly different settings in the settings.py file of django.

vim /etc/redis/redis.conf
# Accept connections on the specified port, default is 6379.
# If port 0 is specified Redis will not listen on a TCP socket.
# port 6379

# If you want you can bind a single interface, if the bind option is not
# specified all the interfaces will listen for incoming connections.
#
# bind 127.0.0.1

# Specify the path for the unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
unixsocket /path/to/redis.sock
unixsocketperm 777

In fact, the above will be:

port 6379
bind 127.0.0.1

Comment it out and add:

unixsocket /path/to/redis.sock
unixsocketperm 777

Cancel the comment.
After changing conf, use:

service redis-server restart

Restart the service.

Configure Redis as the back end of django session storage and caching

In the settings.py file, set:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "unix:///path/to/redis.sock?db=0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

Guarantee the following in MIDDLEWARE_CLASSES:

MIDDLEWARE_CLASSES = [
    'django.middleware.cache.UpdateCacheMiddleware',    # This must be first on the list
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...,
    'django.middleware.cache.FetchFromCacheMiddleware', # This must be last
    ]

So far, cache can be used in code. The simplest example:

from django.core.cache import cache
example_key = "IAmKey"
example_value = cache.get(example_key)
cache.set(example_key, {"value1", 1})
cache.set("anotherKey", "hello", timeout=None)

redis is a key-value storage mechanism. The cache.get(key) function returns None when the value corresponding to the key does not exist. The cache.set(key, value) function can store data, and the optional parameter timeout determines the expiration time in seconds. Timout = 0 means immediate failure and timeout=None means never failure.

All data stored in redis can be deleted most directly:

redis-cli flushall

In addition, redis can be set to store django session data:

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

Note: In the development environment, if the default configuration is not changed when configuring redis, then in settings.py, CACHES sets "LOCATION"="redis://127.0.0.1:6379/0", where the last 0 represents the database id and can be changed.

django-websocket-redis

First link Official documents In which github Find a routine.
django-websocket-redis needs to configure Redis, use Redis as a message queue, and maintain connections.

install

Install using pip:

pip install django-websocket-redis

To configure

The configuration of the development environment (runserver) is different from that of the production environment (Nginx+Uwsgi). Just take this as an example:

development environment

In the development environment, redis uses the default configuration, i.e. 127.0.0.1:6379. The redis-related settings configuration is shown in the previous note.

In settings.py

INSTALLED_APPS = [
    ...
    'ws4redis',
    ...
]
TEMPLATES = [
    {
        ...,
        'OPTIONS': {
            'context_processors': [
                ...,
                'django.template.context_processors.static',
                'django.contrib.auth.context_processors.auth',
                'ws4redis.context_processors.default',
            ],
            ...,
        },
    },
]
WSGI_APPLICATION = 'ws4redis.django_runserver.application'
WS4REDIS_CONNECTION = {
    'db': 7
}
WS4REDIS_EXPIRE = 3600
WEBSOCKET_URL = '/ws/'
WS4REDIS_PREFIX = 'ws'

production environment

In production environments, redis uses unix domain socket configuration, as described earlier

In settings.py

INSTALLED_APPS = [
    ...
    'ws4redis',
    ...
]
TEMPLATES = [
    {
        ...,
        'OPTIONS': {
            'context_processors': [
                ...,
                'django.template.context_processors.static',
                'django.contrib.auth.context_processors.auth',
                'ws4redis.context_processors.default',
            ],
            ...,
        },
    },
]
WSGI_APPLICATION = 'appname.wsgi.application'
WS4REDIS_CONNECTION = {
    'unix_socket_path': '/path/to/redis.sock',
    'db': 5
}
WS4REDIS_EXPIRE = 3600
WEBSOCKET_URL = '/ws/'
WS4REDIS_PREFIX = 'ws'

Here the appname is changed to the name of the project.

* Note: There are many options in WS4REDIS_CONNECTION, just write different from redis default configuration. Note that the'db'item defaults to 0, indicating the selected database, not the same as the one selected in CACHES.

WS4REDIS_EXPIRE represents the message's expiration time in seconds.

Use

Official documents about the use of written more comprehensive, here only in conjunction with my project to explain some of the functions.

Part django

In my project, I only used to send messages to designated users. ws4redis can also set up broadcasting or send messages to groups.

from ws4redis.publisher import RedisPublisher
from ws4redis.redis_store import RedisMessage

redis_publisher = RedisPublisher(facility='foobar', users=['username1', 'username2'])
message = RedisMessage('Hello World')
# and somewhere else
redis_publisher.publish_message(message)

Here username 1, username 2 is actually:

from django.contrib.auth.models import User

In User.username, RedisPublisher sends messages to websocket connections logged in with these usernames.

Part js

The js section can be written in the same way as WebSocket:

var websocket = new WebSocket("ws://yourhost/ws/foobar?subscribe-user")
websocket.on.onmessage = function (data) {
    ......
}

Replace yourhost with the target address. / ws / is the WEBSOCKET_URL previously specified in settings.py, and foobar?subscribe-user is a parameter that determines which messages this connection accepts. This setting corresponds to the previous django code for receiving messages to that user. Other settings can be found in official documents.

Note: In my project, ws4redis is only used to send messages from server to client, and the messages from client to server are sent by POST request.
In fact, I haven't found a way to handle messages sent from client to server. These messages are received and stored by redis, waiting for django to get them. But there's no (more likely I didn't find) way to alert django when the message arrives at redis, instead django gets the message itself when it needs it.
The & echo parameter was mentioned in the official document, but I didn't find its effect.

deploy

The project deployment with django-websocket-redis configuration is different from the simple Django project. The project is deployed according to the production environment mentioned above. Installation of Nginx et al. has been mentioned before.

Note: This deployment method is deployed in Ubuntu 16.04. Again, I used root account. If there is insufficient authority, Please add sudo.

Because there are many paths involved, in order to look more intuitive, the project is assumed to be named example.

Uwsgi

First, make sure that openssl is installed:

apt-get install openssl

Install Uwsgi:

pip install uwsgi

If you haven't installed django in your virtual environment, you can:

pip install Django
pip install django

However, django seems to have recently updated version 2.0, which may cause your project to fail to work properly, so you can use:

pip install Django==1.11.7

Explicit version. requirements.txt for my project will be given at the end of this article.

Next, you need to set up uwsgi configuration for django and websocket respectively:

django

Create py files

vim /path/to/example/example/wsgi_django.py
import os
os.environ.update(DJANGO_SETTINGS_MODULE='example.settings')
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

Create ini file

vim /path/to/uwsgi_django.ini
[uwsgi]
socket = /path/to/django.sock
chdir = /path/to/example
wsgi-file = example/wsgi_django.py
touch-reload = /path/to/reload_django
home = /path/to/.venv/
processes = 5
chmod-socket = 777
buffer-size = 32768
master = true
daemonize = /path/to/django.log
vacuum = true 

websocket

Create py files

vim /path/to/example/example/wsgi_websocket.py
import os
import gevent.socket
import redis.connection
redis.connection.socket = gevent.socket
os.environ.update(DJANGO_SETTINGS_MODULE='example.settings')
from ws4redis.uwsgi_runserver import uWSGIWebsocketServer
application = uWSGIWebsocketServer()

Create ini file

vim /path/to/uwsgi_websocket.ini
[uwsgi]
http-socket = /path/to/websocket.sock
chdir = /path/to/example
wsgi-file = example/wsgi_websocket.py
touch-reload = /path/to/reload_websocket
home = /path/to/.venv/
processes = 2
gevent = 1000
buffer-size = 32768
chmod-socket = 777
http-websockets = true
master = true
daemonize = /path/to/websocket.log
vacuum = true

Here the touch-reload file needs to be created manually, and other files will be created automatically at run time.

Run separately:

uwsgi --ini /path/to/uwsgi_django.ini
uwsgi --ini /path/to/uwsgi_websocket.ini

When there is a file change, execute:

touch /path/to/reload_django
touch /path/to/reload_websocket

uwsgi can be restarted separately.

Nginx

Change user:

vim /etc/nginx/nginx.conf

Change the first line of www-data to your user name (mine is root).

Fill in the configuration:

sudo vim /etc/nginx/conf.d/somename.conf
server {
 listen 80;
 server_name ***.***.***.***; #Exchange it to your own IP
 charset utf-8;

client_max_body_size 75M;


location /static {
 alias /path/to/example/static;
 }


location / {
 uwsgi_pass unix:///path/to/django.sock;
 include /etc/nginx/uwsgi_params;
 }
location /ws/ {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://unix:/path/to/websocket.sock;

 }
}

Restart Nginx:

service nginx restart

So far, the deployment has been completed.

Summary of Common Commands

After the project changes, use:

touch /path/to/reload_django
touch /path/to/reload_websocket

Restart uwsgi.

By:

redis-cli command

Operate redis, such as:

redis-cli flushall

You can empty the redis database.

If necessary, adopt:

service nginx restart

Restart the nginx server.

Pass:

service redis-server restart

Restart redis server.

Pass:

ps aux|grep uwsgi
ps aux|grep redis-server
ps aux|grep nginx

You can view the service process separately and pass:

Kill-9 process number

Or:

killall -9 uwsgi
killall -9 redis-server
killall -9 nginx

Forced interruption.

After deployment according to this method, / ws / requests, common requests and related error information are recorded in / path/to/django.log and / path/to/websocket.log respectively, which can be viewed.

Common mistakes

Updates on Web Pages

After configuring redis, it may occur that there is no update information after a page is refreshed. Looking at the log, it is found that no corresponding http request has been received. This (possibly) is due to the caching mechanism of redis. By adding in settings.py:

CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
CACHE_MIDDLEWARE_SECONDS = 0

To solve this problem. The first means that only anonymous users are cached, and the second means that the cache invalidation time is set to immediately invalidate. I found that adding the first item didn't solve my problem, so I added the second one. Maybe there's a more elegant way to solve it, but I didn't find it.

Repeated display of chat messages

After configuring ws4redis in my project, when a chats with b, a goes offline, refreshes the page and logs in again, the last message from B to a will be received again. The check found that it was websocket that received the message again.

The solution is to change WS4REDIS_EXPIRE = 0 in settings.py to invalidate the message in redis immediately after it is received by the front end.

About safari

ws4redis actually works well in safari. But when using runserver, it may appear at the back end:

websocket.receive: WebSocketError Unexpected EOF while decoding header

At the front end:

WebSocket connection to 'ws://127.0.0.1:8000/ws/foobar?subscribe-user' failed: Invalid HTTP version string: HTTP/1.0

This issue was raised as an issue in github, but it has not changed. The result of the discussion is the problem of django runserver. This problem disappeared after deployment to the server.

About 502

If 502 Bad Gateway occurs when a websocket connection is established, it is likely that the Nginx configuration is incorrectly set. Check that the conf file of Nginx is correct in forwarding the request to Uwsgi.

About 500

If a websocket connection is established:

WebSocket connection to 'ws://***' failed: Error during WebSocket handshake: Unexpected response code: 500

See in the log:

you need to build uWSGI with SSL support to use the websocket handshake api function !!!

Explain that your uwsgi does not support ssl.

This is a very troublesome problem because the server was already installed with openssl when this error occurred. The simple solution is to install Uwsgi outside the virtual environment after installing openssl and execute:

killall -9 uwsgi
uwsgi --ini /path/to/uwsgi_django.ini
uwsgi --ini /path/to/uwsgi_websocket.ini

In general, this solves the problem, because the Uwsgi installed in the virtual environment may not be able to locate your openssl.

If you still can't solve this problem, A Problem on Stack Overflow More possible solutions are given.

Attachment: requirements.txt

Following is the requirements.txt of my project so far, which is not guaranteed to be all useful, but if you find any packages missing somewhere in the process of configuring according to this article, maybe you can find them here:

amqp==2.2.2
billiard==3.5.0.3
celery==4.1.0
coverage==4.4.2
Django==1.11.7
django-redis==4.8.0
django-redis-cache==1.7.1
django-redis-sessions==0.6.1
django-websocket-redis==0.5.1
flake8==3.5.0
gevent==1.2.2
greenlet==0.4.12
kombu==4.1.0
mccabe==0.6.1
mysqlclient==1.3.12
pep8-naming==0.4.1
pycodestyle==2.3.1
pycrypto==2.6.1
pyflakes==1.6.0
python-redis-lock==3.2.0
pytz==2017.3
redis==2.10.6
redis-cache==0.1.3
redis-structures==0.1.6
six==1.11.0
vine==1.1.4
vital-tools==0.1.8
wsaccel==0.6.2

Reference summary

1.Using Redis as Django's session store and cache backend

2.Websockets for Django applications using Redis as message queue

3.How to Set Up My First Server?

4.Error during WebSocket handshake: Unexpected response code: 500

Posted by TheBentinel.com on Tue, 01 Jan 2019 09:51:08 -0800