WSGI服务器
WSGI
转:https://www.jianshu.com/p/29f66eb4e55a
Web Server Gateway Interface 的缩写,即 Web 服务器网关接口。
Python web开发中,服务端程序分为两个部分
-
服务器程序(用来接收、整理客户端发送的请求)
-
应用程序(处理服务器程序传递过来的请求)
其实我们使用的Flask,Django,Tornado等框架其实相当于开发服务端的应用程序,处理后台逻辑
但是,服务器程序和应用程序互相配合才能给用户提供服务,而不同应用程序(不同框架)会有不同的函数、功能。 此时,我们就需要一个标准,让服务器程序和应用程序都支持这个标准,那么,二者就能很好的配合了
WSGI:wsgi是python web开发的标准,类似于协议。它是服务器程序和应用程序的一个约定,规定了各自使用的接口和功能,以便二和互相配合
WSGI 规范了些什么,下图能很直观的说明。
首先,我们对WSGI有个简单的理解,下面我将通过一个实例来帮助大家理解
首先假设我们有这么一个服务器
wsgi/server.py
# coding=utf-8
import socket
# 建立TCP套接字
listener = socket.socket()
# 设置端口可复用
listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
# 绑定IP和端口
listener.bind(('0.0.0.0', 8080))
# 设置监听队列,并创建监听套接字
listener.listen(5)
print('Serving HTTP on 0.0.0.0 port 8080 ...')
while True:
# 等待客户端连接
client_connection, client_address = listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
headers_set = None
# 服务器要传递给应用程序的start_response函数,应用程序在返回数据前需要先调用
def start_response(status, headers):
"""
paramer status:状态码
paramer headers:是由 (header_name, header_value) 这样的元祖组成的列表
[('Content-type', 'text/plain')]
"""
global headers_set
headers_set = [status, headers]
# 获取请求类型和请求地址
method, path, _ = request.split(b' ', 2)
environ = {'REQUEST_METHOD': method.decode('utf-8'),
'PATH_INFO': path.decode('utf-8')}
# 调用应用程序
from wsgi_app import simple_app
app_result = simple_app(environ, start_response)
# 组织reponse并返回,要符合http协议的规范,响应行 相应头 空行 相应体
response_status, response_headers = headers_set
response = f'HTTP/1.1 {response_status}\r\n'
for header in response_headers:
response += f'{header[0]}: {header[1]}\r\n'
response += '\r\n'
response = response.encode('utf-8')
for data in app_result:
response += data
client_connection.sendall(response)
client_connection.close()
通过上面的图,我们可以清晰的知道服务器每接收到一个请求,就会通过 application_callable(environ, start_response) 调用应用程序。
而应用程序在处理完请求准备返回数据的时候,会先调用服务传给它的函数 start_response(status, headers, exec_info),最后再返回可迭代对象作为数据
另外我们在创建一个Web应用
wsgi/app.py
def simple_app(environ, start_response):
"""
paramer environ:
{
AUTH_TYPE
CONTENT_LENGTH #HTTP请求中Content-Length的部分
CONTENT_TYPE #HTTP请求中Content-Tpye的部分
GATEWAY_INTERFACE
HTTP_* #包含一系列变量, 如HTTP_HOST,HTTP_ACCEPT等
PATH_INFO #URL路径除了起始部分后的剩余部分,用于找到相应的应用程序对象,如果请求的路 径就是根路径,这个值为空字符串
PATH_TRANSLATED
QUERY_STRING #URL路径中?后面的部分
REMOTE_ADDR
REMOTE_HOST
REMOTE_IDENT
REMOTE_USER
REQUEST_METHOD #HTTP 请求方法,例如 "GET", "POST"
SCRIPT_NAME #URL路径的起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,那么 这个值可以为空字符串
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL #客户端请求的协议(HTTP/1.1 HTTP/1.0)
SERVER_SOFTWARE
}
paramer start_reponse:应用程序在返回数据之前会先调用,用于告诉服务设置响应的头部信息或错误处理等
"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [f'Request {environ["REQUEST_METHOD"]}'
f' {environ["PATH_INFO"]} has been'
f' processed\r\n'.encode('utf-8')]
好了,启动服务器后(即执行服务器代码,和之前的类似,这里不赘述),然后请求看看结果
➜ ~ curl 127.0.0.1:8080/user/1
Request GET /user/1 has been processed
嗯,程序是正常的。
上面为了说明,代码耦合性较大,如果服务器需要更换应用的话,还得修改服务器代码,这显然是有问题的。现在原理差不多说清楚了,我们把代码优化下
wsgig/wsgi_server_oop.py
# coding=utf-8
import socket
import sys
class WSGIServer:
def __init__(self):
self.listener = socket.socket()
self.listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
self.listener.bind(('0.0.0.0', 8080))
self.listener.listen(1)
print('Serving HTTP on 0.0.0.0'
' port 8080 ...')
self.app = None
self.headers_set = None
def set_app(self, application):
self.app = application
def start_response(self, status, headers):
self.headers_set = [status, headers]
def serve_forever(self):
while True:
listener = self.listener
client_connection, client_address = \
listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
method, path, _ = request.split(b' ', 2)
# 为简洁的说明问题,这里填充的内容有些随意
# 如果有需要,可以自行完善
environ = {
'wsgi.version': (1, 0),
'wsgi.url_scheme': 'http',
'wsgi.input': request,
'wsgi.errors': sys.stderr,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.run_once': False,
'REQUEST_METHOD': method.decode('utf-8'),
'PATH_INFO': path.decode('utf-8'),
'SERVER_NAME': '127.0.0.1',
'SERVER_PORT': '8080',
}
app_result = self.app(environ, self.start_response)
response_status, response_headers = self.headers_set
response = f'HTTP/1.1 {response_status}\r\n'
for header in response_headers:
response += f'{header[0]}: {header[1]}\r\n'
response += '\r\n'
response = response.encode('utf-8')
for data in app_result:
response += data
client_connection.sendall(response)
client_connection.close()
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit('Argv Error')
app_path = sys.argv[1]
module, app = app_path.split(':')
# 导入wsgi_app
module = __import__(module)
app = getattr(module, app)
server = WSGIServer()
server.set_app(app)
server.serve_forever()
基本原理没变,只是使用了面向对象的方式修改了下原来的代码,同时 environ 添加了一些必要的环境信息。
可以使用以前的应用测试一下
➜ wsgi python wsgi_server_oop.py wsgi_app:simple_app
Serving HTTP on 0.0.0.0 port 8080 ...
然后发送请求
➜ ~ curl 127.0.0.1:8080/user/1
Request GET /user/1 has been processed
也可以用flask框架创建一个应用试试
wsgi/flask_app.py
# coding=utf-8
from flask import Flask
from flask import Response
flask_app = Flask(__name__)
@flask_app.route('/user/<int:user_id>',
methods=['GET'])
def hello_world(user_id):
return Response(
f'Get /user/{user_id} has been'
f' processed in flask app\r\n',
mimetype='text/plain'
)
重新启动服务器
➜ wsgi python wsgi_server_oop.py flask_app:flask_app
Serving HTTP on 0.0.0.0 port 8080 ...
然后发送请求
➜ ~ curl 127.0.0.1:8080/user/1
Get /user/1 has been processed in flask app
因为 Flask 也是遵守 WSGI 规范的,所以执行也没有问题。
再来谈谈中间件Middleware
middleware是介于服务器程序和应用程序中间的部分,middleware对服务器程序和应用程序是透明的
对于服务器程序来说,middleware就是应用程序,middleware需要伪装成应用程序,传递给服务器程序
对于应用程序来说,middleware就是服务器程序,middleware需要伪装成服务器程序,接受并调用应用程序
服务器程序获取到了客户端请求的URL,需要把URL交给不同的函数处理,这个功能可以使用middleware实现:
# URL Routing middleware
def urlrouting(url_app_mapping):
def midware_app(environ, start_response): #函数可调用,包含2个参数,返回可迭代的值
url = environ['PATH_INFO']
app = url_app_mapping[url] #获得对应url的应用程序
start_response(status, response_headers)
result = app(environ, start_response) #调用应用程序
return result
return midware_app
函数midware_app就是middleware:
一方面,midware_app函数设置了应用程序所需要的变量,并调用了应用程序。所以对于应用程序来说,它是一个服务器程序
另一方面,midware_app函数是一个可调用的对象,接收两个参数,同时可调用对象返回了一个可迭代的值。所以对于服务器程序来说,它是一个应用程序
写中间件(middleware)的逻辑:
1. middleware需要伪装成应用程序—> WSGI应用程序的要求 —> 1. 可调用 2. 两个参数 3. 返回可迭代的值
2. middleware需要伪装成服务器程序 —> WSGI服务器程序的要求 —> 调用应用程序
上一篇: erlang hibernate
下一篇: 004--利用系统溢出漏洞提权