pythonnginx+uwsgi+WSGI处理请求详解
免费视频福利推荐:
请求从 Nginx 到 uwsgi 到 django 交互概览
作为python web开发,我们⾸先要弄清楚,到底⼀个请求过来,发⽣了什么事,请求的传递是怎么样完成的,由nginx是怎么转发到uwsgi, uwsgi⼜是怎样把请求传给到我们的框架(django or falsk)由我们⾃⼰写的代码处理,返回数据给客户端的。因此我作了以下⼀个粗略的流程图:
uwsgi 处理过程.png
以下我会逐个步骤从下往上详细讲解,并附上代码和配置,
WSGI 协议
从上⾯的图看得出 wsgi server (⽐如uwsgi) 要和 wsgi application(⽐如django )交互,uwsgi需要将过来的请求转给django 处理,那么uwsgi 和 django的交互和调⽤就需要⼀个统⼀的规范,这个规范就是WSGI WSGI(Web Server Gateway Interface) ,WSGI是中提出的⼀个 Web 开发统⼀规范。
Web 应⽤的开发通常都会涉及到 Web 框架(django, flask)的使⽤,各个 Web 框架内部由于实现不同相互不兼容,给⽤户的学习,使⽤和部署造成了很多⿇烦。
正是有了WSGI这个规范,它约定了wsgi server 怎么调⽤web应⽤程序的代码,web 应⽤程序需要符合什么样的规范,只要 web 应⽤程序和 wsgi server 都遵守 WSGI 协议,那么,web 应⽤程序和 wsgi server就可以随意的组合。 ⽐如uwsgi+django , uwsgi+flask, gunicor+django, gunicor+flask 这些的组合都可以任意组合,因为他们遵循了WSGI规范。
WSGI 标准
WSGI 标准中主要定义了两种⾓⾊:
“server” 或 “gateway” 端
“application” 或 “framework” 端
wsgi.png
为了⽅便理解,我们可以把server具体成 uwsgi, application具体成django
这⾥可以看到,WSGI 服务器需要调⽤应⽤程序的⼀个可调⽤对象,这个可调⽤对象(callable object)可以是⼀个函数,⽅法,类或者可调⽤的实例,总之是可调⽤的。
下⾯是⼀个 callable object 的⽰例,这⾥的可调⽤对象是⼀个函数:
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/html')]
start_response(status, response_headers)
return ['Hello World']
这⾥,我们⾸先要注意,这个对象接收两个参数:
environ:请求的环境变量,它是⼀个字典,包含了客户端请求的信息,如 HTTP 请求的⾸部,⽅法等信息,可以认为是请求上下⽂,start_response:⼀个⽤于发送HTTP响应状态(HTTP status )、响应头(HTTP headers)的回调函数。在返回内容之前必须先调⽤这个回掉函数
上⾯的 start_response 这个回调函数的作⽤是⽤于让 WSGI Server 返回响应的 HTTP ⾸部和 HTTP 状态码。这个函数有两个必须的参数,返回的状态码和返回的响应⾸部组成的元祖列表。返回状态码和⾸部的这个操作始终都应该在响应 HTTP body 之前执⾏。
还需要注意的是,最后的返回结果,应该是⼀个可迭代对象,这⾥是将返回的字符串放到列表⾥。如果直接返回字符串可能导致 WSGI 服务器对字符串进⾏迭代⽽影响响应速度。
当然,这个函数是⼀个最简单的可调⽤对象,它也可以是⼀个类或者可调⽤的类实例。
WSGI 实例
wsgi application 的代码 app.py
def application(env, start_response):
start_response('200 OK', [('Content-Type', 'text/html'), ('X-Coder', 'Cooffeeli')])
return ['<h1>你好!!世界</h1>']
wsgi server 代码 wsgi_server.py
我们可以借助 python 的 wsgiref 库运⾏⼀个 WSGI 服务器(当然这个 WSGI 服务器同时也是 Web 服务器),⽤它来运⾏我们的
application
from wsgiref.simple_server import make_server
from app import application
# 启动 WSGI  服务器
httpd = make_server (
'localhost',
9000,
application # 这⾥指定我们的 application object)
)
# 开始处理请求
httpd.handle_request()
python wsgiref_server.py
⾃⼰实现WSGI Server
既然我们知道了WSGI的规范,我们完全可以⾃⼰实现⼀个WSGI Server
根据这个规范,我们可以总结WSGI Server需要实现以下功能:
监听端⼝,接收请求
接受HTTP请求后,解析HTTP协议
根据HTTP内容,⽣成env参数,该参数包括HTTP,wsgi信息,可以看作是请求上下⽂
实现⼀个start_response函数,作为调⽤application的参数,⽤作application回调函数,负责http相应头实现代码: WSGIServer.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
import StringIO
from app import application
from datetime import datetime
class WSGIServer(object):
def __init__(self, server_address):
"""初始构造函数, 创建监听socket"""
self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.listen_sock.bind(server_address)
self.listen_sock.listen(5)
(host, port) = self.sockname()
self.server_port = port
self.server_name = fqdn(host)
def set_application(self, application):
"""设置wsgi application, 供server 调⽤"""
self.application = application
def get_environ(self):
"""构造WSGI环境变量,传给application的env参数"""
'wsgi.version': (1, 0),
'wsgi.url_scheme': 'http',
's': sys.stderr,
's': sys.stderr,
'wsgi.multithread': False,
'wsgi.run_once': False,
'REQUEST_METHOD': quest_method,
'PATH_INFO': quest_path,
webserver接口开发
'SERVER_NAME': self.server_name,
'SERVER_PORT': str(self.server_port),
'wsgi.input': StringIO.quest_data),
}
v
def start_response(self, http_status, http_headers):
"""构造WSGI响应,传给application的start_response"""
self.http_status = http_status
self.http_headers = dict(http_headers)
headers = {
'Date': datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'),
'Server': 'WSGIServer 1.0'
}
self.http_headers.update(headers)
def parse_request(self, text):
"""获取http头信息,⽤于构造env参数"""
request_line = text.splitlines()[0]
request_info = request_line.split(' ')
(quest_method,
def get_http_response(self, response_data):
"""完成response 内容"""
res = 'HTTP/1.1 {status} \r\n'.format(status=self.http_status)
for header in self.http_headers.items():
res += '{0}: {1} \r\n'.format(*header)
res += '\r\n'
res_body = ''
for val in response_data:
res_body += val
res += res_body
return res
def handle_request(self):
"""处理请求"""
# 初始版本,只接受⼀个请求
conn, addr = self.listen_sock.accept()
# 获取http 请求的request内容
self.parse_quest_data)
# 构造调⽤application需要的两个参数 env, start_response
env = _environ()
start_response = self.start_response
# 调⽤application, 并获取需要返回的http response内容
response_data = self.application(env, start_response)
# 获取完整http response header 和 body, 通过socket的sendall返回到客户端
res = _http_response(response_data)
conn.sendall(res)
# 脚本运⾏完毕也会结束
conn.close()
def make_server(server_address, application):
"""创建WSGI Server 负责监听端⼝,接受请求"""
wsgi_server = WSGIServer(server_address)
wsgi_server.set_application(application)
return wsgi_server
SERVER_ADDRESS = (HOST, PORT) =  '', 8124
wsgi_server = make_server(SERVER_ADDRESS, application)
wsgi_server.handle_request()
上⾯的 WSGI 服务器运⾏过程为:
1. 初始化,创建套接字,绑定端⼝
2. 接收客户端请求
3. 解析 HTTP 协议
4. 构造 WSGI 环境变量(environ)
5. 调⽤ application
6. 回调函数 start_response 设置好响应的状态码和⾸部
7. 返回信息
⾄此, wsgi server -> wsgi application 的交互讲解完毕, 下⾯我们继续看nginx->uwsgi交互过程
启动 uwsgi
上⾯说了我们⾃⼰实现WSGI Server的过程,现在我们⽤uwsgi 来作为Server
运⾏监听请求uwsgi
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2
执⾏这个命令会产⽣4个uwsgi进程(每个进程2个线程),1个master进程,当有⼦进程死掉时再产⽣⼦进程,1个 the HTTP router进程,⼀个6个进程。
这个Http route进程的地位有点类似nginx,(可以认为与nginx同⼀层)负责路由http请求给worker, Http route进程和worker之间使⽤的是uwsgi协议
FastCgi协议, uwsgi协议, http协议有什么⽤?
在构建 Web 应⽤时,通常会有 Web Server (nginx)和 Application Server(wsgi server eg:uwsgi) 两种⾓⾊。其中 Web Server 主要负责接受来⾃⽤户的请求,解析 HTTP 协议,并将请求转发给 Application Server,Application Server 主要负责处理⽤户的请求,并将处理的结果返回给 Web Server,最终 Web Server 将结果返回给⽤户。
由于有很多动态语⾔和很多种 Web Server,他们彼此之间互不兼容,给程序员造成了很⼤的⿇烦。因此就有了 CGI/FastCGI ,uwsgi 协议,定义了 Web Server 如何通过输⼊输出与 Application Server 进⾏交互,将 Web 应⽤程序的接⼝统⼀了起来。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。