资讯专栏INFORMATION COLUMN

Bottle源码阅读(一)

whidy / 905人阅读

摘要:在初识一中,我们了解了框架的基本用法。在本篇文章中,我们通过源码来探究一些基本原理。因此下一步就是研究我们写的应用函数是如何被封装成适配的

在初识bottle(一)中,我们了解了bottle框架的基本用法。在本篇文章中,我们通过源码来探究一些基本原理。
1. run的实现

所有的框架请求响应都基于一个原理
http请求 --> wsgi服务器 --> wsgi接口(实际就是框架中自定义实现的函数经过底层封装) --> 响应
可以参考廖雪峰的教程中关于wsgi接口的讲解

下我们先看看bottle是如何实现服务器运行时自动重新加载

def run(app=None,
        server="wsgiref",
        host="127.0.0.1",
        port=8080,
        interval=1,
        reloader=False,
        quiet=False,
        plugins=None,
        debug=None,
        config=None, **kargs):
    """ Start a server instance. This method blocks until the server terminates.

        :param app: WSGI application or target string supported by
               :func:`load_app`. (default: :func:`default_app`)
        :param server: Server adapter to use. See :data:`server_names` keys
               for valid names or pass a :class:`ServerAdapter` subclass.
               (default: `wsgiref`)
        :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
               all interfaces including the external one. (default: 127.0.0.1)
        :param port: Server port to bind to. Values below 1024 require root
               privileges. (default: 8080)
        :param reloader: Start auto-reloading server? (default: False)
        :param interval: Auto-reloader interval in seconds (default: 1)
        :param quiet: Suppress output to stdout and stderr? (default: False)
        :param options: Options passed to the server adapter.
     """
    if NORUN: return
    # 自动重载
    if reloader and not os.environ.get("BOTTLE_CHILD"):
        import subprocess
        lockfile = None
        try:
            # tempfile 临时文件操作模块https://docs.python.org/2/library/tempfile.html
            # 第一个相当于执行os.open()函数返回文件handler,第二个表示绝对路径
            fd, lockfile = tempfile.mkstemp(prefix="bottle.", suffix=".lock")
            os.close(fd)  # We only need this file to exist. We never write to it
            # sys.executable 是获取当前python解释器的路径
            while os.path.exists(lockfile):
                args = [sys.executable] + sys.argv
                environ = os.environ.copy()
                environ["BOTTLE_CHILD"] = "true"
                environ["BOTTLE_LOCKFILE"] = lockfile

                # 创建一个子进程实例
                p = subprocess.Popen(args, env=environ)
                # 如果返回None表示子进程未结束
                while p.poll() is None:  # Busy wait...
                    # 临时文件设置为当前时间
                    os.utime(lockfile, None)  # I am alive!
                    time.sleep(interval)
                # linux 系统的信号机制http://www.cppblog.com/sleepwom/archive/2010/12/27/137564.html
                # 3表示按下退出键
                # 非正常退出时
                if p.poll() != 3:
                    # os.unlink 相当于去除remove()
                    if os.path.exists(lockfile): os.unlink(lockfile)
                    sys.exit(p.poll())
        except KeyboardInterrupt:
            pass
        finally:
            if os.path.exists(lockfile):
                os.unlink(lockfile)
        return

首先第一次运行时,开启一个新的进程,确保运行server时的进程和python解释器一致
不影响主进程的继续运行

    try:
        # 这一部分主要是app的相关设置
        if debug is not None: _debug(debug)
        app = app or default_app()
        if isinstance(app, basestring):
            app = load_app(app)
        if not callable(app):
            raise ValueError("Application is not callable: %r" % app)

        for plugin in plugins or []:
            if isinstance(plugin, basestring):
                plugin = load(plugin)
            app.install(plugin)

        if config:
            app.config.update(config)

        if server in server_names:
            server = server_names.get(server)
        if isinstance(server, basestring):
            server = load(server)
        if isinstance(server, type):
            server = server(host=host, port=port, **kargs)
        if not isinstance(server, ServerAdapter):
            raise ValueError("Unknown or unsupported server: %r" % server)

        server.quiet = server.quiet or quiet
        if not server.quiet:
            _stderr("Bottle v%s server starting up (using %s)...
" %
                    (__version__, repr(server)))
            _stderr("Listening on http://%s:%d/
" %
                    (server.host, server.port))
            _stderr("Hit Ctrl-C to quit.

")
        
        # 当选择自动重载时,如果解释器进程已经启动
        # 则只需要检测应用相关内容有没有变化,如果有变化终止主线程并重新实现异常捕获
        if reloader:
            lockfile = os.environ.get("BOTTLE_LOCKFILE")
            bgcheck = FileCheckerThread(lockfile, interval)
            # 开启新线程检测文件修改,如果修改终止当前主线程,抛出异常
            with bgcheck:
                # 主线程监听请求
                server.run(app)
            if bgcheck.status == "reload":
                sys.exit(3)
        else:
            server.run(app)
    except KeyboardInterrupt:
        pass

    except (SystemExit, MemoryError):
        raise
    except:
        if not reloader: raise
        if not getattr(server, "quiet", quiet):
            print_exc()
        time.sleep(interval)
        sys.exit(3)

FileCheckerThread会对应用相关文件内容变化进行检测
server加载app,由server接收请求并执行相应的应用函数
在此之前,我们先了解FileCheckerThread

2. 应用修改后的自动重载

这是一个上下文管理器,当__enter__时开启一个新的线程,这个线程的任务就是检测应用相关模块文件的变化,决定是否终止主线程,当__exit__时,如果返回True则重现异常,否则正常执行后续代码

class FileCheckerThread(threading.Thread):
    """ Interrupt main-thread as soon as a changed module file is detected,
        the lockfile gets deleted or gets too old. """

    def __init__(self, lockfile, interval):
        threading.Thread.__init__(self)
        self.daemon = True
        self.lockfile, self.interval = lockfile, interval
        #: Is one of "reload", "error" or "exit"
        self.status = None

    def run(self):
        exists = os.path.exists
        mtime = lambda p: os.stat(p).st_mtime
        files = dict()

        for module in list(sys.modules.values()):
            path = getattr(module, "__file__", "")
            if path[-4:] in (".pyo", ".pyc"): path = path[:-1]
            if path and exists(path): files[path] = mtime(path)

        while not self.status:
            if not exists(self.lockfile)
            or mtime(self.lockfile) < time.time() - self.interval - 5:
                self.status = "error"
                thread.interrupt_main()
            for path, lmtime in list(files.items()):
                if not exists(path) or mtime(path) > lmtime:
                    self.status = "reload"
                    thread.interrupt_main()
                    break
            time.sleep(self.interval)

    def __enter__(self):
        self.start()
    
    # 这个地方是重新载入更新后模块的关键
    # 当检测到文件变化时,终止主线程使监听请求停止,退出上下文管理器时,如果返回True则重现异常捕获
    def __exit__(self, exc_type, *_):
        if not self.status: self.status = "exit"  # silent exit
        self.join()
        return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)
3. server调用应用函数

bottle提供了一个ServerAdapter的适配器类,重写run方法就能使bottle可以使用多种框架提供的server。

class ServerAdapter(object):
    quiet = False

    def __init__(self, host="127.0.0.1", port=8080, **options):
        self.options = options
        self.host = host
        self.port = int(port)

    def run(self, handler):  # pragma: no cover
        pass

    def __repr__(self):
        args = ", ".join(["%s=%s" % (k, repr(v))
                          for k, v in self.options.items()])
        return "%s(%s)" % (self.__class__.__name__, args)

默认使用了python自带的wsgiref, 从代码中我们可以看到其中主要由三部分组成:接收请求模块,处理请求模块,组装模块

class WSGIRefServer(ServerAdapter):
    def run(self, app):  # pragma: no cover
        from wsgiref.simple_server import make_server
        from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
        import socket

        class FixedHandler(WSGIRequestHandler):
            def address_string(self):  # Prevent reverse DNS lookups please.
                return self.client_address[0]

            def log_request(*args, **kw):
                if not self.quiet:
                    return WSGIRequestHandler.log_request(*args, **kw)

        handler_cls = self.options.get("handler_class", FixedHandler)
        server_cls = self.options.get("server_class", WSGIServer)

        if ":" in self.host:  # Fix wsgiref for IPv6 addresses.
            if getattr(server_cls, "address_family") == socket.AF_INET:

                class server_cls(server_cls):
                    address_family = socket.AF_INET6

        self.srv = make_server(self.host, self.port, app, server_cls,
                               handler_cls)
        self.port = self.srv.server_port  # update port actual port (0 means random)
        try:
            self.srv.serve_forever()
        except KeyboardInterrupt:
            self.srv.server_close()  # Prevent ResourceWarning: unclosed socket
            raise
4.WSGIServer

4.1 寻根到底,我们现研究一下WSGIServer 的基类
BaseServer 主要实现线程上的控制,实现一些供上层调用的接口,例如

server_activate
serve_forever
shutdown
handle_request
verify_request
handle_error

TCPServer 继承BaseServer, 实现bind,listen,accept, close等函数的封装

    def server_bind(self):
        """Called by constructor to bind the socket.

        May be overridden.

        """
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        """Called by constructor to activate the server.

        May be overridden.

        """
        self.socket.listen(self.request_queue_size)

    def server_close(self):
        """Called to clean-up the server.

        May be overridden.

        """
        self.socket.close()

HttpServer 继承TCPServer, 添加了host和port两个属性
WSGIServer 继承HttpServer, 设置了环境变量,提供了获取应用和设置应用的接口

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

4.2 WSGIRequestHandler的实现
最底层的BaseRequestHandler:处理请求的基类,定义了处理请求的流程
StreamRequestHandler: 继承BaseRequestHandler,提供了处理请求前rfile和wfile属性,使处理请求时能通过类似文件读写获取请求和返回响应

class StreamRequestHandler(BaseRequestHandler):

    """Define self.rfile and self.wfile for stream sockets."""

    # Default buffer sizes for rfile, wfile.
    # We default rfile to buffered because otherwise it could be
    # really slow for large data (a getc() call per byte); we make
    # wfile unbuffered because (a) often after a write() we want to
    # read and we need to flush the line; (b) big writes to unbuffered
    # files are typically optimized by stdio even when big reads
    # aren"t.
    rbufsize = -1
    wbufsize = 0

    # A timeout to apply to the request socket, if not None.
    timeout = None

    # Disable nagle algorithm for this socket, if True.
    # Use only when wbufsize != 0, to avoid small packets.
    disable_nagle_algorithm = False

    def setup(self):
        self.connection = self.request
        if self.timeout is not None:
            self.connection.settimeout(self.timeout)
        if self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile = self.connection.makefile("rb", self.rbufsize)
        self.wfile = self.connection.makefile("wb", self.wbufsize)

    def finish(self):
        if not self.wfile.closed:
            try:
                self.wfile.flush()
            except socket.error:
                # A final socket error may have occurred here, such as
                # the local error ECONNABORTED.
                pass
        self.wfile.close()
        self.rfile.close()

BaseHTTPRequestHandler:继承StreamRequestHandler,handle处理一个请求,轮询直到收到一个明确关闭连接;parse_request解析请求requestline,如果一切正常,继续处理请求

WSGIRequestHandler:继承了BaseHTTPRequestHandler, 添加get_environ获取环境变量, 重写了handle方法。当requestline >65536时返回414, 实例化一个ServerHandler实例

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

handler.run(self.server.get_app())实现了从请求到应用函数执行,并把执行后的结果写入wfile返回
我们再看wsgiref.handlers中BaseHandler中,是如何实现的。

    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.
                
    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

application接受了两个参数,一个envrion, 和一个start_response的方法。因此下一步就是研究我们写的应用函数是如何被封装成适配的application

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38449.html

相关文章

  • Bottle源码阅读(二)

    摘要:在源码阅读一中,我们了解了如何接收请求,处理请求以及如何检测模块变化重启。接下来我们看一下源码是怎么实现的经过封装后,最终获得的是具备有一些属性的装饰器当为时,将的属性传递给,使其具备相同的属性。 在《Bottle源码阅读(一)》中,我们了解了bottle如何接收请求,处理请求以及如何检测模块变化重启server。在ServerHandler类中的run函数中,application接...

    zzbo 评论0 收藏0
  • 初识Bottle(二)

    摘要:而其他的引擎,例如能够帮我们进行验证登录自此,官网的我们已经大致有了了解后续我们可以选择运用该框架实现一些简单的应用,或者可以深入研究其源码,提升自身的编程水平 在初识Bottle(一)中,我们了解了Bottle的基本用法在Bottle源码阅读(一)和Bottle源码阅读(二)可以查看个人对bottle源码的相关阅读笔记 下面继续阅读Bottle的官方文档https://bottlep...

    stormjun 评论0 收藏0
  • Bottle框架中的装饰器类和描述符应用

    摘要:最近在阅读微型框架的源码,发现了中有一个既是装饰器类又是描述符的有趣实现。所以第三版的代码可以这样写第三版的代码没有使用装饰器,而是使用了描述符这个技巧。更大的问题来自如何将描述符与装饰器结合起来,因为是一个类而不是方法。 最近在阅读Python微型Web框架Bottle的源码,发现了Bottle中有一个既是装饰器类又是描述符的有趣实现。刚好这两个点是Python比较的难理解,又混合在...

    Panda 评论0 收藏0
  • flask 源码解析:简介

    摘要:简介官网上对它的定位是一个微开发框架。另外一个必须理解的概念是,简单来说就是一套和框架应用之间的协议。功能比较丰富,支持解析自动防止攻击继承变量过滤器流程逻辑支持代码逻辑集成等等。那么,从下一篇文章,我们就正式开始源码之旅了 文章属于作者原创,原文发布在个人博客。 flask 简介 Flask 官网上对它的定位是一个微 python web 开发框架。 Flask is a micro...

    megatron 评论0 收藏0
  • 初识 Bottle

    摘要:安装是一个轻量型的不依赖于任何第三方库的框架,整个框架只有一个文件。向打声招呼吧新建一个文件在浏览器或者,,得到结果当使用装饰器绑定路由时,实际是使用了的默认应用,即是的一个实例。 1. 安装 bottle是一个轻量型的不依赖于任何第三方库的web框架,整个框架只有bottle.py一个文件。 wget http://bottlepy.org/bottle.py 2. 向bottl...

    mengbo 评论0 收藏0

发表评论

0条评论

whidy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<