前言:
又是WSGI ,这是我曾经比较熟悉的协议,以前针对实现了wsgi server的unicorn和uwsgi都写过源码解析的文章。 其实他们的实现也很简单,就是给flask、django这样的application传递environ,start_response 。
什么是WSGI协议,什么是WSGI Server,他们的区别是什么?
上线的架构图很容易误导别人,乍一看有nginx这样的web服务器,又有gunicorn这样的wsgi server。 我们先说明wsgi 跟 wsgi server的关系,wsgi是个协议,是web底层跟application解耦的协议。wsgi server是自己做web服务器借用wsgi协议来调用application。 我们需要明确一点,nginx是无法直接跟flask application做通信,需要借用wsgi server。flask本身也有个web服务器是werkzeug,so 才能启动服务并监听端口。记得以前uwsgi没名气的时候,我们都在使用apache + mode_wsgi模式,apache也无法直接跟tornado通信,是借用mod_wsgi把torando做成了unix socket服务,说白了也是启动了一个服务,靠apache来转发。
nginx、apache在这里只是启动了proxy的作用,那为什么不直接把uwsgi和gunicorn给暴露出去,因为nginx的静态文件处理能力极强。
WSGI怎么工作的
wsgi主要是两层,服务器方 和 应用程序 :
1 服务器方:从底层解析http解析,然后调用应用程序,给应用程序提供(环境信息)和(回调函数), 这个回调函数是用来将应用程序设置的http header和status等信息传递给服务器方.
2 应用程序:用来生成返回的header,body和status,以便返回给服务器方。
WSGI把来自socket的数据包解析为http格式,然后进而变化为environ变量,这environ变量里面有wsgi本身的信息(比如 host, post,进程模式等),还有client的header及body信息。start_respnse是一个函调函数,必须要附带两个参数,一个是status(http状态),response_headers(响应的header头信息)。
像flask、django、tornado都会暴露WSGI协议入口,我们只需要自己实现WSGI协议,wsgi server然后给flask传递environ,及start_response, 等到application返回值之后,我再socket send返回客户端。
WSGI的优点、缺点是什么?
优点:
多样的部署选择和组件之间的高度解耦
由于上面提到的高度解耦特性,理论上,任何一个符合WSGI规范的App都可以部署在任何一个实现了WSGI规范的Server上,这给Python Web应用的部署带来了极大的灵活性。
Flask自带了一个基于Werkzeug的调试用服务器。根据Flask的文档,在生产环境不应该使用内建的调试服务器,而应该采取以下方式之一进行部署:
GUNICORN
UWSGI
缺点:
没有
我们在wsgi层可以做什么时尚的操作:
- 黑白名单规则防御.
- 可以通过重写环境变量,根据目标URL,将请求消息路由到不同的应用对象。这意思就是说,实现一套类似nginx location proxy的规则,可以把阻塞高性能转给tornado的app. 当然这是理想化的操作.
- 允许在一个进程中同时运行多个应用程序或应用框架.
- 负载均衡和远程处理,通过在网络上转发请求和响应消息.
我们用python具体实现这个wsgi server及协议.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
#xiaorui.cc
import socket
import StringIO
import sys
class WSGIServer( object ):
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
request_queue_size = 1
def __init__( self , server_address):
# 创建一个可用的socket
self .listen_socket = listen_socket = socket.socket(
self .address_family,
self .socket_type
)
#socket的工作模式
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
# Bind
listen_socket.bind(server_address) #绑定地址
# Activate
listen_socket.listen( self .request_queue_size) #监听文件描述符
# Get server host name and port
host, port = self .listen_socket.getsockname()[: 2 ]
self .server_name = socket.getfqdn(host)
self .server_port = port
self .headers_set = []
def set_app( self , application):
self .application = application
def serve_forever( self ): #启动WSGI server服务,不停的监听并获取socket数据。
listen_socket = self .listen_socket
while True :
self .client_connection, client_address = listen_socket.accept()
self .handle_one_request() #处理新连接
def handle_one_request( self ): #主要处理函数
self .request_data = request_data = self .client_connection.recv( 1024 )
print (''.join(
'< {line}\n' . format (line = line)
for line in request_data.splitlines()
))
self .parse_request(request_data)
env = self .get_environ()
#给flask\tornado传递两个参数,environ,start_response
result = self .application(env, self .start_response)
# Construct a response and send it back to the client
self .finish_response(result)
def parse_request( self , text): #处理socket的http协议
request_line = text.splitlines()[ 0 ]
request_line = request_line.rstrip( '\r\n' )
# Break down the request line into components
( self .request_method, # GET
self .path, # /hello
self .request_version # HTTP/1.1
) = request_line.split()
def get_environ( self ): #获取environ数据
env = {}
env[ 'wsgi.version' ] = ( 1 , 0 )
env[ 'wsgi.url_scheme' ] = 'http'
env[ 'wsgi.input' ] = StringIO.StringIO( self .request_data)
env[ 'wsgi.errors' ] = sys.stderr
env[ 'wsgi.multithread' ] = False
env[ 'wsgi.multiprocess' ] = False
env[ 'wsgi.run_once' ] = False
env[ 'REQUEST_METHOD' ] = self .request_method # GET
env[ 'PATH_INFO' ] = self .path # /hello
env[ 'SERVER_NAME' ] = self .server_name # localhost
env[ 'SERVER_PORT' ] = str ( self .server_port) # 8888
return env
def start_response( self , status, response_headers, exc_info = None ): #创建回调函数.
server_headers = [
( 'Date' , 'Tue, 31 Mar 2015 12:54:48 GMT' ),
( 'Server' , 'WSGIServer 0.3' ),
]
self .headers_set = [status, response_headers + server_headers]
def finish_response( self , result): #把application返回给WSGI的数据返回给客户端。
try :
status, response_headers = self .headers_set
response = 'HTTP/1.1 {status}\r\n' . format (status = status)
for header in response_headers:
response + = '{0}: {1}\r\n' . format ( * header)
response + = '\r\n'
for data in result:
response + = data
# Print formatted response data a la 'curl -v'
print (''.join(
'> {line}\n' . format (line = line)
for line in response.splitlines()
))
self .client_connection.sendall(response)
finally :
self .client_connection.close()
SERVER_ADDRESS = (HOST, PORT) = '', 8888
def make_server(server_address, application):
server = WSGIServer(server_address)
server.set_app(application)
return server
if __name__ = = '__main__' :
if len (sys.argv) < 2 :
sys.exit( 'Provide a WSGI application object as module:callable' )
app_path = sys.argv[ 1 ]
module, application = app_path.split( ':' )
module = __import__ (module) #动态加载模块
application = getattr (module, application) #使用自省的模式加载application的WSGI协议入口。
httpd = make_server(SERVER_ADDRESS, application)
print ( 'WSGIServer: Serving HTTP on port {port} ...\n' . format (port = PORT))
httpd.serve_forever()
|
下面是flask application的实例, 我们会发现python的常见web框架都兼容了wsgi接口,没有例外。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#xiaorui.cc
from flask import Flask
from flask import Response
flask_app = Flask( 'flaskapp' )
@flask_app .route( '/search' )
def hello_world():
return Response(
'xiaorui.cc Golang vs python !\n' ,
mimetype = 'text/plain'
)
app = flask_app.wsgi_app
|
运行方式很简单:
1
|
python webserver2.py flaskapp:app
|
这样一个wsgi就构成了,下次我们会借用这wsgi框架扩展成prefork wsgi server,类似gunicorn那样。 以前在wsgi做过一些application的分流,但涉及到高并发的场景下的分流,还是建议直接在nginx层面做。 现在nginx lua的编程能力越来越强,大家都在使用nginx lua做网关及入口的开发。
参考文章:
https://ruslanspivak.com/lsbaws-part2/
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://xiaorui.cc/2016/04/16/打造mvc框架之wsgi协议的优缺点及接口实现/