https://blog.csdn.net/a519640026/article/details/76157976css
做为python web开发,咱们首先要弄清楚,到底一个请求过来,发生了什么事,请求的传递是怎么样完成的,由nginx是怎么转发到uwsgi, uwsgi又是怎样把请求传给到咱们的框架(django or falsk)由咱们本身写的代码处理,返回数据给客户端的。所以我做了如下一个粗略的流程图:html
从上面的图看得出 wsgi server (好比uwsgi) 要和 wsgi application(好比django )交互,uwsgi须要将过来的请求转给django 处理,那么uwsgi 和 django的交互和调用就须要一个统一的规范,这个规范就是WSGI WSGI(Web Server Gateway Interface) ,WSGI是 Python PEP333中提出的一个 Web 开发统一规范
python
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规范。nginx
为了方便理解,咱们能够把server具体成 uwsgi, application具体成django程序员
这里能够看到,WSGI 服务器须要调用应用程序的一个可调用对象,这个可调用对象(callable object)能够是一个函数,方法,类或者可调用的实例,总之是可调用的。web
下面是一个 callable object 的示例,这里的可调用对象是一个函数:shell
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']
这里,咱们首先要注意,这个对象接收两个参数:django
environ:请求的环境变量,它是一个字典,包含了客户端请求的信息,如 HTTP 请求的首部,方法等信息,能够认为是请求上下文,
start_response:一个用于发送HTTP响应状态(HTTP status )、响应头(HTTP headers)的回调函数。在返回内容以前必须先调用这个回掉函数flask
上面的 start_response 这个回调函数的做用是用于让 WSGI Server 返回响应的 HTTP 首部和 HTTP 状态码。这个函数有两个必须的参数,返回的状态码和返回的响应首部组成的元祖列表。返回状态码和首部的这个操做始终都应该在响应 HTTP body 以前执行。服务器
还须要注意的是,最后的返回结果,应该是一个可迭代对象,这里是将返回的字符串放到列表里。若是直接返回字符串可能致使 WSGI 服务器对字符串进行迭代而影响响应速度。
固然,这个函数是一个最简单的可调用对象,它也能够是一个类或者可调用的类实例。
def application(env, start_response): start_response('200 OK', [('Content-Type', 'text/html'), ('X-Coder', 'Cooffeeli')]) return ['<h1>你好!!世界</h1>']
1 from wsgiref.simple_server import make_server 2 from app import application 3 4 # 启动 WSGI 服务器 5 httpd = make_server ( 6 'localhost', 7 9000, 8 application # 这里指定咱们的 application object) 9 ) 10 # 开始处理请求 11 httpd.handle_request() 12
python wsgiref_server.py
运行上面的程序,并访问 http://localhost:9000 , 将返回这次请求全部的首部信息。
这里,咱们利用 environ 字典,获取了请求中全部的变量信息,构形成相应的内容返回给客户端。
environ 这个参数中包含了请求的首部,URL,请求的地址,请求的方法等信息。能够参考 PEP3333来查看 environ 字典中必须包含哪些 CGI 变量。
本身实现WSGI Server
既然咱们知道了WSGI的规范,咱们彻底能够本身实现一个WSGI Server
根据这个规范,咱们能够总结WSGI Server须要实现如下功能:
监听端口,接收请求
接受HTTP请求后,解析HTTP协议
根据HTTP内容,生成env参数,该参数包括HTTP,wsgi信息,能够看做是请求上下文
实现一个start_response函数,做为调用application的参数,用做application回调函数,负责http相应头
实现代码: WSGIServer.py
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 import socket 4 import sys 5 import StringIO 6 from app import application 7 from datetime import datetime 8 9 class WSGIServer(object): 10 11 def __init__(self, server_address): 12 """初始构造函数, 建立监听socket""" 13 self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 14 self.listen_sock.bind(server_address) 15 self.listen_sock.listen(5) 16 (host, port) = self.listen_sock.getsockname() 17 self.server_port = port 18 self.server_name = socket.getfqdn(host) 19 20 21 def set_application(self, application): 22 """设置wsgi application, 供server 调用""" 23 self.application = application 24 25 26 def get_environ(self): 27 """构造WSGI环境变量,传给application的env参数""" 28 self.env = { 29 'wsgi.version': (1, 0), 30 'wsgi.url_scheme': 'http', 31 'wsgi.errors': sys.stderr, 32 'wsgi.multithread': False, 33 'wsgi.run_once': False, 34 'REQUEST_METHOD': self.request_method, 35 'PATH_INFO': self.request_path, 36 'SERVER_NAME': self.server_name, 37 'SERVER_PORT': str(self.server_port), 38 'wsgi.input': StringIO.StringIO(self.request_data), 39 } 40 return self.env 41 42 43 def start_response(self, http_status, http_headers): 44 """构造WSGI响应, 传给application的start_response""" 45 self.http_status = http_status 46 self.http_headers = dict(http_headers) 47 headers = { 48 'Date': datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'), 49 'Server': 'WSGIServer 1.0' 50 } 51 self.http_headers.update(headers) 52 53 54 def parse_request(self, text): 55 """获取http头信息,用于构造env参数""" 56 request_line = text.splitlines()[0] 57 request_info = request_line.split(' ') 58 (self.request_method, 59 self.request_path, 60 self.request_version) = request_info 61 62 63 def get_http_response(self, response_data): 64 """完成response 内容""" 65 res = 'HTTP/1.1 {status} \r\n'.format(status=self.http_status) 66 for header in self.http_headers.items(): 67 res += '{0}: {1} \r\n'.format(*header) 68 69 res += '\r\n' 70 71 res_body = '' 72 for val in response_data: 73 res_body += val 74 75 res += res_body 76 77 return res 78 79 80 def handle_request(self): 81 """处理请求""" 82 # 初始版本,只接受一个请求 83 conn, addr = self.listen_sock.accept() 84 85 # 获取http 请求的request内容 86 self.request_data = conn.recv(1024) 87 self.parse_request(self.request_data) 88 89 # 构造调用application须要的两个参数 env, start_response 90 env = self.get_environ() 91 start_response = self.start_response 92 93 # 调用application, 并获取须要返回的http response内容 94 response_data = self.application(env, start_response) 95 96 # 获取完整http response header 和 body, 经过socket的sendall返回到客户端 97 res = self.get_http_response(response_data) 98 conn.sendall(res) 99 100 # 脚本运行完毕也会结束 101 conn.close() 102 103 104 def make_server(server_address, application): 105 """建立WSGI Server 负责监听端口,接受请求""" 106 wsgi_server = WSGIServer(server_address) 107 wsgi_server.set_application(application) 108 109 return wsgi_server 110 111 112 SERVER_ADDRESS = (HOST, PORT) = '', 8124 113 wsgi_server = make_server(SERVER_ADDRESS, application) 114 wsgi_server.handle_request() 115
上面的 WSGI 服务器运行过程为:
WSGI SERVER---> WSGI APPLICATION
至此, wsgi server -> wsgi application 的交互讲解完毕, 下面咱们继续看nginx->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 应用程序的接口统一了起来。
总而言之, 这些协议就是进程交互的一种沟通方式。
举个例子:美国人和中国人沟通必需要有一个公共的语言:英语, 这时候英语就是两我的沟通的协议, 否则,一个说英语(uwsgi协议), 一个说中文(fastcgi协议)是确定会乱码的,处理不成功的。用同一个协议,你们都知道该如何解析过来的内容。
因此,nginx 和 uwsgi交互就必须使用同一个协议,而上面说了uwsgi支持fastcgi,uwsgi,http协议,这些都是nginx支持的协议,只要你们沟通好使用哪一个协议,就能够正常运行了。
将uwsgi 放在nginx后面,让nginx反向代理请求到uwsgi
uwsgi 原生支持HTTP, FastCGI, SCGI,以及特定的uwsgi协议, 性能最好的明显时uwsgi, 这个协议已经被nginx支持。
因此uwsgi 配置使用哪一个协议,nginx 要使用对应协议
# 使用http协议 uwsgi --http-socket 127.0.0.1:9000 --wsgi-file app.py
# nginx配置 lcation / { proxy_pass 127.0.0.1:9000; }
更多协议
[uwsgi] # 使用uwsgi协议 socket, uwsgi-socket 都是uwsgi协议 # bind to the specified UNIX/TCP socket using default protocol # UNIX/TCP 意思时能够UNIX: xx.sock, 或者 TCP: 127.0.0.1:9000 他们是均可以的 # UNIX 没有走TCP协议,不是面向链接, 而是直接走文件IO # nginx 使用uwsgi_pass socket = 127.0.0.1:9000 socket = /dev/shm/owan_web_uwsgi.sock uwsgi-socket = /dev/shm/owan_web_uwsgi.sock # nginx 使用 uwsgi_pass /dev/shm/owan_web_uwsgi.sock; # 使用fastcgi协议 fastcgi-socket # bind to the specified UNIX/TCP socket using FastCGI protocol # nginx 就能够好象PHP那样配置 使用fastcgi_pass fastcgi-socket = /dev/shm/owan_web_uwsgi.sock # nginx 使用fastcgi_pass /dev/shm/owan_web_uwsgi.sock; # 使用http协议 http-socket # bind to the specified UNIX/TCP socket using HTTP protocol # nginx 使用proxy_pass # 原来proxy_pass 是http协议,但不必定要用TCP # proxy_pass http://unix:/dev/shm/owan_web_uwsgi.sock; http-socket = /dev/shm/owan_web_uwsgi.sock # nginx 使用 proxy_pass /dev/shm/owan_web_uwsgi.sock; chdir = /data/web/advance_python/uwsgi/ wsgi-file = app.py processes = 4 threads = 2 master = true
至此,nginx ->uwsgi ->web 框架 以及 WSGI的相关知识已经讲解完了。 须要补充的是,咱们本身实现的WSGI Server只能支持一个请求,在以后的日子,我会再写一些教程,关于socket IO 复用 和线程池 让咱们本身写server支持多请求,多并发的功能