參考文檔html
http://www.topjishu.com/10935.htmlpython
【轉自 太陽尚遠的博客:http://blog.yeqianfeng.me/2016/04/01/python-yield-expression/】
使用過 python 的 aiohttp 第三方庫的同窗會知道,利用 aiohttp 來構造一個最簡單的web服務器是很是輕鬆的事情,只須要下面幾行代碼就能夠搞定:web
from aiphttp import web import asyncio def index(request): return web.Response(body=b'<h1>Hello World!</h1>') async def init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/index', index) server = await loop.create_server(app.make_handler(), '127.0.0.1', 8000) return server def main(): loop = asyncio.get_event_loop() loop.run_until_complete(init()) loop.run_forever() if __name__ == '__main__': main()
這樣咱們就實現了一個最簡單的 web 服務器...express
運行這個 python 文件,再打開瀏覽器,在地址欄輸入 http://127.0.0.1:8000/index 你就能看見 Hello World
了。是否是很神奇?那麼有的同窗到這裏就會疑惑了,當用戶在瀏覽器輸入 http://127.0.0.1:8000/index
的時候,服務器到底是怎麼把請求定位到咱們的 url 處理函數 index(request)
裏的呢?從代碼來看,能夠確定地判斷,是由於有瀏覽器
app.router.add_route('GET', '/index', index)
這行代碼的緣由,服務器才知道,你的 request 請求(method:GET path:/index) 須要讓 index(request)
函數來處理。那麼行代碼的內部究竟作了什麼?服務器是如何響應一個request請求的呢?讓咱們打開單步調試,一步一步跟着服務器的腳步,看看發生了什麼?服務器
咱們先看服務器是如何接收到請求的,多打幾回斷點就不難發現,當有request進來的時候,服務器會最終進入到 aiohttp 的 server.py 模塊的 ServerHttpProtocol 類裏的 start()函數裏面:app
@asyncio.coroutine def start(self): """Start processing of incoming requests. It reads request line, request headers and request payload, then calls handle_request() method. Subclass has to override handle_request(). start() handles various exceptions in request or response handling. Connection is being closed always unless keep_alive(True) specified. """ # 先看函數註釋,後面的代碼後面說
從源碼的註釋來看,這個函數就是服務器開始處理request的地方了
繼續分析start()函數的代碼:less
...... @asyncio.coroutine def start(self): ....... while True: message = None self._keep_alive = False self._request_count += 1 self._reading_request = False payload = None try: # read http request method # .... # 中間省略若干行... # .... yield from self.handle_request(message, payload) # ....
咱們看到了,在這個代碼快的最後一句,將request交給了handle_request()
函數去處理了,若是這個時候你在 ServerHttpProtocol 類裏面找 handler_request() 函數,會發現,它並非一個 coroutine 的函數,到底是怎麼回事呢? 咱們單步執行到這裏看看,而後 F7 進入到這個函數裏面,發現原來這裏進入的並非 ServerHttpProtocol 類裏的函數,而是 web.py 裏的 RequestHandler 類裏的 handler_request() 函數,原來 RequestHandler 類是繼承自 ServerHttpProtocol 類的,它裏面覆寫了 hander_request() 函數,並用 @asyncio.coroutine 修飾了,咱們看看它的代碼:async
@asyncio.coroutine def handle_request(self, message, payload): if self.access_log: now = self._loop.time() app = self._app # 此處才真正構造了Request對象 request = web_reqrep.Request( app, message, payload, self.transport, self.reader, self.writer, secure_proxy_ssl_header=self._secure_proxy_ssl_header) self._meth = request.method self._path = request.path try: # 能夠發現,這裏 match_info 的得到是經過 self._router.resolve(request)函數來獲得的。 match_info = yield from self._router.resolve(request) # 獲得的 match_info 必須爲 AbstractMatchInfo 類型的對象 assert isinstance(match_info, AbstractMatchInfo), match_info resp = None request._match_info = match_info ...... if resp is None: handler = match_info.handler # 這個handler會不會就是咱們的request的最終處理函數呢? for factory in reversed(self._middlewares): handler = yield from factory(app, handler) # 重點來了,這裏好像是在等待咱們的 url 處理函數處理的結果啊 resp = yield from handler(request) except: ...... # 下面這兩句的的做用就是將返回的結果送到客戶端了,具體的執行過程較爲複雜,博主也就大體看了下,沒有作詳細思考。這裏就不說了。 resp_msg = yield from resp.prepare(request) yield from resp.write_eof() ......
經過上面的代碼中的註釋,咱們大體瞭解了幾個關鍵點:ide
瞭解了以上三點,基本上整個 request 請求的過程大概就瞭解了,咱們一步一步來看。
先看第一點,match_info 是怎麼來的
仍是看代碼,咱們進入到 self._route.resolve(request) 的源碼中:
@asyncio.coroutine def resolve(self, request): path = request.raw_path method = request.method allowed_methods = set() # 請留意這裏是 for 循環 for resource in self._resources: match_dict, allowed = yield from resource.resolve(method, path) if match_dict is not None: return match_dict else: allowed_methods |= allowed else: if allowed_methods: return MatchInfoError(HTTPMethodNotAllowed(method,allowed_methods)) else: return MatchInfoError(HTTPNotFound())
代碼量並很少,上面的代碼裏的 path 和 method 就是 request 對象裏封裝的客戶端的請求的 url 和 method(例如: /index 和 GET),注意到第9行,return 了一個 match_dict 對象,說明沒有差錯的話,正確的返回結果就是這個 match_dict。match_dict 又是啥呢? 看到 match_dict 經過 resource.resolve(method, path) 函數得到的,咱們不着急看這個函數的內部實現,咱們先看看 resource 是什麼類型,這樣看確定是看不出來的,惟一知道的是它是 self._resource (它是一個list)的元素,咱們打開調試器,執行到這一步就能夠看到, self._resource 中存儲的元素是 ResourceRoute 類型的對象,這個 ResourceRoute 咱們先不細說,只知道它有一個 resolve() 的成員函數:
@asyncio.coroutine def resolve(self, method, path): allowed_methods = set() match_dict = self._match(path) if match_dict is None: return None, allowed_methods for route in self._routes: route_method = route.method allowed_methods.add(route_method) if route_method == method or route_method == hdrs.METH_ANY: # 這裏的 return 語句是正常狀況下的返回結果 return UrlMappingMatchInfo(match_dict, route), allowed_methods else: return None, allowed_methods
咱們發現了,以前說的那個 match_dict 原來就是一個 UrlMappingMatchInfo 對象,可是,細心的同窗能夠發現,這個函數裏也有一個 match_dict 對象,這裏的 match_dict 是 self._match(path) 的返回結果, 那咱們再看看 self._match(path) 是怎樣的一個過程,看調試信息的話,能夠看到,這裏的 self 是 PlainResource 類,他的 _match() 方法以下所示:
def _match(self, path): # string comparison is about 10 times faster than regexp matching if self._path == path: return {} else: return None
代碼很是簡潔,就是將傳入的 path (好比 /index)與 PlainResource 類的實例的 _path 屬性比較,若是相等就返回一個空字典,不然返回 None,我想這個返回結果既然是空字典,那他的做用在上層調用處應該是做爲一個 if 語句的判斷條件來用,事實也確實是這樣的。若是,這裏的 PlainResource 是什麼,我在這裏先告訴你,這是你在初始化服務器的時爲服務器添加路由的時候就實例化的對象,它是做爲app的一個屬性存在的,這裏先無論他,可是你要留意它,後面會講到它。
好了,咱們再次回到 resolve(self, method, path) 函數中去(注意了,有兩個 resolve 函數,我用參數將他們區分開來),在得到 match_dict 以後進行 None 的檢查,若是是 None ,說明request的 path 在 app 的route中沒有匹配的, 那就直接返回 None 了,在上上層的 resolve(self, request)函數裏繼續遍歷下一個 resource 對象而後匹配(balabala...)。
若是 match_dict 不爲 None,說明這個resource對象裏的 path 和 request 裏的 path 是匹配的,那麼就:
for route in self._routes: route_method = route.method allowed_methods.add(route_method) if route_method == method or route_method == hdrs.METH_ANY: # 這裏的 return 語句是正常狀況下的返回結果 return UrlMappingMatchInfo(match_dict, route), allowed_methods
這個操做是當 path 匹配的時候再檢查 method,若是這個 resource 的 method 與 request 的 method 也是相同的,或者 resource 的 method 是 "*",(星號會匹配全部的method),則 return 一個 UrlMappingMatchInfo 對象,構造時傳入了 match_dict 和 route,route 是 ResourceRoute 類型的對象,裏面封裝了 PlainResource 類型的對象,也就是 resource 對象。也就是說,如今返回的 UrlMappingMatchInfo 對象就是封裝了與 request 的 path 和 method 徹底匹配的 PlainResource 對象。有點亂啊,是否是,只怪博主水平有限。。。
那麼如今理一理,這個 UrlMappingMatchInfo 返回到哪了,回顧一下上面的內容就發現了,返回到的地方是 resolve(self, request) 函數的 match_dict 對象,還記的麼,這個對象還在 for 循環裏,match_dict 獲得返回值,就判斷是否爲 None, 若是是 None 就繼續匹配下一個 PlainResource(後面會說到這個 PlainResource 是怎麼來的,先不要急),若是不是 None,就直接返回 match_dict(是一個UrlMappingMatchInfo對象),這個 match_dict 返回給了誰?不急,再往前翻一翻,發現是返回給了 handler_request(self, message, payload) 函數的 match_info 了,回頭看 handler_request() 的代碼,要求 match_info 是 AbstractMatchInfo 類型的,其實並不矛盾,由於 UrlMappingMatchInfo 類就是繼承自 AbstractMatchInfo 類的。
好了,如今第一個問題搞明白了,咱們知道了match_info 是什麼,從哪來的,裏面封裝了那些信息。
如今咱們再看看 handler 是什麼:
咱們繼續看 handler_request(self, message, payload):
# 這裏是將返回的 match_info 封裝到了 request 對象中了,以便後面使用,先無論他 request._match_info = match_info ...... # 省略號是省去了部分不做爲重點的代碼 if resp is None: # 這裏咱們獲得了 handler,看看它到底是什麼 handler = match_info.handler for factory in reversed(self._middlewares): handler = yield from factory(app, handler) resp = yield from handler(request)
終於又回到了咱們的 handler 了,能夠看到,handler 實際上是 match_info 的一個屬性,可是咱們看調試信息的話發現 match_info 並無 handler 這一屬性,緣由是由於調試窗口能顯示的都是非函數的屬性,python中,函數也屬於對象的屬性之一,而這裏的 handler 剛好就是一個函數,因此返回的 handler 才能是一個可調用的對象啊。閒話很少說,咱們的目的是搞清楚 handler 究竟是什麼,爲了弄清楚 match_info.handler 是啥,咱們進入 AbstractMatchInfo 類裏面看看:
class AbstractMatchInfo(metaclass=ABCMeta): ...... @asyncio.coroutine # pragma: no branch @abstractmethod def handler(self, request): """Execute matched request handler""" ......
很明顯,handler 是一個抽象方法,它的具體實現應該在其子類裏,因此咱們再看看 UrlMappingMatchInfo 類:
class UrlMappingMatchInfo(dict, AbstractMatchInfo): ...... @property def handler(self): return self._route.handler ......
原來 handler() 函數返回的是 UrlMappingMatchInfo 的 self._route.handler,這個 _route 又是啥呢?不知道就看調試信息啊~,看了調試信息後,原來 _route 是一個 ResourceRoute 類型的對象:
細心的同窗會發現,即使是 _route,也依然沒有看到 hanler 啊,說明 handler 在 ResourceRoute 類裏也是個函數。因此...,還要去看看 ResourceRoute 類:
class ResourceRoute(AbstractRoute): """A route with resource""" ...... # 剩下的不貼了
我找了半天發現並無 handler() 函數啊,好,那咱們就去它的父類找去:
class AbstractRoute(metaclass=abc.ABCMeta): def __init__(self, method, handler, *, expect_handler=None, resource=None): self._method = method # 此處給 _handler 賦值 self._handler = handler ...... # 返回的是self._handler @property def handler(self): return self._handler ......
哈哈,原來在這裏,小婊砸終於找到你啦。原來層層 handler 的最終返回的東西是 AbstractRoute 類裏的 _handler,能夠發現這個 _handler 是在 AbstractRoute 構造函數裏給它賦值的,那麼這個 AbstractRoute 類型的對象何時會實例化呢?
如今咱們回到最原始的地方,就是:
app.router.add_route('GET', '/index', index)
到了這裏,就有必要說一下了,這個 app.router 返回的實際上是一個 UrlDispatcher 對象,在 Application 類裏面有一個 @property 修飾的 router() 函數,返回的是Application對象的 _router 屬性,而 _router 表明的就是一個 UrlDispatcher 對象。因此,上面的 add_route() 函數實際上是 UrlDisparcher 類的成員函數。這個 add_route() 究竟又作了什麼事呢?。進入到 add_route()函數內部:
class UrlDispatcher(AbstractRouter, collections.abc.Mapping): ...... def add_route(self, method, path, handler, *, name=None, expect_handler=None): resource = self.add_resource(path, name=name) return resource.add_route(method, handler, expect_handler=expect_handler) ...... def add_resource(self, path, *, name=None): if not path.startswith('/'): raise ValueError("path should be started with /") if not ('{' in path or '}' in path or self.ROUTE_RE.search(path)): # 注意這裏構造的 resource 對象是 PlainResource 類型的 resource = PlainResource(path, name=name) self._reg_resource(resource) return resource
出於方便,我把接下來要分析的代碼塊也貼在上面,反正都是 UrlDispatcher 類的成員函數。。
看上面的註釋就知道了,函數 add_resource() 返回了一個 PlainResource 類型的對象,前面屢次提到的 PlainResource 終於在這裏看到了來源,構造 resource 對象的時候把傳入 add_route()中的 path 給封裝進去了。而後就到了:
return resource.add_route(method, handler, expect_handler=expect_handler)
看來 PlainResource 類裏面也有一個 add_route() 成員函數,咱們繼續 F7 進入PlainResource 的 add_route()裏面:
class Resource(AbstractResource): ...... def add_route(self, method, handler, *,expect_handler=None): for route in self._routes: if route.method == method or route.method == hdrs.METH_ANY: raise RuntimeError("Added route will never be executed, " "method {route.method} is " "already registered".format(route=route)) route = ResourceRoute(method, handler, self,expect_handler=expect_handler) self.register_route(route) return route ......
這個函數實例化了一個 ResourceRoute 對象 route,而且把咱們一步步傳進來的 method 和handler(真正的 URL 處理函數)也傳入了 ResourceRoute 的構造方法中,咱們來看看這個 ResourceRoute 類的狀況:
class ResourceRoute(AbstractRoute): """A route with resource""" def __init__(self, method, handler, resource, *, expect_handler=None): super().__init__(method, handler, expect_handler=expect_handler, resource=resource)
驚喜的發現原來 ResourceRoute 就是 AbstractRoute 的子類,實例化的時候須要調用父類的構造方法,因此咱們剛纔疑問的 AbstractRoute 類就是在這個時候實例化的,其內部的 _handler 屬性也是在這個時候賦值的,也就是對應下面這句話中的 index 函數,
app.router.add_route('GET', '/index', index)
這樣一來,咱們添加路由的時候,GET,/index,index 這三個信息最終會被封裝成一個 ResourceRoute 類型的對象,而後再通過層層封裝,最終會變成 app 對象內部的一個屬性,你屢次調用這個方法添加其餘的路由就會有多個 ResourceRoute 對象封裝進 app.
好了,咱們終於也弄清了 handler 的問題,看來 handler 所指向的確實就是咱們最終的 url 處理函數。
這樣咱們再回到 handle_request() 中看:
@asyncio.coroutine def handle_request(self, message, payload): ...... handler = match_info.handler for factory in reversed(self._middlewares): handler = yield from factory(app, handler) resp = yield from handler(request) .......
看明白了吧,獲得了匹配 request 的 handler,咱們就能夠放心的調用它啦~~
這裏或許有的同窗還有一個疑問,就是中間那個 for 循環是幹什麼的,我在這裏簡單解釋一下。這裏實際上是涉及到初始化 app 的時候所賦值的另外一個參數 middlewares,就像這樣:
app = web.Application(loop=loop, middlewares=[ data_factory, response_factory, logger_factory])
middlewares 實際上是一種攔截器機制,能夠在處理 request 請求的先後先通過攔截器函數處理一遍,好比能夠統一打印 request 的日誌等等,它的原理就是 python 的裝飾器,不知道裝飾器的同窗還請自行谷歌,middlewares 接收一個列表,列表的元素就是你寫的攔截器函數,for 循環裏以倒序分別將 url 處理函數用攔截器裝飾一遍。最後再返回通過所有攔截器裝飾過的函數。這樣在你最終調用 url 處理函數以前就能夠進行一些額外的處理啦。
終於寫完了,鑑於博主水平有限,有寫的不妥的地方還請各位小夥伴留言指正,你們共同進步 ^_^