Django+uWSGI+nginx:code 400, message Bad request syntax...

Django+uWSGI+nginx:code 400, message Bad request syntax...
This is the programmer's leisure time series!

前段时间,公司更换了服务器,于是迁移服务的重任就落到了我身上。迁移服务可谓是 Bug 重重,中间遇到的坑,手指+脚趾都已经数不过来了😂 一路披荆斩棘,解决了很多 Bug,所有服务均已迁移完毕。在这么多 Bug 中,唯有一个 Bug 令我始终不能忘怀,为啥呢?大部分 Bug 平均都以一两小时/1个的速度给解决了,这个 Bug 用了我两天时间,到最后都没能彻底解决,而是在我一杯 Coffee 下肚后,就平白无故消失了😶

今天,又有一个项目需要部署,于是我采用常规套路(Django+uWSGI+nginx)去服务器上部署。就是这么巧合,我又遇见了这个 Bug:

(ENV) app@ubuntu:~/snack/survey$ python manage.py runserver 0.0.0.0:6100
Performing system checks...

System check identified no issues (0 silenced).
January 29, 2018 - 12:44:07
Django version 1.9.6, using settings 'survey.settings.development'
Starting development server at http://0.0.0.0:6100/
Quit the server with CONTROL-C.
[29/Jan/2018 12:44:09] code 400, message Bad request syntax ('\x00&\x03\x00\x0c\x00QUERY_STRING\x00\x00\x0e\x00REQUEST_METHOD\x03\x00GET\x0c\x00CONTENT_TYPE\x00\x00\x0e\x00CONTENT_LENGTH\x00\x00\x0b\x00REQUEST_URI\x01\x00/\t\x00PATH_INFO\x01\x00/\r\x00DOCUMENT_ROOT\x15\x00/usr/share/nginx/html\x0f\x00SERVER_PROTOCOL\x08\x00HTTP/1.1\x0e\x00REQUEST_SCHEME\x04\x00http\x0b\x00REMOTE_ADDR\x0f\x00192.168.0.103\x0b\x00REMOTE_PORT\x05\x0053294\x0b\x00SERVER_PORT\x02\x0061\x0b\x00SERVER_NAME\x00\x00\t\x00HTTP_HOST\x12\x00192.168.0.103:61\x0f\x00HTTP_CONNECTION')
[29/Jan/2018 12:44:09] "&
                          QUERY_STRINGREQUEST_METHODGET
                                                        CONTENT_TYPECONTENT_LENGTH
DOCUMENT_ROOT/usr/share/nginx/htmlSERVER_PROTOCOHTTP/1.1REQUEST_SCHEMEhttp      REQUEST_URI/	PATH_INFO/
                                                                            REMOTE_ADDR192.168.0.103
                                                                                                      REMOTE_PORT53294
                                                                                                                      SERVER_PORT61
                                                                                                                                    SERVER_NAME	HTTP_HOST192.168.0.103:61HTTP_CONNECTION" 400 -

这个 Bug 可谓是心中的痛,上次折腾了很久,正当我想要放弃的时候,它却莫名其妙地消失了。这次我决定彻底解决这个问题,因为它一直不消失~ 😂

通过搜索,找到了这些回答:

1.1-10

可以参考的不多,上次都已经把这些尝试了几篇😂 这次我再继续把这些弄一遍,不仅浪费时间,而且也没太大意义。我决定先认真思考问题所在,再去找解决办法,于是我开始仔细查看可能出现问题的地方:

uWSGI 配置:

(ENV) app@ubuntu:~/snack/survey$ cat /home/app/snack/survey/uwsgi_sanck_survey.ini 
[uwsgi]

chdir = /home/app/snack/survey

module = survey.wsgi

home = /home/app/snack/ENV 

master = true

processes = 2

pidfile = /tmp/snack-survey.pid

daemonize = /var/log/uwsgi/snack-survey.log

socket = 127.0.0.1:6100

nginx 配置:

(ENV) app@ubuntu:~/snack/survey$ cat /etc/nginx/sites-enabled/snack-survey.conf
server {
    #the port your site will be served on
    listen 61;
    #server_name *.*.*.*;
    charset utf-8;

    client_max_body_size 75M;

    location /static/ {
        alias /var/www/snack.survey/static/;
    }

    location /media/ {
        alias /var/www/snack.survey/media/;
    }
   
    location /favicon.ico {
	alias /var/www/snack.survey/favicon.ico;
    }

    location / {
        uwsgi_pass 127.0.0.1:6100;
        include /etc/nginx/uwsgi_params;
    }

}

Django 配置:

(ENV) app@ubuntu:~/snack/survey$ cat /home/app/snack/survey/survey/settings/development.py
"""
Django settings for admin project.

Generated by 'django-admin startproject' using Django 1.9.6.

For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""

import os
import datetime

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 't%t7p_29h2jwym1*bgco(jr+c8ybof+m*lr2s0%ecp18*@-f61'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ["*"]


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',
    'django_extensions',
    'import_export',

    'survey.apps.base',
]

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'survey.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'survey.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases

DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': 'your_host',
            'NAME': 'your_db_name',
            'USER': 'your_user',
            'PASSWORD': 'your_password',
            'PORT': your_db_port
            }
        }


PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.BCryptPasswordHasher',
    'django.contrib.auth.hashers.MD5PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.SHA1PasswordHasher',
    'django.contrib.auth.hashers.CryptPasswordHasher',
)


# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
     {
         'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
     },
     {
         'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
     },
     {
         'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
     },
     {
         'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
     },
]


REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
    'TEST_REQUEST_DEFAULT_FORMAT': 'json',
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer'
    ),
}

JWT_AUTH = {
    'JWT_ENCODE_HANDLER':
    'rest_framework_jwt.utils.jwt_encode_handler',

    'JWT_DECODE_HANDLER':
    'rest_framework_jwt.utils.jwt_decode_handler',

    'JWT_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_payload_handler',

    'JWT_PAYLOAD_GET_USER_ID_HANDLER':
    'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler',

    'JWT_RESPONSE_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_response_payload_handler',

    'JWT_SECRET_KEY': 'your_password',
    'JWT_ALGORITHM': 'HS256',
    'JWT_VERIFY': True,
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LEEWAY': 0,
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_AUDIENCE': None,
    'JWT_ISSUER': None,

    'JWT_ALLOW_REFRESH': True,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),

    'JWT_AUTH_HEADER_PREFIX': 'Bearer',
}


# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/

LANGUAGE_CODE = 'zh-Hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

STATIC_ROOT = '/var/www/survey/static/'

STATIC_URL = '/static/'

MEDIA_URL = '/media/'

if DEBUG:
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
else:
    MEDIA_ROOT = '/var/www/survey/media/'

项目依赖 :

(ENV) app@VM-234-147-ubuntu:~/snack/survey$ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
bcrypt (3.1.4)
beautifulsoup4 (4.6.0)
certifi (2018.1.18)
cffi (1.11.4)
chardet (3.0.4)
diff-match-patch (20121119)
Django (1.9.6)
django-extensions (1.9.9)
django-import-export (0.7.0)
django-tinymce (2.7.0)
djangorestframework (3.6.3)
djangorestframework-jwt (1.11.0)
et-xmlfile (1.0.1)
idna (2.6)
jdcal (1.3)
lxml (4.1.1)
MySQL-python (1.2.5)
odfpy (1.3.6)
openpyxl (2.5.0)
pip (9.0.1)
pycparser (2.18)
PyJWT (1.5.3)
pytz (2017.3)
PyYAML (3.12)
requests (2.18.4)
setuptools (38.4.0)
six (1.11.0)
tablib (0.12.1)
typing (3.6.4)
unicodecsv (0.14.1)
urllib3 (1.22)
wheel (0.30.0)
xlrd (1.1.0)
xlwt (1.3.0)

在与正常项目仔细对比后,我发现这些确实没有问题。然后我开始一点一点排查问题所在,首先本地测试了 Django 项目,完美访问。然后开始测试 uWSGi,我按照官方文档的步骤,最终确认没有问题。最后我开始测试 nginx,我按照官方文档的步骤,最终确认也没有问题😮😲😨 这太奇怪了,这些都没有问题,难道是我有问题吗😂

不管了,把服务再运行一次看看。很遗憾,还是最初的错误(如果骄傲没被现实大海冷冷拍下,又怎会懂得要多努力才走得到远方,如果梦想不曾坠落悬崖千钧一发.....)╮(╯▽╰)╭

一首《最初的梦想》过后,我有了一个灵感:在配置文件里不是有 log 吗,log 里面的错误信息是不是会多一些呢!

于是我打开 log 文件,并监听其内容变化,然后在页面访问服务地址。

(ENV) app@ubuntu:~/snack/survey$ tail -F /var/log/uwsgi/snack-survey.log 
no request plugin is loaded, you will not be able to manage requests.
you may need to install the package for your language of choice, or simply load it with --plugin.
!!!!!!!!!!! END OF WARNING !!!!!!!!!!
spawned uWSGI master process (pid: 15485)
spawned uWSGI worker 1 (pid: 15486, cores: 1)
spawned uWSGI worker 2 (pid: 15487, cores: 1)
-- unavailable modifier requested: 0 --
-- unavailable modifier requested: 0 --
-- unavailable modifier requested: 0 --
-- unavailable modifier requested: 0 --
-- unavailable modifier requested: 0 --

无论我怎么访问,页面始终 502 Bad Gateway ,log 也里面没太多信息,只有这句话 -- unavailable modifier requested: 0 --不时地浮现。起初我觉得这句话没有啥用,不过我还是抱着看看一看的心态,去 Google 上搜了一下:

1.2-6

咦~,还是有很多信息嘛! 我开始按照这些搜索结果来尝试解决这个问题.

Bingo! Finally i found the answer! 😁😃😄

Installing the python plugin for uwsgi with apt-get install uwsgi-plugin-python for python 2 or apt-get install uwsgi-plugin-python3 for python 3 and adding --plugins = python to the individual uwsgi app config solves this problem.

我按照这个说明,进行了如下的操作:

(ENV) app@ubuntu:~/snack/survey$ sudo apt-get install uwsgi-plugin-python 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Suggested packages:
  python-uwsgidecorators
The following NEW packages will be installed:
  uwsgi-plugin-python
0 upgraded, 1 newly installed, 0 to remove and 3 not upgraded.
Need to get 72.8 kB of archives.
After this operation, 260 kB of additional disk space will be used.
Get:1 http://mirrors.tencentyun.com/ubuntu xenial-updates/universe amd64 uwsgi-plugin-python amd64 2.0.12-5ubuntu3.1 [72.8 kB]
Fetched 72.8 kB in 0s (1,091 kB/s)       
Selecting previously unselected package uwsgi-plugin-python.
(Reading database ... 147230 files and directories currently installed.)
Preparing to unpack .../uwsgi-plugin-python_2.0.12-5ubuntu3.1_amd64.deb ...
Unpacking uwsgi-plugin-python (2.0.12-5ubuntu3.1) ...
Processing triggers for man-db (2.7.5-1) ...
Setting up uwsgi-plugin-python (2.0.12-5ubuntu3.1) ...
/var/lib/dpkg/info/uwsgi-plugin-python.postinst: 61: [: Illegal number: 
(ENV) app@ubuntu:~/snack/survey$ uwsgi --plugins=python --ini uwsgi_sanck_survey.ini 
[uWSGI] getting INI configuration from uwsgi_sanck_survey.ini

果然,页面不再是 502 Bad Gateway 了,取而代之的是正常的系统界面!

终于把这个 Bug 解决了,程序员最开心的事情莫过于此*^____^*

最后,我想再说一句:遇到解决不了的 Bug,并不是你不够努力,而是你努力错了方向!!!


参考文献:

END!😜

Show Comments