说说我对 WSGI 的理解
先说下 WSGI 的表面意思,Web Server Gateway Interface 的缩写,即 Web 服务器网关接口。
之前不知道 WSGI 意思的伙伴,看了上面的解释后,我估计也还是不清楚,所以下面结合实际场景说明,先让大家有个大致的认识。最后我们再自己实现一个,加深对 WSGI 的理解。
我们现在使用 Python 编写 Web 应用,可以用比较流行的 Flask、Django 框架,也可以按自己的想法直接写一个。可选的服务器软件也特别多,比如常见的有 Apache、Nginx、IIS 等,除此外,也有很多小众的软件。但是,现在问题来了,我该怎么部署?在没有 WSGI 规范之前,一个服务器调度 Python 应用是用这种方式,另一款服务器使用的是那种方式,这样的话,编写出来的应用部署时只能选择局限的某个或某些服务器,达不到通用的效果。
注意:下文中的代码基于 Python 3.6 编写。
假如有这么一个服务器
wsgi/server.py
# coding=utf-8
import socket
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
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}')
response = b"""
HTTP/1.1 200 OK
Hello, World!
"""
client_connection.sendall(response)
client_connection.close()
实现比较简单,就是监听 8080 端口,如果有请求在终端进行打印,并返回 Hello, World! 的响应。
终端中启动服务器
➜ wsgi python server.py
Serving HTTP on 0.0.0.0 port 8080 ...
再开一个终端,请求下
➜ ~ curl 127.0.0.1:8080
HTTP/1.1 200 OK
Hello, World!
说明服务器工作正常。
另外有一个 Web 应用
wsgi/app.py
# coding=utf-8
def simple_app():
return b'Hello, World!\r\n'
现在要部署(也就是让这个整体跑起来),简单粗暴的做法就是在服务器里面直接调用 app 中相应的方法。就像这样
wsgi/server2.py
# coding=utf-8
import socket
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
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}')
from app import simple_app
response = 'HTTP/1.1 200 OK\r\n\r\n'
response = response.encode('utf-8')
response += simple_app()
client_connection.sendall(response)
client_connection.close()
运行脚本
注意:因为使用端口相同的缘故,请先关闭上次的脚本,然后再执行,不然会由于端口冲突而报错。
➜ wsgi python server2.py
Serving HTTP on 0.0.0.0 port 8080 ...
然后请求一下看看效果
➜ ~ curl 127.0.0.1:8080
Hello, World!
嗯,可以了。但是,上面的服务器和应用整体是跑起来了,那么我换一个服务器或者应用呢。由于服务器与应用之间怎么交互完全没有规范,比如服务器应该如何把请求信息传给应用,应用处理完毕后又怎么告诉服务器开始返回响应,如果都是各搞各的,服务器需要定制应用,应用也要定制服务器,这要一个应用能跑起来也太麻烦了点吧。
所以,WSGI 的出现就是为了解决上面的问题,它规定了服务器怎么把请求信息告诉给应用,应用怎么把执行情况回传给服务器,这样的话,服务器与应用都按一个标准办事,只要实现了这个标准,服务器与应用随意搭配就可以,灵活度大大提高。
WSGI 规范了些什么,下图能很直观的说明。
[图片]
首先,应用必须是一个可调用对象,可以是函数,也可以是实现了 __call__()
方法的对象。
每收到一个请求,服务器会通过 application_callable(environ, start_response) 调用应用。
应用在处理完毕准备返回数据的时候,先调用服务传给它的函数 start_response(status, headers, exec_info),最后再返回可迭代对象作为数据。(不理解可迭代对象的伙伴可以看下我之前的一篇文章《搞清楚Python的迭代器、可迭代对象、生成器》)
其中,environ 必须是一个字典,包括了请求的相关信息,比如请求方式、请求路径等等,start_response 是应用处理完毕后,需要调用的函数,用于告诉服务设置响应的头部信息或错误处理等等。
status 必须是 999 Message here
这样的字符串,比如 200 OK
、404 Not Found
等,headers 是一个由 (header_name, header_value) 这样的元祖组成的列表,最后一个 exec_info 是可选参数,一般在应用出现错误的时候会用到。
知道了 WSGI 的大致概念,下面我们来实现一个。
首先是应用
wsgi/wsgi_app.py
# coding=utf-8
def simple_app(environ, start_response):
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')]
这里定义了一个函数(可调用对象),它可以使用服务器传给它的请求相关的内容 environ,这里使用了 REQUEST_METHOD 和 PATH_INFO 信息。在返回之前调用了 start_response,方便服务器设置一些头部信息。
然后是服务器
wsgi/wsgi_server.py
# coding=utf-8
import socket
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
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
def start_response(status, headers):
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)
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()
服务器监听相关代码没怎么变化,主要是处理请求的时候有些不同。
首先定义了 start_response(status, headers) 函数,自身并不会调用。
然后调用应用,将当前的请求信息 environ 和上面的 start_response 函数传给它,让其自己决定使用什么请求信息以及在处理完成准备返回数据之前调用 start_response 设置头部信息。
好了,启动服务器后(即执行服务器代码,和之前的类似,这里不赘述),然后请求看看结果
➜ ~ curl 127.0.0.1:8080/user/1
Request GET /user/1 has been processed
嗯,程序是正常的。
上面为了说明,代码耦合性较大,如果服务器需要更换应用的话,还得修改服务器代码,这显然是有问题的。现在原理差不多说清楚了,我们把代码优化下
wsgi/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(':')
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 规范的,所以执行也没有问题。
至此,一个粗略的 WSGI 规范就实现了,虽说代码不优雅,一些核心的东西还是体现出来了。不过毕竟忽略了很多东西,比如错误处理等,要在生产环境中使用的话还远远不够,想知道得更全面的伙伴可以去看看 PEP 3333。
目前流行的 Web 应用框架比如 Django、Bottle 等,服务器 Apahce、Nginx、Gunicorn 等也都支持这个规范。因此,框架和应用随意搭配基本没什么问题。
参考
- https://www.python.org/dev/peps/pep-3333
- http://ruslanspivak.com/lsbaws-part2
- https://www.toptal.com/python/pythons-wsgi-server-application-interface
- https://gist.github.com/thomasballinger/5807241
原文链接:https://www.kevinbai.com/articles/25.html
关注「小小后端」公众号获取最新文章推送!
说说我对 WSGI 的理解的更多相关文章
- WSGI的理解 perfect
https://blog.csdn.net/hzrandd/article/details/10099871 https://blog.csdn.net/cloudxli/article/detail ...
- 对于python WSGI的理解
首先看看WSGI的目的是什么? 是用来定义一个统一的接口. 这个接口是针对Web服务器和python Web应用之间的. 以增加Python web应用在不同Web 服务器之间的可移植性. 也就是说如 ...
- WSGI的理解
Python web开发中,服务端程序可分为2个部分: 服务器程序(用来接收.整理客户端发送的请求) 应用程序(处理服务器程序传递过来的请求) 在开发应用程序的时候,我们会把常用的功能封装起来,成为各 ...
- OpenStack设计与实现5——RESTful API和WSGI
转https://segmentfault.com/a/1190000004361778 Tips:文章为拜读@xingjiarong 后有感而做的分享,先对作者表示感谢,附原文地址:http://b ...
- WSGI 简介(使用python描述)
WSGI 简介 背景 Python Web 开发中,服务端程序可以分为两个部分,一是服务器程序,二是应用程序.前者负责把客户端请求接收,整理,后者负责具体的逻辑处理.为了方便应用程序的开发,我们把常用 ...
- linux性能评估与分析工具
linux是一个开源系统,其内核负责管理系统的进程,内存,设备驱动程序,文件和网络系统, 决定着系统的性能和稳定性.由于内核源码很容易获取,任何人都可以将自己认为优秀的代码 加入到其中.linux默认 ...
- Django 分析(一)Requst、Middleware 和 Response 数据流
0. 前言 通过 Django 编写 HTTP 接口时,我们需要指定 URL.Model 和 Views 函数(或者指定 RESTBaseView 对象解析参数和编写逻辑) 编写逻辑时的基本思路就是解 ...
- Django Full Coverage
Django(个人推荐, 如果项目较大 需要协同开发, 建议使用django这种重量级框架, 如果类似于纯api的后端应用建议使用 flask, 轻量小巧 , 麻雀虽小五脏俱全) 1.Django是什 ...
- 理解WSGI
WSGI是什么? WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义 ...
随机推荐
- Java并发--三大性质
一.多线程的三大性质 原子性:可见性.有序性 二.原子性 原子性介绍 原子性是指:一个操作时不可能中断的,要么全部执行成功要么全部执行失败,有着同生共死的感觉.即使在多线程一起执行的时候,一个操作一旦 ...
- 在vue中使用echarts报错Cannot read property getAttribute of null
报错信息如下: 报错代码: mounted() { // ... this.drwaCharts() // drawCharts方法为自己定义的包含渲染 echarts 图表的方法 // ...} 之 ...
- 修改vsftpd的默认根目录/var/ftp/pub到另一个目录
修改ftp的根目录只要修改/etc/vsftpd/vsftpd.conf文件即可: 加入如下几行: local_root=/var/www/html chroot_local_user=YES ano ...
- Cinder Volume 服务启动流程分析和周期性任务分析
1.cinder-volume服务的程序入口 #!/usr/bin/python2 # PBR Generated from u'console_scripts' import sys from ci ...
- 你所不知道的Spring的@Autowired实现细节
前言 相信很多Java开发都遇到过一个面试题:Resource和Autowired的区别是什么?这个问题的答案相信基本都清楚,但是这两者在Spring中是如何实现的呢?这就要分析Spring源码才能知 ...
- 操作系统-I/O(2)设备的分配
作业执行前对设备提出申请时,指定某台具体的物理设备会让设备分配变得简单,但如果所指定设备出现故障,即便计算机系统中有同类设备也不能运行 设备独立性:用户通常不指定物理设备,而是指定逻辑设备,使得用户作 ...
- N皇后问题的二进制优化详细思路
题目啊常规解法(DFS)在此就不赘述了... 直接进入正题. 众所周知,N皇后是NP完全类问题,n稍微大了点求解过程就会变得很长. 算法方面很难再有质的效率突破,但这不妨在其他细节上下下功夫. 揆诸常 ...
- IdentityServer4中ResourceOwnerPassword模式获取accecc_token,并使用refresh_token刷新accecc_token
一.IS4服务端配置 1.配置Client new Client { ClientId = "xamarin", ClientSecrets = { new Secret(&quo ...
- formily-面向中后台场景的复杂解决方案
正文 在解决企业级应用的前端问题中,表单是个无法绕过的大山,正好最近有时间,调研一下 Formily-来自阿里巴巴的面向中后台复杂场景的表单解决方案,也是一个表单框架,前身是 UForm.主要解决如何 ...
- TCP/IP网络编程之socket交互流程
一.概要 本篇文章主要讲解基于.net中tcp/ip网络通信编程.在自我进步的过程中记录这些内容,方便自己记忆的同时也希望可以帮助到大家.技术的进步源自于分享和不断的自我突破. 技术交流QQ群:580 ...