Python WSGI学习总结

1.WHAT?(什么是wsgi,它用来做什么?)

WSGI的全称是Web Server Gateway Interface,翻译过来就是Web服务器网关接口。具体的来说,WSGI是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。WSGI一开始是在PEP-0333中定义的,最新版本是在Python的PEP-3333定义的。

那么,它实现了那些功能,用来做什么?

  • 操作 wsgi 的环境变量
  • 应答头部的处理
  • 实现简单的 HTTP server
  • 简单的对程序端和服务器端校验函数

WSGI 是服务器程序与应用程序的一个约定,它规定了双方各自需要实现什么接口,提供什么功能,以便二者能够配合使用

WSGI 不能规定的太复杂,否则对已有的服务器来说,实现起来会困难,不利于WSGI的普及。同时WSGI也不能规定的太多,例如cookie处理就没有在WSGI中规定,这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用,而不是成为一个Web框架的标准。

另一方面,WSGI需要使得middleware(是中间件么?)易于实现。middleware处于服务器程序与应用程序之间,对服务器程序来说,它相当于应用程序,对应用程序来说,它相当于服务器程序。这样,对用户请求的处理,可以变成多个 middleware 叠加在一起,每个middleware实现不同的功能。请求从服务器来的时候,依次通过middleware,响应从应用程序返回的时候,反向通过层层middleware。我们可以方便地添加,替换middleware,以便对用户请求作出不同的处理。

WSGI协议分为两部分,分别为WSGI Server和WSGI Application,WSGI Server负责接受客户端请求、解析请求、并按照协议规范将请求转发给WSGI Application,同时负责接受WSGI Application的响应并发送给客户端;WSGI Application负责接受由WSGI Server发送过来的请求,实现业务处理逻辑,并将标准的响应发回给WSGI Server:

具体来说,WSGI Server解析客户端由socket发送过来的http数据包,将请求的http version、method、host、path等包装成environ参数,并提供start_response回调函数,并将environ和

start_response函数作为参数传递给由WSGI Application提供的callable对象,获取callable对象的返回结果,处理后依照http协议传递给客户端,完成一次请求。

它更像是一个客户端(浏览器)和服务器(服务器后端,具体处理请求,返回用户请求数据)之间的桥梁,在这个桥梁使用的过程中可以给他加一些其他自己需要的基础设施(插件)

WSGI 内容概要

WSGI主要是对应用程序与服务器端的一些规定,所以,它的主要内容就分为两个部分。

应用程序

WSGI规定:

1. 应用程序需要是一个可调用的对象

在Python中:

  • 可以是函数
  • 可以是一个实例,它的类实现了__call__方法
  • 可以是一个类,这时候,用这个类生成实例的过程就相当于调用这个类

同时,WSGI规定:

2. 可调用对象接收两个参数

这样,如果这个对象是函数的话,它看起来要是这个样子:

# callable function
def application(environ, start_response):
    pass

如果这个对象是一个类的话,它看起来是这个样子:

# callable class
class Application:
    def __init__(self, environ, start_response):
        pass

如果这个对象是一个类的实例,那么,这个类看起来是这个样子:

# callable object
class ApplicationObj:
    def __call__(self, environ, start_response):
        pass

最后,WSGI还规定:

3.可调用对象要返回一个值,这个值是可迭代的。

middleware

另外,有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。

middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。

其实无论是服务器程序,middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。

2.HOW?(它是怎么实现所提供的功能的,我们在项目中怎么用?)

首先结合概念看这张图,

Python WSGI学习总结_第1张图片

详细分析:

wsgi组成及作用:

Python WSGI学习总结_第2张图片

wsgiref

|-- handlers.py # 核心代码,负责 wsgi 程序的处理

|-- headers.py # 头部处理的代码

|-- __init__.py #

|-- simple_server.py # 简单的 wsgi HTTP 服务器实现

|-- util.py # 帮助函数

`-- validate.py # wsgi 格式检查和校验

如何使用:

之前说到wsgi是对socket的封装,那么下面我们写一个最小的web服务器来看下wsgi怎么用,

首先我们不用wsgi,而是直接使用socket编程来实现web请求的处理

import socket

def req(client):
    “”“
    用户请求处理,也就是我们常说的application(应用)
    ”“”
	buf = client.recv(1024)
	print(buf)
	msg = "HTTP/1.1 200 OK\r\n\r\n"
	client.send(('%s' % msg).encode())
	msg = "Hello, World! xxx this is socket with no wsgi!!!"
	client.send(('%s' % msg).encode())

def main():
    “”“
    web服务器,实际上是socket  bind本地回环等待用户发送请求到8000端口处理
    ”“”
	ip_port = ("localhost", 8000)
	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	sock.bind(ip_port)
	sock.listen(5)

	while True:
		conn, addr = sock.accept()
		req(conn)
		conn.close()

if __name__ == "__main__":

	main()

运行如上代码,然后在浏览器输入 localhost:8000或者127.0.0.1:8000就会得到如下打印:

Hello, World! xxx this is socket with no wsgi!!!

再来看看wsgi的代码,对比socket会发现,过程一模一样!

from wsgiref.simple_server import make_server

def wsgireq(env, res):
	print(env)
	res("200 OK",[("Content-Type","text/html")])
	body = "

Hello World!, this is the wsgi response!

" return [body.encode("utf-8")] if __name__ == "__main__": httpd = make_server("127.0.0.1", 8000, wsgireq) print("Serving http on port 8000") httpd.serve_forever()

一样的方法,浏览器请求 localhost:8000或者127.0.0.1:8000就会得到如下打印:

Hello World!, this is the wsgi response!

区别在于:

wsgi将socket固定服务器等待请求的代码封装到make_server()方法中,代码整体看起来更简洁明了。将单条通信的msg封装成为整体更适用于web编程的body体,body体可以返回整体的HTML静态网页,当我们实现了所有请求处理后就会得到一个网站的web服务器!

一条 HTTP 请求的旅程:

服务器端启动服务,等到客户端输入 curl -i 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实现桥梁作用的原理:

simple_server.py

我们先看一下 make_server 是怎么启动一个 wsgi 服务器的:

def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
server = server_class((host, port), handler_class)
    server.set_app(app)
    return server

这个函数做的事情就是:监听在本地的端口上,接受来自客户端的请求,
通过 WSGIServer 和 WSGIRequestHandler 处理后,把请求交给程序的
可调用对象 app,然后返回 app 的结果给客户端。

对照我们写的服务器代码:
from wsgiref.simple_server import make_server

def wsgireq(env, res):
	print(env)
	res("200 OK",[("Content-Type","text/html")])
	body = "

Hello World!, this is the wsgi response!

" return [body.encode("utf-8")] if __name__ == "__main__": httpd = make_server("127.0.0.1", 8000, wsgireq) httpd.serve_forever() 在这里我们传入的参数是ip port 和我们写好的app wsgireq,然后使用默认的 server_class=WSGIServer, handler_class=WSGIRequestHandler类来启动 我们的服务器。然后等待处理客户端的请求。 为了深入了解,我们后面分析下这两个默认类做了什么!

WSGIServer 和 WSGIRequestHandler。下面分析一下它们的代码和执行的功能:

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 变量的动作
environ如下:
{'ALLUSERSPROFILE': 'C:\\ProgramData', 
'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming', 
'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files', 
'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 
'DRIVERDATA': 'C:\\Windows\\System32\\Drivers\\DriverData',等等  很长的......  
environ参数是一个Python的字典,里面存放了所有和客户端相关的信息,这样application
对象就能知道客户端请求的资源是什么,请求中带了什么数据等。environ字典包含了一些CGI规范
要求的数据,以及WSGI规范新增的数据,还可能包含一些操作系统的环境变量以及Web服务器相关的
环境变量。我们来看一些environ中常用的成员:
首先是CGI规范中要求的变量:

REQUEST_METHOD: 请求方法,是个字符串,'GET', 'POST'等
SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据
path的一部分来决定请求由哪个virtual host处理
PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分
QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容
CONTENT_TYPE: HTTP headers中的content-type内容
CONTENT_LENGTH: HTTP headers中的content-length内容
SERVER_NAME和SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起
来可以得到完整的URL路径
SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
HTTP_: 和HTTP请求中的headers对应。

WSGI规范中还要求environ包含下列成员:

wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0
wsgi.url_scheme:http或者https
wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body
wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里
wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True
wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True
wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True

上面列出的这些内容已经包括了客户端请求的所有数据,足够application对象处理客户端请求了。


添加了处理满足 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()
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())


这个类从名字就能知道它的功能——处理客户端的 HTTP 请求,它也是在原来处理 http 请求的
BaseHTTPRequestHandler 类上添加了 wsgi 规范相关的内容。

get_environ: 解析 environ 变量
handle: 处理请求,把封装的环境变量交给 ServerHandler,然后由 ServerHandler 调用 
wsgi app,ServerHandler 类会在下面介绍。
 
handler.py
 
这个文件主要是 wsgi server 的处理过程,定义 start_response、调用 wsgi app 、处理
 content-length 等等。
 

可以参考这篇文章里的 wsgi server 的简单实现。

WSGI的实现和部署

要使用WSGI,需要分别实现server角色和application角色。

Application端的实现一般是由Python的各种框架来实现的,比如Django, web.py等,一般开发者不需要关心WSGI的实现,框架会会提供接口让开发者获取HTTP请求的内容以及发送HTTP响应。

Server端的实现会比较复杂一点,这个主要是因为软件架构的原因。一般常用的Web服务器,如Apache和nginx,都不会内置WSGI的支持,而是通过扩展来完成。比如Apache服务器,会通过扩展模块mod_wsgi来支持WSGI。Apache和mod_wsgi之间通过程序内部接口传递信息,mod_wsgi会实现WSGI的server端、进程管理以及对application的调用。Nginx上一般是用proxy的方式,用nginx的协议将请求封装好,发送给应用服务器,比如uWSGI,应用服务器会实现WSGI的服务端、进程管理以及对application的调用。

参考:

https://www.jianshu.com/p/c66d3adeaaed

https://www.cnblogs.com/wangkun122/articles/8493778.html

你可能感兴趣的:(python)