Flask Tour: написание простого веб-фреймворка на Python

Python uWSGI

Рекомендуемое чтение

Что такое веб-фреймворк

Рабочий процесс веб-приложения Python

Ниже просмотры из интернета:

WSGI

WSGI

请求-响应

Зачем вам нужно устанавливать спецификацию WSGI

pep-3333Рекомендуется установить простую и унифицированную спецификацию интерфейса в доме веб-серверов и веб-приложений/веб-фреймворков, а именно интерфейс шлюза веб-сервера Python (сокращенно WSGI). Для обеспечения переносимости веб-приложений между различными веб-серверами.

использованная литература

Написать код

Часть кода относится кПростой учебник по веб-фреймворку Python, Я думаю, что эта детская обувь из питона очень хороша.

# wsgi_demo.py

import pprint
# 导入python内置的wsgi server
from wsgiref.simple_server import make_server

def application(environ, start_response):
    """

    :param environ: 包含一些特定的WSGI环境信息的字典, 由WSGI服务器提供
    :param start_response:  生成WSGI响应的回掉函数, 接受两个必要的位置参数和一个可选参数。status,response_headers和 exc_info
    :return: 响应体的的迭代器
    """
    pprint.pprint(environ)
    status = '200 ok'
    response_headers = [('Content-type', 'text/html;charset=utf8')]
    start_response(status, response_headers)
    return ['<h1>Hello, web!</h1>'.encode()]

if __name__ == '__main__':
    httpd = make_server('0.0.0.0', 5000, application)
    httpd.serve_forever()

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

python wsgi_demo.py

Связывание с curl через другую командную строку:

~$ curl http://0.0.0.0:5000/
<h1>Hello, web!</h1>

Общая среда

Словарь среды используется для хранения этих переменных среды CGI. См. это объяснение для понимания CGI/WSGI/uWSGI.Связь между торнадо cgi wsgi uwsgi?

1. REQUEST_METHOD 
HTTP的请求方式,比如 "GET" 或者 "POST"。这个参数永远不可能是空字符串,故必须指定。

2. PATH_INFO 
URL请求中‘路径’(‘path’)的其余部分,指定请求的目标在应用程序内部的虚拟位置。如果请求的目标是应用程序根目录并且末尾没有'/'符号结尾的话,那么PATH_INFO可能为空字符串 。

3. QUERY_STRING 
URL请求中紧跟在“?”后面的那部分,它可以为空或不存在。

4. CONTENT_TYPE 
HTTP请求中Content-Type字段包含的所有内容,它可以为空或不存在。

5. HTTP_ 变量组 
这组变量对应着客户端提供的HTTP请求报头(即那些名字以 “HTTP_” 开头的变量)

...

Мы фактически прошли вышеперечисленное

pprint.pprint(environ)

Вы можете просмотреть информацию, содержащуюся в нем.

{
'Apple_PubSub_Socket_Render':'/private/tmp/com.apple.launchd.9BSE0tnnTO/Render',
 'CLICOLOR': '1',
 'COLORFGBG': '7;0',
 'COLORTERM': 'truecolor',
 'CONTENT_LENGTH': '',
 'CONTENT_TYPE': 'text/plain',
 'FLUTTER_STORAGE_BASE_URL': 'https://storage.flutter-io.cn',
 'GATEWAY_INTERFACE': 'CGI/1.1',
 'GOBIN': '/Users/xx/Documents/goBin',
 'GOPATH': '/Users/xx/Documents/goWorkPlace',
 'GOROOT': '/usr/local/go',
 'HOME': '/Users/xx',
 'HTTP_ACCEPT': '*/*',
 'HTTP_HOST': '0.0.0.0:5000',
 'HTTP_USER_AGENT': 'curl/7.54.0',
 'ITERM_PROFILE': 'Default',
 'ITERM_SESSION_ID': 'w0t0p0:D66287F5-65FC-4141-94AC-06A1B3CBEAE4',
 'LANG': 'zh_CN.UTF-8',
 'LOGNAME': 'xx',
 'LSCOLORS': 'exfxhxhxgxhxhxgxgxbxbx',
 'PATH': '/Users/xx/.local/share/virtualenvs/flask-demo-MqfCTpGB/bin:/Users/xx/Documents/flutter/bin:/Users/xx/.pyenv/plugins/pyenv-virtualenv/shims:/Users/xx/.pyenv/plugins/pyenv-virtualenv/shims:/Users/xx/.pyenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Users/xx/.local/bin:/usr/local/go/bin:/Users/xx/Documents/goBin',
 'PATH_INFO': '/',
 'PIPENV_ACTIVE': '1',
 'PIP_DISABLE_PIP_VERSION_CHECK': '1',
 'PIP_PYTHON_PATH': '/Users/xx/.pyenv/versions/3.6.0/bin/python3.6',
 'PS1': '(flask-demo) \\[\\033[01;33m\\]\\u \\W\\$\\[\\033[00m\\] ',
 'PUB_HOSTED_URL': 'https://pub.flutter-io.cn',
 'PWD': '/Users/xx/Documents/GitHub/flask-demo',
 'PYENV_SHELL': 'bash',
 'PYENV_VIRTUALENV_INIT': '1',
 'PYTHONDONTWRITEBYTECODE': '1',
 'QUERY_STRING': '',
 'REMOTE_ADDR': '127.0.0.1',
 'REMOTE_HOST': '',
 'REQUEST_METHOD': 'GET',
 'SCRIPT_NAME': '',
 'SERVER_NAME': 'XxdeMacBook-Pro.local',
 'SERVER_PORT': '5000',
 'SERVER_PROTOCOL': 'HTTP/1.1',
 'SERVER_SOFTWARE': 'WSGIServer/0.2',
 'SHELL': '/bin/bash',
 'SHLVL': '2',
 'SSH_AUTH_SOCK': '/private/tmp/com.apple.launchd.qMWayGCpdP/Listeners',
 'TERM': 'xterm-256color',
 'TERM_PROGRAM': 'iTerm.app',
 'TERM_PROGRAM_VERSION': '3.2.0',
 'TERM_SESSION_ID': 'w0t0p0:D66287F5-65FC-4141-94AC-06A1B3CBEAE4',
 'TMPDIR': '/var/folders/6g/kjvjmf8j59j2tf2mm360dqjm0000gn/T/',
 'USER': 'xx',
 'VIRTUAL_ENV': '/Users/xx/.local/share/virtualenvs/flask-demo-MqfCTpGB',
 'XPC_FLAGS': '0x0',
 'XPC_SERVICE_NAME': '0',
 '_': '/Users/xx/.local/share/virtualenvs/flask-demo-MqfCTpGB/bin/python',
 '__CF_USER_TEXT_ENCODING': '0x1F5:0x19:0x34',
 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>,
 'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>,
 'wsgi.input': <_io.BufferedReader name=6>,
 'wsgi.multiprocess': False,
 'wsgi.multithread': True,
 'wsgi.run_once': False,
 'wsgi.url_scheme': 'http',
 'wsgi.version': (1, 0)
}

В приложении мы получаем множество необходимых нам параметров через окружение, например, запрошенную строку запроса.

Инкапсулировать объект запроса

# request.py

from six.moves import urllib

class Request(object):
    """接受environ参数, 然后一些子函数供外界使用去获取需要的值"""
    def __init__(self, environ):
        self.environ = environ

    def args(self):
        """ 把查询参数转成字典形式 """
        get_arguments = urllib.parse.parse_qs(
            self.environ['QUERY_STRING']
        )
        return {k: v[0] for k, v in get_arguments.items()}

    def path(self):
        return self.environ['PATH_INFO']

Инкапсулировать объект Response

# response.py

import http.client
from six.moves import urllib
from wsgiref.headers import Headers

class Response(object):
    """返回内容, 状态码, 字符编码, 返回类型等"""

    def __init__(self, response=None, status=200,
                 charset='utf-8', content_type='text/html'):
        self.response = [] if response is None else response
        self.charset = charset
        self.headers = Headers()
        content_type = '{content_type}; charset={charset}'.format(
            content_type=content_type, charset=charset)
        self.headers.add_header('content-type', content_type)
        self._status = status

    @property
    def status(self):
        status_string = http.client.responses.get(self._status, 'UNKNOWN')
        return '{status} {status_string}'.format(
            status=self._status, status_string=status_string)

    def __iter__(self):
        for val in self.response:
            if isinstance(val, bytes):
                yield val
            else:
                yield val.encode(self.charset)

функция декоратора

# transfer.py

from request import Request

def request_response_application(func):
    """把WSGI 函数转换成使用Request/Response 对象"""
    def application(environ, start_response):
        request = Request(environ)
        response = func(request)
        start_response(
            response.status,
            response.headers.items()
        )
        return iter(response)
    return application

Пересмотренная демонстрация

# wsgi_demo.py

import pprint
# 导入python内置的wsgi server
from wsgiref.simple_server import make_server

from transfer import request_response_application
from response import Response

@request_response_application
def application(request):
    # 获取查询字符串中的 name
    name = request.args().get('name', 'default_name')
    return Response(['<h1>hello {name}</h1>'.format(name=name)])

if __name__ == '__main__':
    httpd = make_server('0.0.0.0', 5000, application)
    httpd.serve_forever()

тестовая демонстрация

python wsgi_demo.py

По умолчанию:

~$ curl http://0.0.0.0:5000/
<h1>hello default_name</h1>

строка запроса с именем

~$ curl http://0.0.0.0:5000/demo?name=kobe
<h1>hello kobe</h1>