摘要:在初識(shí)一中,我們了解了框架的基本用法。在本篇文章中,我們通過(guò)源碼來(lái)探究一些基本原理。因此下一步就是研究我們寫(xiě)的應(yīng)用函數(shù)是如何被封裝成適配的
在初識(shí)bottle(一)中,我們了解了bottle框架的基本用法。在本篇文章中,我們通過(guò)源碼來(lái)探究一些基本原理。1. run的實(shí)現(xiàn)
所有的框架請(qǐng)求響應(yīng)都基于一個(gè)原理
http請(qǐng)求 --> wsgi服務(wù)器 --> wsgi接口(實(shí)際就是框架中自定義實(shí)現(xiàn)的函數(shù)經(jīng)過(guò)底層封裝) --> 響應(yīng)
可以參考廖雪峰的教程中關(guān)于wsgi接口的講解
下我們先看看bottle是如何實(shí)現(xiàn)服務(wù)器運(yùn)行時(shí)自動(dòng)重新加載
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 # 自動(dòng)重載 if reloader and not os.environ.get("BOTTLE_CHILD"): import subprocess lockfile = None try: # tempfile 臨時(shí)文件操作模塊https://docs.python.org/2/library/tempfile.html # 第一個(gè)相當(dāng)于執(zhí)行os.open()函數(shù)返回文件handler,第二個(gè)表示絕對(duì)路徑 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 是獲取當(dāng)前python解釋器的路徑 while os.path.exists(lockfile): args = [sys.executable] + sys.argv environ = os.environ.copy() environ["BOTTLE_CHILD"] = "true" environ["BOTTLE_LOCKFILE"] = lockfile # 創(chuàng)建一個(gè)子進(jìn)程實(shí)例 p = subprocess.Popen(args, env=environ) # 如果返回None表示子進(jìn)程未結(jié)束 while p.poll() is None: # Busy wait... # 臨時(shí)文件設(shè)置為當(dāng)前時(shí)間 os.utime(lockfile, None) # I am alive! time.sleep(interval) # linux 系統(tǒng)的信號(hào)機(jī)制http://www.cppblog.com/sleepwom/archive/2010/12/27/137564.html # 3表示按下退出鍵 # 非正常退出時(shí) if p.poll() != 3: # os.unlink 相當(dāng)于去除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
首先第一次運(yùn)行時(shí),開(kāi)啟一個(gè)新的進(jìn)程,確保運(yùn)行server時(shí)的進(jìn)程和python解釋器一致
不影響主進(jìn)程的繼續(xù)運(yùn)行
try: # 這一部分主要是app的相關(guān)設(shè)置 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. ") # 當(dāng)選擇自動(dòng)重載時(shí),如果解釋器進(jìn)程已經(jīng)啟動(dòng) # 則只需要檢測(cè)應(yīng)用相關(guān)內(nèi)容有沒(méi)有變化,如果有變化終止主線程并重新實(shí)現(xiàn)異常捕獲 if reloader: lockfile = os.environ.get("BOTTLE_LOCKFILE") bgcheck = FileCheckerThread(lockfile, interval) # 開(kāi)啟新線程檢測(cè)文件修改,如果修改終止當(dāng)前主線程,拋出異常 with bgcheck: # 主線程監(jiān)聽(tīng)請(qǐng)求 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會(huì)對(duì)應(yīng)用相關(guān)文件內(nèi)容變化進(jìn)行檢測(cè)
server加載app,由server接收請(qǐng)求并執(zhí)行相應(yīng)的應(yīng)用函數(shù)
在此之前,我們先了解FileCheckerThread
這是一個(gè)上下文管理器,當(dāng)__enter__時(shí)開(kāi)啟一個(gè)新的線程,這個(gè)線程的任務(wù)就是檢測(cè)應(yīng)用相關(guān)模塊文件的變化,決定是否終止主線程,當(dāng)__exit__時(shí),如果返回True則重現(xiàn)異常,否則正常執(zhí)行后續(xù)代碼
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() # 這個(gè)地方是重新載入更新后模塊的關(guān)鍵 # 當(dāng)檢測(cè)到文件變化時(shí),終止主線程使監(jiān)聽(tīng)請(qǐng)求停止,退出上下文管理器時(shí),如果返回True則重現(xiàn)異常捕獲 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調(diào)用應(yīng)用函數(shù)
bottle提供了一個(gè)ServerAdapter的適配器類(lèi),重寫(xiě)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)
默認(rèn)使用了python自帶的wsgiref, 從代碼中我們可以看到其中主要由三部分組成:接收請(qǐng)求模塊,處理請(qǐng)求模塊,組裝模塊
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 raise4.WSGIServer
4.1 尋根到底,我們現(xiàn)研究一下WSGIServer 的基類(lèi)
BaseServer 主要實(shí)現(xiàn)線程上的控制,實(shí)現(xiàn)一些供上層調(diào)用的接口,例如
server_activate serve_forever shutdown handle_request verify_request handle_error
TCPServer 繼承BaseServer, 實(shí)現(xiàn)bind,listen,accept, close等函數(shù)的封裝
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兩個(gè)屬性
WSGIServer 繼承HttpServer, 設(shè)置了環(huán)境變量,提供了獲取應(yīng)用和設(shè)置應(yīng)用的接口
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的實(shí)現(xiàn)
最底層的BaseRequestHandler:處理請(qǐng)求的基類(lèi),定義了處理請(qǐng)求的流程
StreamRequestHandler: 繼承BaseRequestHandler,提供了處理請(qǐng)求前rfile和wfile屬性,使處理請(qǐng)求時(shí)能通過(guò)類(lèi)似文件讀寫(xiě)獲取請(qǐng)求和返回響應(yīng)
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處理一個(gè)請(qǐng)求,輪詢直到收到一個(gè)明確關(guān)閉連接;parse_request解析請(qǐng)求requestline,如果一切正常,繼續(xù)處理請(qǐng)求
WSGIRequestHandler:繼承了BaseHTTPRequestHandler, 添加get_environ獲取環(huán)境變量, 重寫(xiě)了handle方法。當(dāng)requestline >65536時(shí)返回414, 實(shí)例化一個(gè)ServerHandler實(shí)例
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())實(shí)現(xiàn)了從請(qǐng)求到應(yīng)用函數(shù)執(zhí)行,并把執(zhí)行后的結(jié)果寫(xiě)入wfile返回
我們?cè)倏磜sgiref.handlers中BaseHandler中,是如何實(shí)現(xiàn)的。
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接受了兩個(gè)參數(shù),一個(gè)envrion, 和一個(gè)start_response的方法。因此下一步就是研究我們寫(xiě)的應(yīng)用函數(shù)是如何被封裝成適配的application
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/38449.html
摘要:在源碼閱讀一中,我們了解了如何接收請(qǐng)求,處理請(qǐng)求以及如何檢測(cè)模塊變化重啟。接下來(lái)我們看一下源碼是怎么實(shí)現(xiàn)的經(jīng)過(guò)封裝后,最終獲得的是具備有一些屬性的裝飾器當(dāng)為時(shí),將的屬性傳遞給,使其具備相同的屬性。 在《Bottle源碼閱讀(一)》中,我們了解了bottle如何接收請(qǐng)求,處理請(qǐng)求以及如何檢測(cè)模塊變化重啟server。在ServerHandler類(lèi)中的run函數(shù)中,application接...
摘要:而其他的引擎,例如能夠幫我們進(jìn)行驗(yàn)證登錄自此,官網(wǎng)的我們已經(jīng)大致有了了解后續(xù)我們可以選擇運(yùn)用該框架實(shí)現(xiàn)一些簡(jiǎn)單的應(yīng)用,或者可以深入研究其源碼,提升自身的編程水平 在初識(shí)Bottle(一)中,我們了解了Bottle的基本用法在Bottle源碼閱讀(一)和Bottle源碼閱讀(二)可以查看個(gè)人對(duì)bottle源碼的相關(guān)閱讀筆記 下面繼續(xù)閱讀Bottle的官方文檔https://bottlep...
摘要:最近在閱讀微型框架的源碼,發(fā)現(xiàn)了中有一個(gè)既是裝飾器類(lèi)又是描述符的有趣實(shí)現(xiàn)。所以第三版的代碼可以這樣寫(xiě)第三版的代碼沒(méi)有使用裝飾器,而是使用了描述符這個(gè)技巧。更大的問(wèn)題來(lái)自如何將描述符與裝飾器結(jié)合起來(lái),因?yàn)槭且粋€(gè)類(lèi)而不是方法。 最近在閱讀Python微型Web框架Bottle的源碼,發(fā)現(xiàn)了Bottle中有一個(gè)既是裝飾器類(lèi)又是描述符的有趣實(shí)現(xiàn)。剛好這兩個(gè)點(diǎn)是Python比較的難理解,又混合在...
摘要:簡(jiǎn)介官網(wǎng)上對(duì)它的定位是一個(gè)微開(kāi)發(fā)框架。另外一個(gè)必須理解的概念是,簡(jiǎn)單來(lái)說(shuō)就是一套和框架應(yīng)用之間的協(xié)議。功能比較豐富,支持解析自動(dòng)防止攻擊繼承變量過(guò)濾器流程邏輯支持代碼邏輯集成等等。那么,從下一篇文章,我們就正式開(kāi)始源碼之旅了 文章屬于作者原創(chuàng),原文發(fā)布在個(gè)人博客。 flask 簡(jiǎn)介 Flask 官網(wǎng)上對(duì)它的定位是一個(gè)微 python web 開(kāi)發(fā)框架。 Flask is a micro...
摘要:安裝是一個(gè)輕量型的不依賴(lài)于任何第三方庫(kù)的框架,整個(gè)框架只有一個(gè)文件。向打聲招呼吧新建一個(gè)文件在瀏覽器或者,,得到結(jié)果當(dāng)使用裝飾器綁定路由時(shí),實(shí)際是使用了的默認(rèn)應(yīng)用,即是的一個(gè)實(shí)例。 1. 安裝 bottle是一個(gè)輕量型的不依賴(lài)于任何第三方庫(kù)的web框架,整個(gè)框架只有bottle.py一個(gè)文件。 wget http://bottlepy.org/bottle.py 2. 向bottl...
閱讀 2066·2021-11-22 13:52
閱讀 992·2021-11-17 09:33
閱讀 2719·2021-09-01 10:49
閱讀 2853·2019-08-30 15:53
閱讀 2665·2019-08-29 16:10
閱讀 2438·2019-08-29 11:31
閱讀 1364·2019-08-26 11:40
閱讀 1877·2019-08-26 10:59