在一个 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 的服务器端发生了什么呢?

  1. 服务器程序创建 socket,并监听在特定的端口,等待客户端的连接
  2. 客户端发送 http 请求
  3. socket server 读取请求的数据,交给 http server
  4. http server 根据 http 的规范解析请求,然后把请求交给 WSGIServer
  5. WSGIServer 把客户端的信息存放在 environ 变量里,然后交给绑定的 handler 处理请求
  6. HTTPHandler 解析请求,把 method、path 等放在 environ,然后 WSGIRequestHandler 把服务器端的信息也放到 environ 里
  7. WSGIRequestHandler 调用绑定的 wsgi ServerHandler,把上面包含了服务器信息,客户端信息,本次请求信息得 environ 传递过去
  8. wsgi ServerHandler 调用注册的 wsgi app,把 environ 和 start_response 传递过去
  9. wsgi app 将reponse header、status、body 回传给 wsgi handler
  10. 然后 handler 逐层传递,最后把这些信息通过 socket 发送到客户端
  11. 客户端的程序接到应答,解析应答,并把结果打印出来。

WSGI剖析的更多相关文章

  1. 01 flask源码剖析之werkzurg 了解wsgi

    01 werkzurg了解wsgi 目录 01 werkzurg了解wsgi 1. wsgi 2. flask之werkzurg源码流程 3. 总结 1. wsgi django和flask内部都没有 ...

  2. django和apache交互的wsgi分析

    很多django程序员会用django进行web程序的开发,会用django的命令行命令进行程序的调试,但不会将simpleserver换成apache的配置,也有很多django程序员知道怎么用ws ...

  3. 深入剖析Nginx一点小笔记

    前几天在图书馆看书,恰好看到这本<深入剖析nginx>,花了快一周的时间看完了这本书,写点笔记心得便于以后复习. 以前对nginx的认识就只是停留在一个反向代理服务器上.百度了一下ngin ...

  4. Django 中的 WSGI

    Django 源码小剖: Django 中的 WSGI 2013-09-06 22:31 by 捣乱小子, 334 阅读, 0 评论, 收藏, 编辑 Django 其内部已经自带了一个方便本地测试的小 ...

  5. Django 源码小剖: 初探 WSGI

    Django 源码小剖: 初探 WSGI python 作为一种脚本语言, 已经逐渐大量用于 web 后台开发中, 而基于 python 的 web 应用程序框架也越来越多, Bottle, Djan ...

  6. Flask核心机制--上下文源码剖析

    一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...

  7. bottle框架剖析

    bottle框架剖析 使用 源码分析 一.使用 大致有以下几部分 quick start request routing generate contents request Data template ...

  8. Django----djagorest-framwork源码剖析

    restful(表者征状态转移,面向资源编程)------------------------------------------->约定 从资源的角度审视整个网络,将分布在网络中某个节点的资源 ...

  9. 04: 打开tornado源码剖析处理过程

    目录:Tornado其他篇 01: tornado基础篇 02: tornado进阶篇 03: 自定义异步非阻塞tornado框架 04: 打开tornado源码剖析处理过程 目录: 1.1 torn ...

随机推荐

  1. Vue学习看这篇就够

    Vue -渐进式JavaScript框架 介绍 vue 中文网 vue github Vue.js 是一套构建用户界面(UI)的渐进式JavaScript框架 库和框架的区别 我们所说的前端框架与库的 ...

  2. D - Doing Homework HDU - 1074 (状压dp)

    题目链接:https://cn.vjudge.net/contest/68966#problem/D 具体思路:我们可以把每个情况都枚举出来,然后用递归的形式求出最终的情况. 比如说 我们要求  10 ...

  3. iframe测试

    iframe类似于frameset,iframe 和 frameset 都用于html页面的框架布局.(都可用于后台管理界面,iframe放于body标签内,frameset不能放在body标签) 1 ...

  4. ubuntu网络连接:Ifupdown(eth0)的连接不能修改或删除

    今天其实遇到了三个问题,一个是ubuntu系统上的网络图标不见了,把网络图标弄出来以后有无法联网,联网成功却变成Ifupdown(eth0)的连接且不能修改或删除. 这里我只记录最后一个问题,前两个问 ...

  5. C# 所生成项目的处理器架构“MSIL”与引用“Oracle.DataAccess, Version=4.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342, processorArchitecture=x86”的处理器架构“x86”不匹配。这种不匹配可能会导致运行时失败。

    这个问题一般都是Oracle.DataAccess的版本不兼容问题造成的. 解决办法: 1.把Oracle.DataAccess.dll文件拿到C盘或D盘的安装文件的地方进行搜索. 2.会出现在pro ...

  6. 自动化测试Robotium获取当前页面的activity,用于判断是否进入这个页面

    一.启动app 二.进入命令行窗口输入 adb shell “dumpsys activity activities | grep mFocusedActivity” 三.断言方法 assertTru ...

  7. Linux下获取和设置IP

    在Linux下获取关于IP和网关的操作:重点是对struct ifreq 的操作. 那么进入目录/usr/include/net/if.h下看查找struct ifreq结构体. /* Interfa ...

  8. tf.nn.embedding_lookup函数

    tf.nn.embedding_lookup(params, ids, partition_strategy='mod', name=None, validate_indices=True, max_ ...

  9. git —— pycharm+git管理/编辑项目

    pycharm+git  管理/编辑项目 一.pycharm中配置github 二.配置git 并不是配置了GitHub就可以的.还需要配置一下Git 前提是本地中已经安装了git 三.把本地项目上传 ...

  10. Spring框架的基本使用(IOC部分)

    Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架. Spring的好处 1.方便解耦,简化开发: Spring就是一个大工厂,专门负责生成Bean,可以将所有对象创建和依赖关 ...