wsgiref 源码解析
Web Server Gateway Interface(wsgi),即Web服务器网关接口,是Web服务器软件和用Python编写的Web应用程序之间的标准接口。
想了解更多关于WSGI请前往: https://www.cnblogs.com/delav/p/9571865.html
wsgiref是wsgi规范的参考实现。
wsgiref目录:Python27\Lib\wsgiref
共有五个文件:
handlers.py #负责wsgi程序的处理 headers.py #处理HTTP响应头 simple_server.py #实现wsgi协议的简单服务器 util.py # 一些wsgi相关的其他处理 validate.py #检查符合wsgi规范的中间件
首先实现一个简单例子
simple.py # coding: utf-8
from wsgiref.simple_server import make_server
def app(environ, start_respponse):
status = "200 OK"
header = [('Content-type', 'text/html')]
start_respponse(status, header)
return 'Hello World' httpd = make_server('', 8080, app)
print "Sever on port 8080....."
httpd.serve_forever()
运行simple.py,打开浏览器输入 http://127.0.0.1:8080
页面会显示 Hello World
simple_sever.py源码
"""BaseHTTPServer that implements the Python WSGI protocol (PEP 333, rev 1.21) This is both an example of how WSGI can be implemented, and a basis for running
simple web applications on a local machine, such as might be done when testing
or debugging an application. It has not been reviewed for security issues,
however, and we strongly recommend that you use a "real" web server for
production use. For example usage, see the 'if __name__=="__main__"' block at the end of the
module. See also the BaseHTTPServer module docs for other API information.
""" from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import urllib, sys
from wsgiref.handlers import SimpleHandler __version__ = "0.1"
__all__ = ['WSGIServer', 'WSGIRequestHandler', 'demo_app', 'make_server'] server_version = "WSGIServer/" + __version__
sys_version = "Python/" + sys.version.split()[0]
software_version = server_version + ' ' + sys_version class ServerHandler(SimpleHandler): 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) 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 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(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return 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()
)
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app()) def demo_app(environ,start_response):
from StringIO import StringIO
stdout = StringIO()
print >>stdout, "Hello world!"
print >>stdout
h = environ.items(); h.sort()
for k,v in h:
print >>stdout, k,'=', repr(v)
start_response("200 OK", [('Content-Type','text/plain')])
return [stdout.getvalue()] 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)
server.set_app(app)
return server if __name__ == '__main__':
httpd = make_server('', 8000, demo_app)
sa = httpd.socket.getsockname()
print "Serving HTTP on", sa[0], "port", sa[1], "..."
import webbrowser
webbrowser.open('http://localhost:8000/xyz?abc')
httpd.handle_request() # serve one request, then exit
httpd.server_close()
服务是通过 make_sever 这个函数来启动的
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)
server.set_app(app)
return server
调用启动make_server后,会监听host主机(为空表示本地主机)的port端口,当收到客户端的请求后,先经过WSGIServer和WSGIRequestHandler的处理,再把处理后的请求发送给app应用程序,app返回请求的结果。
WSDIServer类的内容如下:
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
WSDIServer继承HTTPServer,(使用pycharm不断追踪,会找到最终的TCPServer原始的绑定套接字方法),在HTTPServer的基础上添加符合wsgi规范的内容。
这里的server_bind方法覆盖了原来HTTPServer的server_bind方法,增加setup_environ的功能,setup_environ方法是用来初始化environ变量,是一个字典。
还有get_app和set_app方法,添加了符合wsgi的application
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(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return
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()
)
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())
WSGIRequestHandler继承BaseHTTPRequestHandler类,BaseHTTPRequestHandler是对客户端的请求进行处理,WSGIRequestHandler在这个的基础上添加符合wsgi规范的相关内容。
get_environ方法用来解析environ变量
get_stderr方法作异常处理
handle方法用来处理请求,把封装的environ变量交给 ServerHandler,然后由 ServerHandler 调用app应用程序。
app应用程序必须接受两个参数,一个是environ,另一个是start_response。
在前面实现的例子simple.py的app函数下打印environ(print environ),会输出一个字典,记录着CGI中的环境变量。
而start_response函数是handlers.py文件里面的,源码如下:
def start_response(self, status, headers,exc_info=None):
"""'start_response()' callable as specified by PEP 333""" if exc_info:
try:
if self.headers_sent:
# Re-raise original exception if headers sent
raise exc_info[0], exc_info[1], exc_info[2]
finally:
exc_info = None # avoid dangling circular ref
elif self.headers is not None:
raise AssertionError("Headers already set!") assert type(status) is StringType,"Status must be a string"
assert len(status)>=4,"Status must be at least 4 characters"
assert int(status[:3]),"Status message must begin w/3-digit code"
assert status[3]==" ", "Status message must have a space after code"
if __debug__:
for name,val in headers:
assert type(name) is StringType,"Header names must be strings"
assert type(val) is StringType,"Header values must be strings"
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
self.status = status
self.headers = self.headers_class(headers)
return self.write
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()
start_response接受两个参数status(HTTP状态)和headers(HTTP响应头header),返回write方法,write方法返回的是HTTP响应体body,必须返回一个可调用对象。
把前面的simple.py改一下
simple.py # coding: utf-8
from wsgiref.simple_server import make_server def app(environ, start_respponse):
status = "200 OK"
header = [('Content-type', 'text/html')]
# start_respponse(status, header)
write = start_response(status, header)
write('Delav! ')
return 'Hello World' httpd = make_server('', 8080, app)
print "Sever on port 8080....."
httpd.serve_forever()
再运行打开浏览器127.0.0.1:8080,你会发现输出为 Delav! Hello World
我们在调用app应用程序的时候,会对应用执行结果迭代输出。
app应用程序必须在第一次返回可迭代数据(return "Hello World")之前调用 start_response 方法。
这是因为可迭代数据是返回数据的body部分,在它返回之前,需要使用start_response返回 response_headers 数据。
一个HTTP请求过程:
- 服务器程序创建 socket,并监听8080端口,等待客户端的连接
- 客户端发送 http 请求(浏览器访问127.0.0.1:8080)
- 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 发送到客户端
- 客户端的程序接到应答,解析应答,并把结果打印出来
wsgiref 源码解析的更多相关文章
- wsgiref源码解析
wsgiref是PEP 333定义的wsgi规范的范例实现,里面的功能包括了: wsgi的环境变量 应答头部的处理 实现简单的HTTP服务器 简单的对程序端和服务器端校验函数 我们先看一个简单的代码实 ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
- jQuery2.x源码解析(缓存篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...
- Spring IoC源码解析——Bean的创建和初始化
Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器 ...
- jQuery2.x源码解析(构建篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...
- jQuery2.x源码解析(设计篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...
随机推荐
- java 二叉树的创建 遍历
本来说复习一下BFS和DFS,辗转就来到了二叉树...本文包括二叉树的创建和遍历 概念 数据:1 2 3 4 5 6 7生成一颗二叉树 上面的数是数据,不是位置,要区别一下数据和位置 红色的代表位置, ...
- Linux命令应用大词典-第6章 文件处理
6.1 sort:对文件中的数据进行排序 6.2 uniq:将重复行从输出文件中删除 6.3 cut:从文件每行中输出选定的字节.字符或字段 6.4 comm:逐行比较两个已经排序的文件 6.5 di ...
- 第五模块:WEB开发基础 第3章·BootStrap&JQuery开发
01-JQuery介绍 02-jQuery文件引入和加载的区别 03-jQuery的基础选择器 04-jQuery的层级选择器 05-jQuery的基本过滤选择器 06-jQuery的属性选择器 07 ...
- win 下通过dos命令格式化磁盘
该命令可以解决好多问题,比如: 1.u盘作为启动后,如何恢复成正常的u盘 1.win + r ->cmd 进入dos模式 2.输入diskpart后回车,点击确定,进入diskpart命令的交互 ...
- TW实习日记:第26天
这周组长休年假去了,并且之前主要负责的项目也已经上线了,可以说没那么忙了,手头就一个协助别的组做的移动端项目.可是这个项目特别坑,由于网端是9年前的项目,导致后台的接口有非常多的问题,并且入参多得令人 ...
- bson文件的切分
描述 最近遇到问题需要将较大的bson文件(MongoDB导出的二进制json文件)按文档(记录)进行切分,网上这方面的资料实在太少,弄了一天多终于达到了基本要求(还不知道有没有BUG) 代码 pac ...
- typescript 学习记录
类型判断: typeJudge() { //typeof 用来判断变量类型 var s: string = 'egret'; var isString: boolean = typeof s === ...
- Flex 布局浅析
除了 CSS 中传统的布局系统之外,CSS3还提供了一个新布局系统.在这个新的框模型中,框的子代采用水平或垂直布局,而且可将未使用的空间分配给特定的子代,或者通过“弹性”分配给应展开的子代,在各子代间 ...
- day-17 L1和L2正则化的tensorflow示例
机器学习中几乎都可以看到损失函数后面会添加一个额外项,常用的额外项一般有两种,一般英文称作ℓ1-norm和ℓ2-norm,中文称作L1正则化和L2正则化,或者L1范数和L2范数.L2范数也被称为权重衰 ...
- opencv打开视频文件出错
使用C#调用mingw的so文件,在C++端使用opencv打开视频.这样的项目完成过了一个,第二次做的时候,发现opencv打开视频文件出错. 首先怀疑是opencv的opencv_ffmpeg24 ...