什么是 web 框架
Python Web 应用工作流程
下面图来自网上:
WSGI
为什么需要建立 WSGI 规范
https://www.python.org/dev/peps/pep-3333/ 建议在 Web 服务器和 Web 应用程序 / Web 框架之家建立一种简单统一的接口规范, 即 Python Web 服务器网关接口 (简称 WSGI). 以确保 Web 应用程序在不同的 Web 服务器之间具有可移植性.
参考资料
https://www.python.org/dev/peps/pep-3333/
译文 pep-3333 https://www.zybuluo.com/miaomiaomiao/note/48495
编写代码
代码部分参考的是手撸个简单的 python Web 框架教程 https://zhuanlan.zhihu.com/p/38223777 , 我觉得这个 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()
通过命令行跑起这个 server.
python wsgi_demo.py
通过另外一个命令行使用 curl 链接:
- ~$ curl http://0.0.0.0:5000/
- <h1>Hello, Web!</h1>
常用的 environ
environ 字典被用来包含这些 CGI 环境变量. 关于理解 CGI/WSGI/uWSGI 可以看看这个解释 tornado 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)
- }
在 application 里面我们通过 environ 获得我们需要的很多参数, 比如请求的查询字符串等.
封装 Request 对象
- # 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
改版后的 demo
- # 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()
测试 demo
python wsgi_demo.py
默认情况:
- ~$ curl http://0.0.0.0:5000/
- <h1>hello default_name</h1>
带有 name 的查询字符串
- ~$ curl http://0.0.0.0:5000/demo?name=kobe
- <h1>hello kobe</h1>
来源: https://juejin.im/post/5c442e4de51d4551df6f167d