说说我对 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程序CPU 100%调试
前置 PID为进程id,NID为线程ID 步骤一.找到最耗CPU的进程 top 然后键入P,按CPU占用率排序(M是按内存排序) 步骤二.找到进程中最耗CPU的线程 top -Hp PID 步骤三.将 ...
- ceph 快照,克隆
转载 https://my.oschina.net/wangzilong/blog/1595081 ceph 快照,克隆 ceph是一个非常好的后端存储系统.其中包括最常用的块存储,对象存储,文件系统 ...
- 第二篇 Scrum冲刺博客
一.会议图片 二.项目进展 成员 完成情况 今日任务 冯荣新 搜索框,首页轮播图,分类导航 商品列表,商品详情轮播图 陈泽佳 背景展示,选择并显示图片 历史足迹,静态页面 徐伟浩 登录权限获取 商品信 ...
- python格式化输出及大量案例
python格式化输出符号及大量案例 1.格式化输出符号 python格式化输出符号 格式化符号 含义 %c 转化成字符 %r 优先使用repr()函数进行字符串转化 %s 转换成字符串,优先使用st ...
- 怎么下载chrome的扩展程序
很多时候我们是没办法访问谷歌扩展应用程序 chrome应用商店的,这时候我们最好能把对应扩展应用程序下载保存,以便提供给其他人员使用. 搜索得到知乎有很全的方法: 如何导出并打包第三方chrome扩展 ...
- win7中java编程工具安装 java环境变量设置
一.下载java 官方地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u26-download-400750.html ...
- Spring Cloud--尚硅谷2020最新版
Spring Cloud 初识Spring Cloud与微服务 在传统的软件架构中,我们通常采用的是单体应用来构建一个系统,一个单体应用糅合了各种业务模块.起初在业务规模不是很大的情况下,对于单体应用 ...
- OGG复制进程延迟高,优化方法二(存在索引),SQL选择不好的索引
https://www.cnblogs.com/lvcha001/p/13469500.html 接前序,本次场景中有索引,但是OGG复制进程使用了低效率的索引? 类似SQL使用低效索引,如何让Or ...
- [CSP-S2019]树上的数 题解
CSP-S2 2019 D1T3 考场上写了2h还是爆零……思维题还是写不来啊 思路分析 最开始可以想到最简单的贪心,从小到大枚举每个数字将其移动到最小的节点.但是通过分析样例后可以发现,一个数字在移 ...
- python官网打不开
这可能是因为该站点使用过期的或不安全的 TLS 安全设置. 解决:依次打开IE的Internet选项.高级,往下拉,找到安全模块,勾上四个使用:使用SSL 3.0.使用TLS 1.0.使用TLS 1. ...