Tornado 源碼閱讀

aaanly 8年前發布 | 17K 次閱讀 源碼閱讀 Python開發 Python Tornado

Date: 03/14/2016

Tornado 源碼閱讀

準備開始學習django的文檔和源代碼了,先總結一下看tornado源碼的收獲,防止忘記。 這里寫的是tornadov1.2的代碼,因為我看了一遍4.3版本,和1.2思路相差不大,只是 utils更多了,1.2版本代碼量少,更有利于理清思路。列出我們將會涉及到的文件:

# tree . ├── httpserver.py
├── httputil.py
├── __init__.py
├── ioloop.py
├── iostream.py
├── web.py

自上而下 - 總體概覽

首先,我們來一張圖,表明tornado整體的框架,但是并不涉及tornado中所有文件和類, 只是其中一部分而已。

img/tornado.png

不太會畫圖,描述一下圖片的意思吧:

  • tornado是以IOLoop為核心的(雖然沒有畫在中間), IOLoop 是對epoll, kqueue, select的一層包裝,是異步的核心,相當于 python3.4 引入的 DefaultSelector
  • Application 是整個web應用的配置中心,配置一些東西比如 secret_cookie 等,其中還包括了 URL正則和對應的Handler 列表。
  • HTTPServer 或者是 WSGI 負責解析HTTP請求
  • HTTPConnection是一次處理的包裝,在HTTPServer初始化的時候,會把Application的實例 傳到HTTPServer中,當請求來了,就調用application,從而執行 __call__ 方法
def __call__(self, request): """Called by HTTPServer to execute the request.""" transforms = [t(request) for t in self.transforms]
    handler = None args = []
    kwargs = {}
    handlers = self._get_host_handlers(request) if not handlers:
        handler = RedirectHandler( self, request, url="http://" + self.default_host + "/") else: for spec in handlers:
            match = spec.regex.match(request.path) if match: # None-safe wrapper around urllib.unquote to handle # unmatched optional groups correctly def unquote(s): if s is None: return s return urllib.unquote(s)
                handler = spec.handler_class(self, request, **spec.kwargs) # Pass matched groups to the handler.  Since # match.groups() includes both named and unnamed groups, # we want to use either groups or groupdict but not both. kwargs = dict((k, unquote(v)) for (k, v) in match.groupdict().iteritems()) if kwargs:
                    args = [] else:
                    args = [unquote(s) for s in match.groups()] break if not handler:
            handler = ErrorHandler(self, request, status_code=404) # In debug mode, re-compile templates and reload static files on every # request so you don't need to restart to see changes if self.settings.get("debug"): if getattr(RequestHandler, "_templates", None): for loader in RequestHandler._templates.values():
                loader.reset()
        RequestHandler._static_hashes = {}

    handler._execute(transforms, *args, **kwargs) return handler
  • IOStream是socket上的一層包裝,也會傳到HTTPConnection中。

從socket開始

好了,概覽看完了,接下來我們從服務器socket開始過一遍。

application = web.Application([
    (r"/", MainPageHandler),
])
http_server = httpserver.HTTPServer(application)
http_server.listen(8080)
ioloop.IOLoop.instance().start()

這里我們看到了,首先實例化Application,里邊設置了路由表。

看看HTTPServer的demo:

import httpserver import ioloop def handle_request(request):
    message = "You requested %s\n" % request.uri
    request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % ( len(message), message))
    request.finish()
http_server = httpserver.HTTPServer(handle_request)
http_server.listen(8888)
ioloop.IOLoop.instance().start()

實例化HTTPServer的時候,我們就要給它傳一個處理http請求的東西,在項目中,也就是我們 上面經過實例化過的application。調用 http_server.listen 方法就會調用 http_server.start

def listen(self, port, address=""):
    self.bind(port, address)
    self.start(1)

start做了一件最重要的事情:把自己加入IOLoop。這樣子,每當有請求來,epoll就會把 當前進程拉起來,然后開始執行,當然,首先,我們要找到 poll 才能說服你嘛。別著急, 我們先看看 ioloop.IOLoop.instance().start() 做了什么。 在 這里 我們找到了事件循環,是吧,我就說一定是這樣的^_^:

while True:

并且在 這里 我們看到了 poll。

event_pairs = self._impl.poll(poll_timeout)

然后就在 self._handlers 里找到對應的handler,去處理。對于已經打開的socket, 那自然就是讀寫數據了,那么對于服務器起的這個socket,注冊的handler是個啥呢? 玄機還藏在 httpserver.py 里, 點這里 :

self.io_loop.add_handler(self._socket.fileno(), self._handle_events,
        ioloop.IOLoop.READ)

當服務器socket可讀時,調用 self._handle_events 方法,ok,我們繼續看:

# 省略了一部分代碼,故不用Python高亮
while True:
    try:
        connection, address = self._socket.accept()
    except ...

    stream = iostream.IOStream(connection, io_loop=self.io_loop)
    HTTPConnection(stream, address, self.request_callback,
            self.no_keep_alive, self.xheaders)

實例化了一個 HTTPConnection, 并且還設置了 callback,這里的callback就是我們 實際項目中的 application. HTTPConnection的 __init__ 函數最后:

self._header_callback = stack_context.wrap(self._on_headers)
self.stream.read_until("\r\n\r\n", self._header_callback)

嗯~parse HTTP的頭部,然后工廠模式調用不同的方法是不是?我猜是這樣的,繼續看 下去,越來越帶勁了,哈哈哈哈:

def _on_headers(self, data):
    # blablabla, parse...
    method, uri, version = start_line.split(" ")  # GET /index HTTP/1.1
    # blablabla, ...
    self._request = HTTPRequest(
        connection=self, method=method, uri=uri, version=version,
        headers=headers, remote_ip=self.address[0]
    )
    # blablabla, ...
    self.request_callback(self._request)

到這里,我還記得,最后一句,就是調用 application.__call__ 方法,你還記得嗎? 希望還沒暈。千萬別轉暈了,現在我們要轉回去,跳到 Application 里了哦~

def __call__(self, request):
    transforms = [t(request) for t in self.transforms]
    handler = None args = []
    kwargs = {}
    handlers = self._get_host_handlers(request) if not handlers: pass # 略

咱們繼續跳,現在該看 self._get_host_handlers 干了什么了!

def _get_host_handlers(self, request):
    host = request.host.lower().split(':')[0] for pattern, handlers in self.handlers: if pattern.match(host): return handlers return None

看到沒,在application的 handlers 里找到匹配的 handler,然后返回,所以文檔上說, 如果一個URL匹配到了n個handler,調用的是第一個被找到的(根據你放列表的順序)。 我們先繼續往下看,初始化完 handler 以后,代碼執行了 handler的 _execute 方法, 以及在 這里 我們看到了執行代碼的操作:

handler = spec.handler_class(self, request, **spec.kwargs)  # 找 Handler
handler._execute(transforms, *args, **kwargs)  # 執行

然后我們再回去看 __call__ 里的代碼,首先我們看到__init__

def __init__(self, application, request, **kwargs):

然后,我們再看 _execute :

def _execute(self, transforms, *args, **kwargs):
    """Executes this request with the given output transforms."""
    self._transforms = transforms
    with stack_context.ExceptionStackContext(
        self._stack_context_handle_exception):
        if self.request.method not in self.SUPPORTED_METHODS:
            raise HTTPError(405)
        # If XSRF cookies are turned on, reject form submissions without
        # the proper cookie
        if self.request.method not in ("GET", "HEAD") and \
        self.application.settings.get("xsrf_cookies"):
            self.check_xsrf_cookie()
        self.prepare()
        if not self._finished:
            getattr(self, self.request.method.lower())(*args, **kwargs)
            if self._auto_finish and not self._finished:
                self.finish()


看到沒,工廠在此!:

getattr(self, self.request.method.lower())(*args, **kwargs)

干!終于找到你了!這里前后調用了兩個鉤子,首先 self.prepare 然后 self.finish 現在你知道可以用他們來做一些類似中間件的工作了吧~

好了,這之后就是 finish, flush 等等了,我就不繼續講下去了。總結一下,一開始我以為 工廠會在很早出現,沒想到一直到 _execute 才出現-。-。

估計這樣做是為了代碼不太散亂?

如果你對最新的tornado代碼有興趣,也可以去看,不過看完之后可能會一頭霧水,因為 對比1.2來說,代碼量實在是大多了~

來源:https://github.com/jiajunhuang/blog/blob/master/tornado.rst

 本文由用戶 aaanly 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!