Flask学习-Wsgiref库

一、前言

前面在Flask学习-Flask基础之WSGI中提到了WerkZeug,我们知道,WerkZeug是一个支持WSGI协议的Server,其实还有很多其他支持WSGI协议的Server。http://wsgi.readthedocs.io/en/latest/servers.html,这里可以看到有uwsgi、werkzeug.servingwsgirefpython-fastcgi等等几十个。wsgiref是官方给出的一个实现了WSGI标准用于演示用的简单Python内置库,它实现了一个简单的WSGI Server和WSGI Application(在simple_server模块中),主要分为五个模块:simple_server, util, headers, handlers, validate。 wsgiref源码地址:https://pypi.python.org/pypi/wsgiref。通过学习Wsgiref,我相信能够更加清晰的了解Web框架的实现。

二、例子

wsgiref 是 PEP 333 定义的 wsgi 规范的范例实现,里面的功能包括了:

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

由于simple_server中已经实现了一个简单的WSGI Server和WSGI Application,我们只要直接运行就可以了。

simple_server.py:

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()

  

  

然后将app运行起来后,结果如下:

三、过程分析

1、Server端启动流程

make_server(‘localhost‘, 8000, application)

先查看make_server在wsgiref中的定义:

def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
   #  -> HTTPServer.__init__
    #    -> TCPServer.__init__
    #       -> TCPServer.server_bind
    #           -> TCPServer.socket.bind
    #       -> TCPServer.server_activate
    #           -> TCPServer.socket.listen
  server = server_class((host, port), handler_class)   server.set_app(app)   return server

  

虽然代码只有三行,但是可以看出生成一个 server 都需要些什么:

    • (host, port):主机名和端口号
    • handler_class:用于处理请求的handler类
    • app 服务器程序在处理时,一定会调用我们之前写好的应用程序,这样他们才能配合起来为客户端面服务,所以,你看到了那个 set_app 调用。

另外,在注释部分,你可以看到那代码背后都发生了什么。

生成 server 实例时,默认的 server_class 是 WSGIServer,它是HTTPServer的子类,后者又是TCPServer的子类,TCPServer又是BaseServer的子类。所以初始化 server 时,会沿着类的继承关系执行下去,最终,生成 server 实例的过程,其实是最底层的 TCPServer 在初始化时,完成了对socket的bind和listen。

后面的 set_app 设置了 app,它会在 handler_class (默认为WSGIRequestHandler)的handle函数中被取出来,然后交给 handler 的 run 函数运行。过程如下:

上图可以看出函数之间的调用关系,也可以看出 make_server 到 使用 socket 监听用户请求的过程。至此,Server端已经起来了。

2、Client端请求过程

httpd.handle_request() 

整个过程可以看出,HTTP建立于TCP之上。

稍微难理解一点的地方是:

1、BaseServer.finish_requset()后,就到了WSGIRequestHandler初始化这一步。

2、BaseRequestHandler初始化时,会调用handle()

BaseServer最需要注意的是在构造的时候设置了请求处理类RequestHandlerClass

class BaseServer:
      def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

  

流程走到TCPServer.handle_request,过程如下:

  • 首先一个连接进来,BaseServer监听到连接,调用self._handle_request_noblock()处理
  • self._handle_request_noblock()最终找到finish_request方法
  • finish_request方法实例化请求处理类RequestHandlerClass
RequestHandlerClass是由调用make_server()时传入的handler_class
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), WSGIRequestHandler)
  server.set_app(app) return server

  

WSGIServer的初始化最终会调用BaseServer的__init__()函数。这里的RequestHandlerClass其实就是make_server传入的WSGIRequestHandler。

四、BaseHandler

从图中可以看出handler模块中的四部分,它们其实是四个类,从基类到子类依次为:

  • BaseHandler
  • SimpleHandler
  • BaseCGIHandler
  • CGIHandler

最主要的实现在 BaseHandler中,其它几个类都是在基类基础上做了简单的实现。BaseHandler是不能直接使用的,因为有几个关键的地方没有实现,SimpleHandler是一个可以使用的简单实现。simple_server中的 ServerHandler类就是这个模块中SimpleHandler的子类。

在 BaseHandler中,最重要的是 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)
            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.

  

它先设置好环境变量,再调用我们的 demo_app, 并通过 finish_response 将调用结果传送给客户端。如果处理过程遇到错误,转入 handle_error 处理。

此外,BaseHandler中还包含了 WSGI 中多次提到的 start_response,start_response 在 demo_app 中被调用,我们看看它的实现:

def start_response(self, status, headers,exc_info=None):
        """‘start_response()‘ callable as specified by PEP 333"""

        # M:
        # exc_info:
        #    The exc_info argument, if supplied, must be a Python sys.exc_info()
        #    tuple. This argument should be supplied by the application only if
        #    start_response is being called by an error handler.

        #    exc_info is the most recent exception catch in except clause

        #    in error_output:
        #        start_response(
        #             self.error_status,self.error_headers[:],sys.exc_info())

        # headers_sent:
        #    when send_headers is invoked, headers_sent = True
        #    when close is invoked, headers_sent = False

        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"

        # M: set status and headers

        self.status = status

        # M:
        #    headers_class is Headers in module headers
        self.headers = self.headers_class(headers)

        return self.write

  

可以看出,它先对参数进行了检查,然后再将headers 存储在成员变量中,这两点 WSGI标准中都有明确说明,需要检查参数,并且这一步只能将 headers存储起来,不能直接发送给客户端。也就是说,这个 start_response 还没有真正 response。

其实刚刚介绍 run 函数的时候已经提到了,真正的 response 在 finish_response 函数中:

def finish_response(self):
        """Send any iterable data, then close self and the iterable

        Subclasses intended for use in asynchronous servers will
        want to redefine this method, such that it sets up callbacks
        in the event loop to iterate over the data, and to call
        ‘self.close()‘ once the response is finished.
        """

        # M:
        #    result_is_file:
        #       True if ‘self.result‘ is an instance of ‘self.wsgi_file_wrapper‘
        #    finish_content:
        #       Ensure headers and content have both been sent
        #    close:
        #       Close the iterable (if needed) and reset all instance vars
        if not self.result_is_file() or not self.sendfile():
            for data in self.result:
                self.write(data) # send data by self.write
            self.finish_content()
        self.close()

  

另外一个需要注意的地方是错误处理,在 run 函数中通过 try/except 捕获错误,错误处理使用了 handle_error 函数,WSGI中提到,start_response 函数的第三个参数 exc_info 会在错误处理的时候使用,我们来看看它是如何被使用的:

def handle_error(self):
        """Log current error, and send error output to client if possible"""
        self.log_exception(sys.exc_info())
        if not self.headers_sent:
            self.result = self.error_output(self.environ, self.start_response)
            self.finish_response()
        # XXX else: attempt advanced recovery techniques for HTML or text?

    def error_output(self, environ, start_response):
        """WSGI mini-app to create error output

        By default, this just uses the ‘error_status‘, ‘error_headers‘,
        and ‘error_body‘ attributes to generate an output page.  It can
        be overridden in a subclass to dynamically generate diagnostics,
        choose an appropriate message for the user‘s preferred language, etc.

        Note, however, that it‘s not recommended from a security perspective to
        spit out diagnostics to any old user; ideally, you should have to do
        something special to enable diagnostic output, which is why we don‘t
        include any here!
        """

        # M:
        # sys.exc_info():
        #    Return information about the most recent exception caught by an except
        #    clause in the current stack frame or in an older stack frame.

        start_response(self.error_status,self.error_headers[:],sys.exc_info())
        return [self.error_body]

  

看到了吧,handle_error 又调用了 error_output ,后者调用 start_response,并将其第三个参数设置为 sys.exc_info(),这一点在 WSGI 中也有说明。

好了,这一部分我们就介绍到这里,不再深入过多的细节,毕竟源代码还是要自己亲自阅读的。剩下的三个部分不是核心问题,就是一些数据结构和工具函数,我们简单说一下。

五、headers

这个模块是对HTTP 响应部分的头部设立的数据结构,实现了一个类似Python 中 dict的数据结构。可以看出,它实现了一些函数来支持一些运算符,例如 __len____setitem____getitem____delitem____str__, 另外,还实现了 dict 操作中的 getkeysvalues函数。

六、util

这个模块主要就是一些有用的函数,用于处理URL, 环境变量。

七、validate

这个模块主要是检查你对WSGI的实现,是否满足标准,包含三个部分:

  • validator
  • Wrapper
  • Check

validator 调用后面两个部分来完成验证工作,可以看出Check部分对WSGI中规定的各个部分进行了检查。

原文地址:https://www.cnblogs.com/skyflask/p/9220768.html

时间: 2024-07-31 18:30:06

Flask学习-Wsgiref库的相关文章

[ZHUAN]Flask学习记录之Flask-SQLAlchemy

From: http://www.cnblogs.com/agmcs/p/4445583.html Flask-SQLAlchemy库让flask更方便的使用SQLALchemy,是一个强大的关系形数据库框架,既可以使用orm方式操作数据库,也可以使用原始的SQL命令. Flask-Migrate 是一个数据迁移框架,需要通过Flask-script库来操作. 一.配置Flask-SQLAlchemy 程序使用的数据库地址需要配置在SQLALCHEMY_DATABASE_URI中,SQLALch

Flask 学习(四)静态文件

Flask 学习(四)静态文件 动态 web 应用也需要静态文件,一般是 CSS 和 JavaScript 文件.理想情况下你的服务器已经配置好提供静态文件的服务. 在开发过程中, Flask 也能做好这个工作. 静态文件引用 我们先来看下普通的 html 引用静态文件,如 css(js也同样,就不多加示例了),以下为一简单实例,直接打开html: flask 处理 —— static 若直接将该html 当成 flask 模板,相对路径自然就失效了,静态文件将不会被成功读取. 那在flask中

MXNet 学习 (1) --- 最易上手的深度学习开源库 --- 安装及环境搭建

安装环境:Win 10 专业版 64位 + Visual Studio 2015 Community. 记录下自己在有GPU的环境下安装配置MXNet的过程.该过程直接使用MXNet release 的 pre-built 包,没有自己使用CMake编译.网上有很多自己编译的教程,过程都比较繁琐,直接使用release包对新手来说更加简单方便. 选择MXNet的原因是因为看了<Caffe.TensorFlow.MXNet三个开源库的对比>这篇博文,其中指出MXNet相对来说是最易上手的深度学习

Flask 学习(一)概述及安装

Flask 概述及安装 Flask 简介 Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . 官方网址 :http://flask.pocoo.org/ 了解 Flask:首先,Flask 是Python 的一个Web 应用框架:其次,它是“微型”的 . 比起同类现有的web框架(如:Django),Flask 并不包含数据库抽象层,表单验证等.Flask 旨在保持代码简洁且易于扩展(Flask源码十

Flask学习之五 用户登录

英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-logins 中文翻译地址:http://www.pythondoc.com/flask-mega-tutorial/userlogin.html 开源中国社区:http://www.oschina.net/translate/the-flask-mega-tutorial-part-v-user-logins 备注:我是三个一起看的,有些

Flask学习之六 个人资料和头像

英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vi-profile-page-and-avatars 中文翻译地址:http://www.pythondoc.com/flask-mega-tutorial/profile.html 开源中国社区:http://www.oschina.net/translate/the-flask-mega-tutorial-part-vi-profile-page-

Flask学习之基础知识与功能

一:flask的背景介绍 Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器. "微"(micro) 并不表示你需要

Flask学习记录之MarkDown编辑文本

为了让网页支持markdown编辑文本,使用如下了4个库 PageDown : 在前端提供一个可以实时将markdown内容转换成html文本进行效果预览的编辑器 Flask-PageDown: 这个库将PageDown集成到Flask-Wtf库中,更方便使用 MarkDown: 将MarkDown标记文本转换为Html文本 Bleach: 基于白名单清除Html文本中不安全的标签 PageDown的使用 和其他类库相识,需要初始化 from flask.ext.pagedown import

Flask学习记录之Flask-SQLAlchemy,Flask-Migrate

Flask-SQLAlchemy库让flask更方便的使用SQLALchemy,是一个强大的关系形数据库框架,既可以使用orm方式操作数据库,也可以使用原始的SQL命令. Flask-Migrate 是一个数据迁移框架,需要通过Flask-script库来操作. 一.配置Flask-SQLAlchemy 程序使用的数据库地址需要配置在SQLALCHEMY_DATABASE_URI中,SQLALchemy支持多种数据库,配置格式如下: Postgres: postgresql://scott:[e