WSGI剖析
在一个 HTTP 请求到达服务器时, 服务器接收并调用 web 应用程序解析请求, 产生响应数据并返回给服务器. 这里涉及了两个方面的东西: 服务器(server)和应用程序(application). 势必要有一个合约要求服务器和应用程序都去遵守, 如此按照此合约开发的无论是服务器还是应用程序都会具有较大的普遍性. 而这就好像在计算机通信的早期, 各大公司都有属于自己的通信协议, 如此只会让市场杂乱无章, 宁愿只要一种通信协议.而针对 python 的合约是 WSGI(Python Web Server Gateway Interface).
一、WSGI
WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。
WSGI 的官方定义是,the Python Web Server Gateway Interface。从名字就可以看出来,这东西是一个Gateway,也就是网关。网关的作用就是在协议之间进行转换。
- 也就是说,WSGI就像是一座桥梁,一边连着web服务器,另一边连着用户的应用。WSGI将web组件分为三部分:WSGI 的作用如图所示:
WSGI作用
WSGI有两方:“服务器”或“网关”一方,以及“应用程序”或“应用框架”一方。服务方调用应用方,提供环境信息,以及一个回调函数(提供给应用程序用来将消息头传递给服务器方),并接收Web内容作为返回值。
WSGI将Web组件分成了三类:Web 服务器(WSGI Server)、Web中间件(WSGI Middleware)与Web应用程序(WSGI Application)。
WSGI server所做的工作仅仅是将从客户端收到的请求传递给WSGI application,然后将WSGI application的返回值作为响应传给客户端。WSGI applications 可以是栈式的,这个栈的中间部分叫做中间件
,两端是必须要实现的application和server。
WSGI application接口应该实现为一个可调用对象,例如函数、方法、类、含__call__
方法的实例。这个可调用对象可以接收2个参数:
- 一个字典,该字典可以包含了客户端请求的信息以及其他信息,可以认为是请求上下文,一般叫做environment(编码中多简写为environ、env);
- 一个用于发送HTTP响应状态(HTTP status )、响应头(HTTP headers)的回调函数。
同时,可调用对象的返回值是响应正文(response body),响应正文是可迭代的、并包含了多个字符串。
Web Server接收HTTP请求,封装一系列环境变量,按照WSGI接口标准调用注册的WSGI Application,最后将响应返回给客户端
WSGI Application结构如下:
def application (environ, start_response): response_body = 'Request method: %s' % environ['REQUEST_METHOD'] # HTTP响应状态
status = '200 OK' # HTTP响应头,注意格式
response_headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))
] # 将响应状态和响应头交给WSGI server
start_response(status, response_headers) # 返回响应正文
return [response_body]
下面的程序可以将environment字典的内容返回给客户端:
# -*- coding: utf-8 -*- from wsgiref.simple_server import make_server def application (environ, start_response): response_body = [
'%s: %s' % (key, value) for key, value in sorted(environ.items())
]
response_body = '\n'.join(response_body) # 由于下面将Content-Type设置为text/plain,所以`\n`在浏览器中会起到换行的作用 status = '200 OK'
response_headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers) return [response_body] # 实例化WSGI server
httpd = make_server (
'127.0.0.1',
8051, # port
application # WSGI application,此处就是一个函数
) # handle_request函数只能处理一次请求,之后就在控制台`print 'end'`了
httpd.handle_request()
MiddleWare:
WSGI 的宽松耦合的特性,我们可以轻松的在 Application 和 Server 之前插入任何的中间插件,在不需要改动 Server 和 Application 的前提下,实现一些特殊功能。
class Router(): def __init__(self):
self.path_info = {} def route(self, environ, start_response):
application = self.path_info[environ['PATH_INFO']]
return application(environ, start_response) def __call__(self, path):
def wrapper(application):
self.path_info[path] = application
return wrapper """ The above is the middleware""" router = Router() @router('/world')
def world(environ, start_response):
status = '200 OK'
output = 'World!'start_response(status, response_headers)
return [output] @router('/hello')
def hello(environ, start_response):
status = '200 OK'
output = 'Hello'
response_headers = [('Content-type', 'text/plain'), ('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
作为 Application 时,我们用 Router 实例化一个对象。然后对 “ PATH-APP “ 进行注册,根据不同的 PATH,我们要进一步选择哪个 App。接着,就是把 router.route() 喂给 Server ,作为 Application 侧的可调用实体。有请求到来时,根据已经注册的 “PATH-APP” 对选择应用并执行。
- Server 端类似,我们要先实例化并完成注册。然后,比如,拿我们上一小节实现的 WSGI 容器为例,我们需要修改 result = router.route(environ, start_response),同样完成了router的功能。
下面是另外一个,实现了 postprocessor 的一个例子,在 Application 返回的 HTTP Header 里面再加一个 Header。
def myapp(environ, start_response):
response_headers = [('content-type', 'text/plain')]
start_response('200 OK', response_headers)
return ['Check the headers!'] class Middleware:
def __init__(self, app):
self.wrapped_app = app def __call__(self, environ, start_response):
def custom_start_response(status, headers, exc_info=None):
headers.append(('X-A-SIMPLE-TOKEN', ""))
return start_response(status, headers, exc_info)
return self.wrapped_app(environ, custom_start_response) app = Middleware(myapp)
Web应用的本质:
1)、浏览器发送HTTP请求
2)、服务器接收到请求,生成HTML文档
3)、服务器把HTML文档作为HTTP响应的Body发送给浏览器
4)、浏览器收到HTTP响应,从HTTP Body取出HTML文档进行显示
接受HTTP请求、解析HTTP请求、发送HTTP响应都是重复的苦力活,如果我们自己来写这些底层代码,还没开始写HTML,先要花半把个月研读HTTP规范。所以底层的代码应该由专门的服务器软件实现,我们用Python专注于生成HTML文档。
因为我们不想要接触TCP连接、HTTP原始请求和响应格式。所以需要一个统一的接口,专心用python编写Web业务。
这个接口就是 WSGI:(Web 服务器网关接口)。
二、WSGI接口
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。
def applicaion(environ,start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'hello world']
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
environ:一个包含所有HTTP请求信息的
dict
对象(后面详细介绍environ是什么东西,有哪些内容);start_response:一个发送HTTP响应的函数。
三、python-wsgiref源码分析
simple_server.py
def make_server(
host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
"""Create a new WSGI server listening on `host` and `port` for `app`"""
server = server_class((host, port), handler_class)
# WSGIServer((host, port), handler_class)
server.set_app(app)
return server
监听在本地的端口上,接受来自客户端的请求,通过 WSGIServer 和 WSGIRequestHandler 处理后,把请求交给程序的的可调用对象 app,然后返回 app 的结果给客户端。
这里有两个重要的类:WSGIServer, WSGIRequestHandler
先来看看WSGIServer源码:
class WSGIServer(HTTPServer):
"""BaseHTTPServer that implements the Python WSGI protocol""" application = None def server_bind(self):
"""Override server_bind to store the server name."""
HTTPServer.server_bind(self)
self.setup_environ() def setup_environ(self):
# Set up base environment
env = self.base_environ = {}
env['SERVER_NAME'] = self.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['SERVER_PORT'] = str(self.server_port)
env['REMOTE_HOST']=''
env['CONTENT_LENGTH']=''
env['SCRIPT_NAME'] = '' def get_app(self):
return self.application def set_app(self,application):
self.application = application
WSGIServer 在原来的 HTTPServer 上面封装了一层,在原来的 HTTPServer 的基础上又额外做了下面的事情:
- 覆写原来的 server_bind 函数,添加初始化 environ 变量的动作
- 添加了处理满足 wsgi 的 app 函数:set_app 和 get_app
另外,WSGIRequestHandler
class WSGIRequestHandler(BaseHTTPRequestHandler): server_version = "WSGIServer/" + __version__ def get_environ(self):
env = self.server.base_environ.copy()
env['SERVER_PROTOCOL'] = self.request_version
env['REQUEST_METHOD'] = self.command
if '?' in self.path:
path,query = self.path.split('?',1)
else:
path,query = self.path,'' env['PATH_INFO'] = urllib.unquote(path)
env['QUERY_STRING'] = query host = self.address_string()
if host != self.client_address[0]:
env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0] if self.headers.typeheader is None:
env['CONTENT_TYPE'] = self.headers.type
else:
env['CONTENT_TYPE'] = self.headers.typeheader length = self.headers.getheader('content-length')
if length:
env['CONTENT_LENGTH'] = length for h in self.headers.headers:
k,v = h.split(':',1)
k=k.replace('-','_').upper(); v=v.strip()
if k in env:
continue # skip content length, type,etc.
if 'HTTP_'+k in env:
env['HTTP_'+k] += ','+v # comma-separate multiple headers
else:
env['HTTP_'+k] = v
return env def get_stderr(self):
return sys.stderr def handle(self):
"""Handle a single HTTP request""" self.raw_requestline = self.rfile.readline()
if not self.parse_request(): # An error code has been sent, just exit
return handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ()) # 注意,这里实例化了ServerHandler
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())
这个类处理客户端的 HTTP 请求,它也是在原来处理 http 请求的BaseHTTPRequestHandler 类上添加了 wsgi 规范相关的内容。
- get_environ:解析environ变量
- handle: 解析验证客户端的http的request是否合法,把封装的环境变量交给 ServerHandler,然后由 ServerHandler 调用 wsgi app。
我们之前定义的app,恰恰就是传给run方法,通过run方法的包装,实现wsgi协议的通信。
handle.py
主要是wsgi server 的处理过程,定义 start_response、调用 wsgi app 、处理set_content_length等等。
该文件下存在五个类:
ServerHandler
class ServerHandler(SimpleHandler): # 该类继承关系:ServerHandler---->SimpleHandler------>BaseHandler server_software = software_version def close(self):
try:
self.request_handler.log_request(
self.status.split(' ',1)[0], self.bytes_sent
)
finally:
SimpleHandler.close(self)
而BaseHandler主要用于操作WSGI app。其类下面定义的run:
def run(self, application):
"""Invoke the application"""
# Note to self: don't move the close()! Asynchronous servers shouldn't
# call close() from finish_response(), so if you close() anywhere but
# the double-error branch here, you'll break asynchronous servers by
# prematurely closing. Async servers must return from 'run()' without
# closing if there might still be output to iterate over.
try:
self.setup_environ() //获取环境变量
self.result = application(self.environ, self.start_response) //传递进来的app
self.finish_response()
except:
try:
self.handle_error()
except:
# If we get an error handling an error, just give up already!
self.close()
raise # ...and let the actual server figure it out.
run方法最重要的就是调用自定义的wsgi app,并把在finish_reponse方法中把结果send给客户端。
下面来看看finish_reponse方法:
def finish_response(self):
if not self.result_is_file() or not self.sendfile():
for data in self.result: //遍历self.result里面的数据
self.write(data)
self.finish_content()
self.close()
self.result = application(self.environ, self.start_response) //也就是上面run方法里面的self.result
def write(self, data):
"""'write()' callable as specified by PEP 333""" assert type(data) is StringType,"write() argument must be string" if not self.status:
raise AssertionError("write() before start_response()") elif not self.headers_sent:
# Before the first output, send the stored headers
self.bytes_sent = len(data) # make sure we know content-length
self.send_headers()
else:
self.bytes_sent += len(data) # XXX check Content-Length and truncate if too many bytes written?
self._write(data)
self._flush()
Write---发送数据长度
def finish_content(self):
"""Ensure headers and content have both been sent"""
if not self.headers_sent:
self.headers['Content-Length'] = ""
self.send_headers() //将头信息发送出去
else:
pass
finish_response返回http的body是一方面,其中还需要返回http的headers。这个操作分别在wirte方法和finish_content中,它们都调用了send_headers方法,send_headers方法由通过调用send_preamble构造header数据,并最终通过_write 方法写入到缓冲可写文件中。完成服务器对客户端的响应。
其中send_header()方法如下:
def send_headers(self):
"""Transmit headers to the client, via self._write()"""
self.cleanup_headers() //统计头长度
self.headers_sent = True //修改头状态
if not self.origin_server or self.client_is_modern():
self.send_preamble() //version/status/date/server 等信息写入缓冲文件
self._write(str(self.headers)) //将头信息写入缓冲文件
def send_preamble(self):
"""Transmit version/status/date/server, via self._write()"""
if self.origin_server:
if self.client_is_modern():
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status)) //写入协议和状态
if 'Date' not in self.headers: //写入日期信息
self._write(
'Date: %s\r\n' % format_date_time(time.time())
)
if self.server_software and 'Server' not in self.headers:
self._write('Server: %s\r\n' % self.server_software)
else:
self._write('Status: %s\r\n' % self.status)
四、一条Http请求的旅程
服务器端启动服务,等到客户端输入 http://localhost:8000/
命令,摁下回车键,看到终端上的输出,整个过程中,wsgi 的服务器端发生了什么呢?
- 服务器程序创建 socket,并监听在特定的端口,等待客户端的连接
- 客户端发送 http 请求
- socket server 读取请求的数据,交给 http server
- http server 根据 http 的规范解析请求,然后把请求交给 WSGIServer
- WSGIServer 把客户端的信息存放在 environ 变量里,然后交给绑定的 handler 处理请求
- HTTPHandler 解析请求,把 method、path 等放在 environ,然后 WSGIRequestHandler 把服务器端的信息也放到 environ 里
- WSGIRequestHandler 调用绑定的 wsgi ServerHandler,把上面包含了服务器信息,客户端信息,本次请求信息得 environ 传递过去
- wsgi ServerHandler 调用注册的 wsgi app,把 environ 和 start_response 传递过去
- wsgi app 将reponse header、status、body 回传给 wsgi handler
- 然后 handler 逐层传递,最后把这些信息通过 socket 发送到客户端
- 客户端的程序接到应答,解析应答,并把结果打印出来。
WSGI剖析的更多相关文章
- 01 flask源码剖析之werkzurg 了解wsgi
01 werkzurg了解wsgi 目录 01 werkzurg了解wsgi 1. wsgi 2. flask之werkzurg源码流程 3. 总结 1. wsgi django和flask内部都没有 ...
- django和apache交互的wsgi分析
很多django程序员会用django进行web程序的开发,会用django的命令行命令进行程序的调试,但不会将simpleserver换成apache的配置,也有很多django程序员知道怎么用ws ...
- 深入剖析Nginx一点小笔记
前几天在图书馆看书,恰好看到这本<深入剖析nginx>,花了快一周的时间看完了这本书,写点笔记心得便于以后复习. 以前对nginx的认识就只是停留在一个反向代理服务器上.百度了一下ngin ...
- Django 中的 WSGI
Django 源码小剖: Django 中的 WSGI 2013-09-06 22:31 by 捣乱小子, 334 阅读, 0 评论, 收藏, 编辑 Django 其内部已经自带了一个方便本地测试的小 ...
- Django 源码小剖: 初探 WSGI
Django 源码小剖: 初探 WSGI python 作为一种脚本语言, 已经逐渐大量用于 web 后台开发中, 而基于 python 的 web 应用程序框架也越来越多, Bottle, Djan ...
- Flask核心机制--上下文源码剖析
一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...
- bottle框架剖析
bottle框架剖析 使用 源码分析 一.使用 大致有以下几部分 quick start request routing generate contents request Data template ...
- Django----djagorest-framwork源码剖析
restful(表者征状态转移,面向资源编程)------------------------------------------->约定 从资源的角度审视整个网络,将分布在网络中某个节点的资源 ...
- 04: 打开tornado源码剖析处理过程
目录:Tornado其他篇 01: tornado基础篇 02: tornado进阶篇 03: 自定义异步非阻塞tornado框架 04: 打开tornado源码剖析处理过程 目录: 1.1 torn ...
随机推荐
- [转]程序进行性能分析工具gprof使用入门
性能分析工具 软件的性能是软件质量的重要考察点,不论是在线服务程序还是离线程序,甚至是终端应用,性能都是用户体验的关键.这里说的性能重大的范畴来讲包括了性能和稳定性两个方面,我们在做软件测试的时候也是 ...
- CentOS7最小化安装之后无法联网以及无法使用ifconfig以及无法使用yum安装软件
无法联网解决办法,CentOS-7默认网卡未激活,进入BOOS设置修改,或者直接修改配置文件,然后重启服务 1 修改网卡配置文件: 激活: 2 重启服务即可: service network rest ...
- 5 - django-csrf-session&cookie
目录 1 CSRF跨站请求伪造 1.1 CSRF攻击介绍及防御 1.2 防御CSRF攻击 1.2.1 验证 HTTP Referer 字段 1.2.2 在请求地址中添加 token 并验证 1.2.3 ...
- YUV422(UYVY)转RGB565源代码及其讲解.md
目录 前言 源码 代码分析 YUV三个分量的关系 循环遍历 结束语 前言 使用zmm220核心板,IFACE102版本的内核等,4300型号的LCD,XC7011_SC1145摄像头,亲测有效. 本文 ...
- 05 Diagnostics 诊断
Diagnostics 诊断 Introduction 介绍 Profiling 分析 Tracing 跟踪 Debugging 调试 Runtime statistics and events 运行 ...
- mac 下安装pip
pip是常用的Python包管理工具,类似于Java的maven.用python的同学,都离不开pip. 在新mac中想用home-brew安装pip时,遇到了一些小问题: bogon:~ wangl ...
- SQL语句资料
--语 句 功 能 --数据操作 SELECT --从数据库表中检索数据行和列 INSERT --向数据库表添加新数据行 DELETE --从数据库表中删除数据行 UPDATE --更新数据 ...
- 最邻近规则分类KNN算法
例子: 求未知电影属于什么类型: 算法介绍: 步骤: 为了判断未知实例的类别,以所有已知类别的实例作为参照 选择参数K 计算未知实例与所有已知实例的距离 选择最近K个已 ...
- 从exp入手分析漏洞
分析poc和分析exp有一些不一样,因为exp是人为构造后的东西,它会执行一段自定的shellcode.结果是根本不会触发异常或者异常在离触发点十万八千里的地方.这样分析poc的技巧就用不上了(因为无 ...
- Kubernetes监控:部署Heapster、InfluxDB和Grafana
本节内容: Kubernetes 监控方案 Heapster.InfluxDB和Grafana介绍 安装配置Heapster.InfluxDB和Grafana 访问 grafana 访问 influx ...