在学习Flask Web开发时遇到了WSGI,那么WSGI是什么呢?WSGI和Flask有什么关系呢?

一、WSGI为什么会出现?

在学习一个东西之前,我们肯定想知道:它为什么会出现?那么,WSGI为什么会出现呢?

我们知道,部署一个web应用,经常需要使用nginx、apache或者IIS等web服务器把web应用跑起来,然后用户在浏览器可以通过URL进行访问。

为了能够让各种web服务器都能支持web应用,所以必须在web应用和web服务器之间有一个统一的规范(协议)。

其实,在PEP 3333中也有提到它的目标:

为了定义了一个“ Web 服务器和 Python Web 应用程序或框架之间”的标准接口,以便提升 Web 应用程序在不同 Web 服务器上的可移植性。

注意:

1、这里的web应用是Python Web应用程序。

2、这里的web服务器是指nginx、apache等。

3、WSGI为什么会出现在WSGI有更加详细的说明。

https://github.com/python/peps/blob/master/pep-0333.txt

二、什么是WSGI

全称Python Web Server Gateway Interface,指定了web服务器和Python web应用或web框架之间的标准接口,以提高web应用在一系列web服务器间的移植性。

具体可查看 官方文档

从上面的介绍看出,WSGI是一种协议或规范:描述web server如何与web application通信的规范,以及webapp怎么处理前端请求。

一个web服务具体流程类:

  1. 用户操作操作浏览器发送请求;
  2. 请求转发至对应的web服务器
  3. web服务器将请求转交给web应用程序,web应用程序处理请求
  4. web应用将请求结果返回给web服务器,由web服务器返回用户响应结果
  5. 浏览器收到响应,向用户展示

WSGI的主要作用是在Web服务器(uwsgi)和Web应用程序(application)承担“翻译官”的角色。对于这一角色可以这样理解:

  1. Web服务器的责任在于监听和接收请求。在处理请求的时候调用WSGI提供的标准化接口,将请求的信息转给WSGI;
  2. WSGI的责任在于“中转”请求和响应信息,还有将HTTP报文转换为WSGI规定的格式。WSGI接收到Web服务器提供的请求信息后可以做一些处理,之后通过标准化接口调用Web应用,并将请求信息传递给Web应用。同时,WSGI还将会处理Web应用返回的响应信息,并通过服务器返回给客户端;
  3. Web应用的责任在于接收请求信息,并且生成响应。

根据以上分析,要实现符合WSGI标准的Web服务,服务器和应用程序的设计就要符合WSGI规范。

有了wsgi这份接口规范,在web开发的过程中,能更加自由的选择服务器端和框架;在服务器端和框架的开发过程能够分离开来,不用过多的考虑双方具体的实现,使得服务器端和框架开发者能够专心自己领域的开发工作。

三、WSGI的实现

因为WSGI其实就是一个协议,根据官方的定义,大致内容如下:

● WSGI application能够调用python对象(函数或者一个带有__call__方法的类。__call__方法有2个参数:第一个参数是WSGI的environ,第二个参数是一个start_response函数。
● application必须使用start_response(status,headers),并且返回值是一个可迭的代序列,序列中的每个对象将标准输出。
● WSGI environ和CGI environ一样,都是一些键值对,要么是提供给server,要么提供给middleware。
● 可以将包装后的middleware添加到你的app中。

一起实现一个简单WSGI服务吧

def application(environ, start_response):status = "200 OK"response_headers = [('Content-Type', 'text/html')]start_response(status, response_headers)path = environ['PATH_INFO'][1:] or 'hello'return [b'<h1> %s </h1>' % path.encode()]

方法 application由 web服务器调用,参数env,start_response 由 web服务器实现并传入。其中,env是一个字典,包含了类似 HTTP_HOST,HOST_USER_AGENT,SERVER_PROTOCO 等环境变量。start_response则是一个方法,该方法接受两个参数,分别是status,response_headers。application方法的主要作用是,设置 http 响应的状态码和 Content-Type 等头部信息,并返回响应的具体结果。

接下来,我们需要一个服务器启动WSGI服务器用来处理验证,使用Python内置的WSGI服务器模块wsgiref,编写server.py:

# coding:utf-8
"""
desc: WSGI服务器实现
"""
from wsgiref.simple_server import make_server
from learn_wsgi.client import applicationdef main():server = make_server('localhost', 8001, hello)print('Serving HTTP on port 8001...')server.serve_forever()if __name__ == '__main__':main()

执行python server.py,浏览器打开"http://localhost:8001/a",即可验证。

上述代码就是一个完整的 WSGI 应用,当一个支持 WSGI 的 web服务器接收到客户端的请求后,便会调用这个 application 方法。WSGI 层并不需要关心env,start_response 这两个变量是如何实现的,就像在 application 里面所做的,直接使用这两个变量即可。

当然,以上只是一个简单的案例,那么在python的Web框架内部是如何遵循WSGI规范的呢?以Flask举例,而这个叫做application的东西,在Flask框架内部的名字,叫做wsgi_app。

四、Werkzeug

都知道Flask是一个web框架,而且Flask是基于werkzeug开发的,那werkzeug是什么呢?

Werkzeug是一个WSGI工具包,他可以作为一个Web框架的底层库。这里稍微说一下, werkzeug 不是一个web服务器,也不是一个web框架,而是一个工具包,官方的介绍说是一个 WSGI 工具包,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response 等等。使用它可以减轻web框架开发工作量。werkzeug也实现了WSGI容器的功能。WSGI容器的作用就是根据web服务器传递而来的参数构建一个让WSGI应用成功执行的环境,例如request,而且还得把WSGI应用上处理好的结果返回给web服务器。此外WSGI容器也叫应用服务器。而且利用python/http/server.py库实现了一个简易的http服务器。因此在调试的时候可以直接使用app.run()把服务器给运行起来。

注: 一般应用服务器都集成了web服务器,主要是为了调试方便,出于性能和稳定性考虑,并不能在生产环境中使用。

WSGI简化了编写Web app的复杂度,使程序员不必关注底层的数据传输而专注于Web本身。框架则基于WSGI进一步抽象,用一个函数处理一个URL。而URL与函数的绑定,称为路由(route),而这些就交给Web框架来做了。Python Flask的路由,是由装饰器实现的

五、Flask的WSGI的实现

有了上面的知识,从最简单的这个flask程序来看WSGI的实现。

  1. 使用app.run()方法来启动flask应用(app.run()代码隐藏着创建一个服务器),app应用本身会作为参数传递到WSGI服务器中。
  2. 在客户端(这里就是浏览器)输入网址(发送一个请求),服务器使用WSGI 中间件来处理这个请求。
  3. WSGI 处理请求对应着wsgi_app(self, environ, start_response)方法,self参数对应着app,即flask程序;environ和 start_response由服务器提供。
  4. wsgi_app()作用就是调用各种请求处理函数来处理请求,然后返回处理结果。即用户输入网址后,看到了网页响应。
from flask import Flaskapp = Flask(__name__)
#生成app实例,传递 __name__参数,__name__ 就是当前模块名字。@app.route("/")
def index():return "hello world"if __name__ == '__main__':app.run(debug=True)

5.1  首先  app.run() 方法开始

看 run() 方法的定义,调用了werkzeug库中的一个 run_simple() 方法,最后启动了 BaseWSGIServer  服务器。 运行 run() 方法是只传递了 debug=True 参数。 看 run()  方法内部:

  1. 第一个 if 语句设置默认host参数值为 127.0.0.1
  2. 第二个 if 语句设置默认port参数值为5000
  3. 第三个 if 语句中传递了debug 参数值为 True
  4. the options to be forwarded to the underlying Werkzeug server. 这里把debug状态传递到底层的Werkzeug server。即use_reloader=True ; use_debugger=True
  5. 最后调用werkzeug库中的一个run_simple()方法。同时,传递了刚刚设置的几个参数
def run(self, host=None, port=None, debug=None, **options):"""Runs the application on a local development server...."""from werkzeug.serving import run_simpleif host is None:host = '127.0.0.1'if port is None:server_name = self.config['SERVER_NAME']if server_name and ':' in server_name:port = int(server_name.rsplit(':', 1)[1])else:port = 5000if debug is not None:self.debug = bool(debug)options.setdefault('use_reloader', self.debug)options.setdefault('use_debugger', self.debug)try:run_simple(host, port, self, **options)finally:self._got_first_request = False

5.2 run_simple() 方法

  1. hostname, port, application 对应着刚才run()方法中传递过来的host, port, self 参数。(这里self 就是Flask实例化了的app)

  2. 同时run()方法中还传递了user_debugger=True;user_loader=True 。剩余的参数使用初始值。

  3. 根据上面 user_loader=True,第一个if语句成立,调用了werkzeug.debug模块中的 DebuggedApplication类来对应用程序包装一次。传入了application参数和use_evalex参数,调用run_simple()方法时设置了use_evalex=True。DebuggedApplication类的简单说明:Enables debugging support for a given application

  4. 第二个if条件语句不成立,不执行之后的代码。

  5. 定义了log_startup函数和inner()函数,使用的时候再看具体实现了什么。

  6. if use_reloader: 成立,会执行之后的代码。最关键的一行代码: run_with_reloader(inner, extra_files, reloader_interval, reloader_type)。调用了run_with_reloader方法,inner作为run_with_reloader方法中的main_func 参数;run_simple()方法设置了extra_files=None ,reloader_interval=1,     reloader_type='auto' 同时作为参数传递到run_with_reloader方法中。

  7. 然后,inner()方法中关键语句:make_server()创建http服务器; server_forever()让服务器不要关闭,一直等待下一个请求。

def run_simple(hostname, port, application, use_reloader=False,use_debugger=False, use_evalex=True,extra_files=None, reloader_interval=1,reloader_type='auto', threaded=False,processes=1, request_handler=None, static_files=None,passthrough_errors=False, ssl_context=None):"""Start a WSGI application. Optional features include a reloader,multithreading and fork support.:param hostname: The host for the .  eg: ``'localhost'``:param port: The port for the server.  eg: ``8080``:param application: the WSGI application to execute...(省略了其余的参数介绍。)"""if use_debugger:from werkzeug.debug import DebuggedApplicationapplication = DebuggedApplication(application, use_evalex)if static_files:from werkzeug.wsgi import SharedDataMiddlewareapplication = SharedDataMiddleware(application, static_files)def log_startup(sock):display_hostname = hostname not in ('', '*') and hostname or 'localhost'if ':' in display_hostname:display_hostname = '[%s]' % display_hostnamequit_msg = '(Press CTRL+C to quit)'port = sock.getsockname()[1]_log('info', ' * Running on %s://%s:%d/ %s',ssl_context is None and 'http' or 'https',display_hostname, port, quit_msg)def inner():try:fd = int(os.environ['WERKZEUG_SERVER_FD'])except (LookupError, ValueError):fd = Nonesrv = make_server(hostname, port, application, threaded,processes, request_handler,passthrough_errors, ssl_context,fd=fd)if fd is None:log_startup(srv.socket)srv.serve_forever()if use_reloader:# If we're not running already in the subprocess that is the# reloader we want to open up a socket early to make sure the# port is actually available.if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':if port == 0 and not can_open_by_fd:raise ValueError('Cannot bind to a random port with enabled ''reloader if the Python interpreter does ''not support socket opening by fd.')# Create and destroy a socket so that any exceptions are# raised before we spawn a separate Python interpreter and# lose this ability.address_family = select_ip_version(hostname, port)s = socket.socket(address_family, socket.SOCK_STREAM)s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)s.bind((hostname, port))if hasattr(s, 'set_inheritable'):s.set_inheritable(True)# If we can open the socket by file descriptor, then we can just# reuse this one and our socket will survive the restarts.if can_open_by_fd:os.environ['WERKZEUG_SERVER_FD'] = str(s.fileno())s.listen(LISTEN_QUEUE)log_startup(s)else:s.close()# Do not use relative imports, otherwise "python -m werkzeug.serving"# breaks.from werkzeug._reloader import run_with_reloaderrun_with_reloader(inner, extra_files, reloader_interval,reloader_type)else:inner()

5.3  makeserver() 方法

  1. inner()方法中调用makeserver()方法时传递了所有需要的参数: hostname = 127.0.0.1;port = 5000;  app:在这里就是flask 程序;thread-线程。 processes = 1 单进程 …
  2. 根据判断条件,make_server()方法最后会返回BaseWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd) 来启动BaseWSGIServer,创造一个单线程,单进程的WSGI server。
def make_server(host=None, port=None, app=None, threaded=False, processes=1,request_handler=None, passthrough_errors=False,ssl_context=None, fd=None):"""Create a new server instance that is either threaded, or forksor just processes one request after another."""if threaded and processes > 1:raise ValueError("cannot have a multithreaded and ""multi process server.")elif threaded:return ThreadedWSGIServer(host, port, app, request_handler,passthrough_errors, ssl_context, fd=fd)elif processes > 1:return ForkingWSGIServer(host, port, app, processes, request_handler,passthrough_errors, ssl_context, fd=fd)else:return BaseWSGIServer(host, port, app, request_handler,passthrough_errors, ssl_context, fd=fd)

5.4 class BaseWSGIServer(HTTPServer, object)

BaseWSGIServer类继承自HTTPServer类,最后详细讲。

class BaseWSGIServer(HTTPServer, object):"""Simple single-threaded, single-process WSGI server."""multithread = Falsemultiprocess = Falserequest_queue_size = LISTEN_QUEUEdef __init__(self, host, port, app, handler=None,passthrough_errors=False, ssl_context=None, fd=None):if handler is None:handler = WSGIRequestHandlerself.address_family = select_ip_version(host, port)if fd is not None:real_sock = socket.fromfd(fd, self.address_family,socket.SOCK_STREAM)port = 0HTTPServer.__init__(self, (host, int(port)), handler)self.app = appself.passthrough_errors = passthrough_errorsself.shutdown_signal = Falseself.host = hostself.port = self.socket.getsockname()[1]# Patch in the original socket.if fd is not None:self.socket.close()self.socket = real_sockself.server_address = self.socket.getsockname()if ssl_context is not None:if isinstance(ssl_context, tuple):ssl_context = load_ssl_context(*ssl_context)if ssl_context == 'adhoc':ssl_context = generate_adhoc_ssl_context()# If we are on Python 2 the return value from socket.fromfd# is an internal socket object but what we need for ssl wrap# is the wrapper around it :(sock = self.socketif PY2 and not isinstance(sock, socket.socket):sock = socket.socket(sock.family, sock.type, sock.proto, sock)self.socket = ssl_context.wrap_socket(sock, server_side=True)self.ssl_context = ssl_contextelse:self.ssl_context = Nonedef log(self, type, message, *args):_log(type, message, *args)def serve_forever(self):self.shutdown_signal = Falsetry:HTTPServer.serve_forever(self)except KeyboardInterrupt:passfinally:self.server_close()def handle_error(self, request, client_address):if self.passthrough_errors:raisereturn HTTPServer.handle_error(self, request, client_address)def get_request(self):con, info = self.socket.accept()return con, info

5.5 HTTPServer类

  1. HTTPServer类在python安装路径的Lib/http/server.py 模块中。
  2. HTTPServer类实现了一个server_bind()方法用来绑定服务器地址和端口。
  3. class HTTPServer(socketserver.TCPServer) HTTPServer类继承了socketserver模块中的TCPServer类。
class HTTPServer(socketserver.TCPServer):allow_reuse_address = 1    # Seems to make sense in testing environmentdef server_bind(self):"""Override server_bind to store the server name."""socketserver.TCPServer.server_bind(self)host, port = self.server_address[:2]self.server_name = socket.getfqdn(host)self.server_port = port

5.6  TCPServer 类

  1. TCPServer在socketserver模块中。首先,socket 介绍: A network socket is an internal endpoint for sending or receiving data at a single node in a computer network. 。
  2. TCPServer类中的介绍:Base class for various socket-based server classes. Defaults to synchronous IP stream (i.e., TCP).
  3. TCPServer类继承了BaseServer基类。都是定义了服务器端的基本属性。后面的继承类可以重写这里的一部分属性。

5.7 再回到BaseWSGIServer 类

BaseWSGIServer类重写了一大堆东西,首先 __init__ 初始化类中的变量,供类中其他函数调用。

  1. __init__ 函数首先定义了handler = WSGIRequestHandler 来处理请求。
  2. WSGIRequestHandler简单介绍:A request handler that implements WSGI dispatching.
  3. class WSGIRequestHandler(BaseHTTPRequestHandler, object) 
    WSGIRequestHandler 继承自 BaseHTTPRequestHandler
  4. class BaseHTTPRequestHandler(socketserver.StreamRequestHandler)
    BaseHTTPRequestHandler继承自socketserver模块中的StreamRequestHandler
  5. class StreamRequestHandler(BaseRequestHandler) 
    StreamRequestHandler继承自BaseRequestHandler,就是用来处理请求的类。
  6. 最初的BaseRequestHandler基类定义的属性很少,一层层的重写,到了WSGIRequestHandler类,越来越完善了。
  7. 最重要的要知道,这个WSGIRequestHandler类是用来处理请求的。
def __init__(self, host, port, app, handler=None,passthrough_errors=False, ssl_context=None, fd=None):if handler is None:handler = WSGIRequestHandler...

8. WSGIRequestHandler类中定义了很多方法。因为WSGI 是单线程、单进程的server,来看这个handle_one_request(self)方法,用来处理一个请求。

    def handle_one_request(self):"""Handle a single HTTP request."""self.raw_requestline = self.rfile.readline()if not self.raw_requestline:self.close_connection = 1elif self.parse_request():return self.run_wsgi()

  9. 调用了run_wsgi()方法,run_wsgi()方法方法好长,重点看这句execute(self.server.app) ,在这儿处理请求使用Flask中的__call__ 方法。。app(Flask实例对象)作为参数在make_server()时已经传递到服务器中了。

    def run_wsgi(self):...def write(data):...def start_response(status, response_headers, exc_info=None):...return writedef execute(app):...try:execute(self.server.app)except ......

10. 服务器收到http请求,去调用app的时候,实际上是用了Flask 的 __call__方法,会调用wsgi_app()方法。

def __call__(self, environ, start_response):"""Shortcut for :attr:`wsgi_app`."""return self.wsgi_app(environ, start_response)

11. 到了wsgi_app(),这里wsgi_app作为中间件的存在,连接着服务器和应用程序。对服务器来说wsgi_app是应用程序;对应用程序来说,wsgi_app是服务器。

wsgi_app(self, environ, start_response)需要三个参数,self即需要运行的flask 应用程序,在创建服务器时传递到了 WSGI server。environ, start_response由服务器提供,wsgi_app的功能就是根据请求查找各种请求处理函数,然后返回请求处理结果到服务器。

    def wsgi_app(self, environ, start_response):"""The actual WSGI application.  This is not implemented in`__call__` so that middlewares can be applied without losing areference to the class.  So instead of doing this::app = MyMiddleware(app):param environ: a WSGI environment:param start_response: a callable accepting a status code,a list of headers and an optionalexception context to start the response"""ctx = self.request_context(environ)ctx.push()error = Nonetry:try:response = self.full_dispatch_request()except Exception as e:error = eresponse = self.handle_exception(e)except:error = sys.exc_info()[1]raisereturn response(environ, start_response)finally:if self.should_ignore_error(error):error = Nonectx.auto_pop(error)

六、结束语

最后以 Nginx(web server),WSGI,Flask(web app) 之间的对话结束本文。

===========================================================
Nginx:Hey,WSGI,我刚从用户那里收到了一个请求,现在转发给你。 
WSGI:好的,Nginx,我会设置好环境变量,然后将这个请求传递给Flask处理。 
Flask:Thanks WSGI!给我一些时间,我将会把请求的响应返回给你。 
WSGI:All right,那我等你。 
Flask:Okay,我完成了,这里是请求的响应结果,请求把结果传递给Nginx。 
WSGI:Good job!Nginx,这里是响应结果,已经按照要求给你传递回来了。 
Nginx:Cool,我收到了,我把响应结果返回给客户端。大家合作愉快~

WSGI与Flask相关推荐

  1. WSGI、Flask及Werkzeug三者之间的关系

    目录 一.WSGI是什么? 二.Werkzeug是什么 三.Flask的WSGI实现 一.WSGI是什么? WSGI是一套接口规范.一个WSGI程序用以接受客户端请求,传递给应用,再返回服务器的响应给 ...

  2. 如何理解Nginx, WSGI, Flask之间的关系

    转载自: http://blog.csdn.net/lihao21/article/details/52304119 概览 之前对 Nginx,WSGI(或者 uWSGI,uwsgi),Flask(或 ...

  3. Flask+gunicorn部署HTTP服务

    FLASK Flask提供了HTTP开发服务的框架,但是他本身不提供HTTP Server.内部集成的一个简单的Server只是用于开发调试. Flask内部的HTTP服务只用于开发使用,在启动Fla ...

  4. python flask gunicorn nginx 部署

    WSGI协议 Web框架致力于如何生成HTML代码,而Web服务器用于处理和响应HTTP请求.Web框架和Web服务器之间的通信,需要一套双方都遵守的接口协议.WSGI协议就是用来统一这两者的接口的. ...

  5. python Flask框架如何请求及返回数据——flask详细教程

    python Flask框架如何请求及返回数据--flask详细教程 文章目录: 1 Flask介绍 1.1 Flask简单介绍 1.2 Flask相关资料信息 2 Flask快速入门 2.1 Fla ...

  6. Flask入门系列(转载)

    一.入门系列: Flask入门系列(一)–Hello World 项目开发中,经常要写一些小系统来辅助,比如监控系统,配置系统等等.用传统的Java写,太笨重了,连PHP都嫌麻烦.一直在寻找一个轻量级 ...

  7. flask websocket json_Win10环境下使用Flask配合Celery异步推送实时/定时消息/2020年最新攻略...

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_163 首先得明确一点,和Django一样,在2020年Flask 1.1.1以后的版本都不需要所谓的三方库支持,即Flask-Ce ...

  8. 快速上手Flask(一) 认识框架Flask、项目结构、开发环境

    文章目录 快速上手Flask(一) 认识框架Flask.项目结构.开发环境 Web开发轻量级框架Flask Flash历史和团队 Pallets 项目 flask运行过程 使用flask的场景 使用P ...

  9. Linux生产环境运行flask

    说明 在生产环境运行Flask程序,也就是常用的linux服务器上跑Flask程序. 起因 每次在服务器上跑Flask程序都会有下面这个warning (env) [root@i8z code]# p ...

最新文章

  1. C#跨平台开源项目实战(WPF/Android/IOS/Blazor)
  2. LeetCode 771. 宝石与石头(哈希)
  3. kindeditor和easyui整合出不来
  4. 3-17Pytorch与线性代数运算
  5. SMP IRQ affinity
  6. red hat linux 6.4 DNS配置(怎么不让发表?)
  7. 【渝粤教育】国家开放大学2018年秋季 2632T城市轨道交通客运组织 参考试题
  8. 两个HC-05蓝牙模块互相绑定构成无线串口模块
  9. 常见的几种数组排序方法
  10. 校园网\中心机房\拓扑图 思科模拟器(cisco)
  11. LoRa网关实现水表抄表无线远程数采方案
  12. python计算圆周率_【Python】计算圆周率到小数点后任意位数
  13. 趁年轻,我们干点什么吧
  14. docker安装及加速器
  15. unixbench测试CPU性能工具/mbw测试内存
  16. CentOS7常见问题
  17. 软件项目验收需要的文档
  18. python并行编程 - 介绍篇
  19. 我的python面试简历
  20. 计算机专业英语电池,电池分为哪几种?英文缩写?

热门文章

  1. 干货分享!提高项目执行力的六大方法
  2. 天刀服务器维护开服时间表,4月12日服务器例行维护公告 帮派战开启
  3. base64编码的三种方式、各方式性能比较
  4. rust怎么搜索官服_rust怎么进官服 | 手游网游页游攻略大全
  5. linux的mysql装在哪了,Linux怎么查看软件安装路径 查看mysql安装在哪
  6. Android 动画技术
  7. 鼻炎的症状和治疗 鼻炎有哪些表现
  8. 磁盘阵列raid5创建
  9. fpdisp4.exe
  10. 推荐系统图结构Graph Embedding技术