web.py源碼分析: application

本文主要分析的是web.py庫的 application.py 這個模塊中的代碼。總的來說, 這個模塊主要實現了WSGI兼容的接口,以便應用程序能夠被WSGI應用服務器調用 。WSGI是 Web Server Gateway Interface 的縮寫,具體細節可以查看 WSGI的WIKI頁面

接口的使用

使用web.py自帶的HTTP Server

下面這個例子來自官方文檔的 Hello World ,這個代碼一般是應用入口的代碼:

import web urls = ("/.*", "hello")
app = web.application(urls, globals())

class hello:
    def GET(self):
        return 'Hello, world!'

if __name__ == "__main__":
    app.run()

上面的例子描述了一個web.py應用最基本的組成元素:

  • URL路由表

  • 一個 web.application 實例 app

  • 調用 app.run()

其中, app.run() 的調用是初始化各種WCGI接口,並啓動一個內置的HTTP服務器和這些接口對接,代碼如下:

def run(self, *middleware):
    return wsgi.runwsgi(self.wsgifunc(*middleware))

與WSGI應用服務器對接

如果你的應用要與WSGI應用服務器對接,比如uWSGI,gunicorn等,那麼應用入口的代碼就要換一種寫法了:

import web
class hello:
  def GET(self):
    return 'Hello, world!'
urls = ("/.*", "hello")
app = web.application(urls, globals())
application = app.wsgifunc()

在這種場景下,應用的代碼不需要啓動HTTP服務器,而是實現一個WSGI兼容的接口供WSGI服務器調用。web.py框架爲我們實現了這樣的接口,你只需要調用 application = app.wsgifunc() 就可以了,這裏所得到的 application 變量就是WSGI接口(後面分析完代碼你就會知道了)。

WSGI接口的實現分析

分析主要圍繞着下面兩行代碼進行:

app = web.application(urls, globals())
application = app.wsgifunc()

web.application實例化

初始化這個實例需要傳遞兩個參數:URL路由元組和 globals() 的結果。

另外,還可以傳遞第三個變量: autoreload ,用來指定是否需要自動重新導入Python模塊,這在調試的時候很有用,不過我們分析主要過程的時候可以忽略。

application 類的初始化代碼如下:

class application:
  def __init__(self, mapping=(), fvars={}, autoreload=None):
    if autoreload is None:
      autoreload = web.config.get('debug', False)
    self.init_mapping(mapping)
    self.fvars = fvars
    self.processors = []
    self.add_processor(loadhook(self._load))
    self.add_processor(unloadhook(self._unload))
    if autoreload:
      ...

其中,autoreload相關功能的代碼略去了。其他的代碼主要作了如下幾個事情:

  • self.init_mapping(mapping) :初始化URL路由映射關係。

  • self.add_processor() :添加了兩個處理器。

初始化URL路由映射關係

def init_mapping(self, mapping):
    self.mapping = list(utils.group(mapping, 2))

這個函數還調用了一個工具函數,效果是這樣的:

urls = ("/", "Index",
        "/hello/(.*)", "Hello",
        "/world", "World")

如果用戶初始化時傳遞的元組是這樣的,那麼調用 init_mapping 之後:

self.mapping = [["/", "Index"],
                ["/hello/(.*)", "Hello"],
                ["/world", "World"]]

後面框架在進行URL路由時,就會遍歷這個列表。

添加處理器

    self.add_processor(loadhook(self._load))
    self.add_processor(unloadhook(self._unload))

這兩行代碼添加了兩個處理器: self._load 和 self._unload ,而且還對這兩個函數進行了裝飾。處理器的是用在HTTP請求處理前後的,它不是真正用來處理一個HTTP請求,但是可以用來作一些額外的工作,比如官方教程裏面有提到的給子應用添加session的做法,就是使用了處理器:

def session_hook():
    web.ctx.session = session

app.add_processor(web.loadhook(session_hook))

處理器的定義和使用都是比較複雜的,後面專門講。

wsgifunc函數

wsgifunc的執行結果是返回一個WSGI兼容的函數,並且該函數內部實現了URL路由等功能。

def wsgifunc(self, *middleware):
  """Returns a WSGI-compatible function for this application."""
  ...
  for m in middleware: 
    wsgi = m(wsgi)
  return wsgi

除開內部函數的定義,wsgifunc的定義就是這麼簡單,如果沒有實現任何中間件,那麼就是直接返回其內部定義的 wsgi 函數。

wsgi函數

該函數實現了WSGI兼容接口,同時也實現了URL路由等功能。

def wsgi(env, start_resp):
  # clear threadlocal to avoid inteference of previous requests
  self._cleanup()
  self.load(env)
  try:
    # allow uppercase methods only
    if web.ctx.method.upper() != web.ctx.method:
      raise web.nomethod()
    result = self.handle_with_processors()
    if is_generator(result):
      result = peep(result)
    else:
      result = [result]
  except web.HTTPError, e:
    result = [e.data]
  result = web.safestr(iter(result))
  status, headers = web.ctx.status, web.ctx.headers
  start_resp(status, headers)
  def cleanup():
    self._cleanup()
    yield '' # force this function to be a generator
  return itertools.chain(result, cleanup())
for m in middleware: 
  wsgi = m(wsgi)
return wsgi

下面來仔細分析一下這個函數:

    self._cleanup()
    self.load(env)

self._cleanup() 內部調用 utils.ThreadedDict.clear_all() ,清除所有的thread local數據,避免內存泄露(因爲web.py框架的很多數據都會保存在thread local變量中)。

self.load(env) 使用 env 中的參數初始化 web.ctx 變量,這些變量涵蓋了當前請求的信息,我們在應用中有可能會使用到,比如 web.ctx.fullpath 。

    try:
  # allow uppercase methods only
  if web.ctx.method.upper() != web.ctx.method:
      raise web.nomethod()
  result = self.handle_with_processors()
  if is_generator(result):
      result = peep(result)
  else:
      result = [result]
    except web.HTTPError, e:
  result = [e.data]

這一段主要是調用 self.handle_with_processors() ,這個函數會對請求的URL進行路由,找到合適的類或子應用來處理該請求,也會調用添加的處理器來做一些其他工作(關於處理器的部分,後面專門講)。對於處理的返回結果,可能有三種方式:

  • 返回一個可迭代對象,則進行安全迭代處理。

  • 返回其他值,則創建一個列表對象來存放。

  • 如果拋出了一個HTTPError異常(比如我們使用raise web.OK("hello, world")這種方式來返回結果時),則將異常中的數據 e.data 封裝成一個列表。

-

  result = web.safestr(iter(result))
  status, headers = web.ctx.status, web.ctx.headers
  start_resp(status, headers)
  def cleanup():
    self._cleanup()
    yield '' # force this function to be a generator
  return itertools.chain(result, cleanup())

接下來的這段代碼,會對前面返回的列表 result 進行字符串化處理,得到HTTP Response的body部分。然後根據WSGI的規範作如下兩個事情:

  • 調用 start_resp 函數。

  • 將 result 結果轉換成一個迭代器。

現在你可以看到,之前我們提到的 application = app.wsgifunc() 就是將 wsgi 函數賦值給 application 變量,這樣應用服務器就可以採用WSGI標準和我們的應用對接了。

處理HTTP請求

前面分析的代碼已經說明了web.py框架如何實現WSGI兼容接口的,即我們已經知道了HTTP請求到達框架以及從框架返回給應用服務器的流程。那麼框架內部是如何調用我們的應用代碼來實現一個請求的處理的呢?這個就需要詳細分析剛纔忽略掉的處理器的添加和調用過程。

loadhook和unloadhook裝飾器

這兩個函數是真實處理器的函數的裝飾器函數(雖然他的使用不是採用裝飾器的@操作符),裝飾後得到的處理器分別對應請求處理之前( loadhook )和請求處理之後( unloadhook )。

loadhook

def loadhook(h):
    def processor(handler):
        h()
        return handler()

    return processor

這個函數返回一個函數 processor ,它會確保先調用你提供的處理器函數 h ,然後再調用後續的操作函數 handler 。

unloadhook

def unloadhook(h):
  def processor(handler):
    try:
      result = handler()
      is_generator = result and hasattr(result, 'next')
    except:
      # run the hook even when handler raises some exception
      h()
      raise
    if is_generator:
      return wrap(result)
    else:
      h()
      return result
  def wrap(result):
    def next():
      try:
        return result.next()
      except:
        # call the hook at the and of iterator
        h()
        raise
    result = iter(result)
    while True:
      yield next()
  return processor

這個函數也返回一個 processor ,它會先調用參數傳遞進來的 handler ,然後再調用你提供的處理器函數。

handle_with_processors函數

def handle_with_processors(self):
  def process(processors):
    try:
      if processors:
        p, processors = processors[0], processors[1:]
        return p(lambda: process(processors))
      else:
        return self.handle()
    except web.HTTPError:
      raise
    except (KeyboardInterrupt, SystemExit):
      raise
    except:
      print >> web.debug, traceback.format_exc()
      raise self.internalerror()
  # processors must be applied in the resvere order. (??)
  return process(self.processors)

這個函數挺複雜的,最核心的部分採用了遞歸實現(我感覺不遞歸應該也能實現同樣的功能)。爲了說明清晰,採用實例說明。

前面有提到,初始化 application 實例的時候,會添加兩個處理器到 self.processors :

    self.add_processor(loadhook(self._load))
    self.add_processor(unloadhook(self._unload))

所以,現在的 self.processors 是下面這個樣子的:

self.processors = [loadhook(self._load), unloadhook(self._unload)]
# 爲了方便後續說明,我們縮寫一下:
self.processors = [load_processor, unload_processor]

當框架開始執行 handle_with_processors 的時候,是逐個執行這些處理器的。我們還是來看代碼分解,首先簡化一下 handle_with_processors 函數:

def handle_with_processors(self):
  def process(processors):
    try:
      if processors:  # 位置2
        p, processors = processors[0], processors[1:]
        return p(lambda: process(processors))  # 位置3
      else:
        return self.handle()  # 位置4
    except web.HTTPError:
      raise
    ...
  # processors must be applied in the resvere order. (??)
  return process(self.processors)  # 位置1
  1. 函數執行的起點是 位置1 ,調用其內部定義函數 process(processors) 。

  2. 如果 位置2 判斷處理器列表不爲空,則進入 if 內部。

  3. 在 位置3 調用本次需要執行的處理器函數,參數爲一個lambda函數,然後返回。

  4. 如果 位置2 判斷處理器列表爲空,則執行 self.handle() ,該函數真正的調用我們的應用代碼(下面會講到)。

以上面的例子來說,目前有兩個處理器:

self.processors = [load_processor, unload_processor]

從 位置1 進入代碼後,在 位置2 會判斷還有處理器要執行,會走到 位置3 ,此時要執行代碼是這樣的:

return load_processor(lambda: process([unload_processor]))

load_processor 函數是一個經過 loadhook 裝飾的函數,因此其定義在執行時是這樣的:

def load_processor(lambda: process([unload_processor])):
    self._load()
    return process([unload_processor])  # 就是參數的lambda函數

會先執行 self._load() ,然後再繼續執行 process 函數,依舊會走到 位置3 ,此時要執行的代碼是這樣的:

return unload_processor(lambda: process([]))

unload_processor 函數是一個經過 unloadhook 裝飾的函數,因此其定義在執行時是這樣的:

def unload_processor(lambda: process([])):
  try:
    result = process([])  # 參數傳遞進來的lambda函數
    is_generator = result and hasattr(result, 'next')
  except:
    # run the hook even when handler raises some exception
    self._unload()
    raise
  if is_generator:
    return wrap(result)
  else:
    self._unload()
    return result

現在會先執行 process([]) 函數,並且走到 位置4 (調用 self.handle() 的地方),從而得到應用的處理結果,然後再調用本處理器的處理函數 self._unload() 。

總結一下執行的順序:

  • self._load()

    • self.handle()

  • self._unload()

如果還有更多的處理器,也是按照這種方法執行下去, 對於 loadhook 裝飾的處理器,先添加的先執行,對於 unloadhook 裝飾的處理器,後添加的先執行 。

handle函數

講了這麼多,纔講到真正要調用我們寫的代碼的地方。在所有的load處理器執行完之後,就會執行 self.handle() 函數,其內部會調用我們寫的應用代碼。比如返回個 hello, world 之類的。 self.handle 的定義如下:

def handle(self):
    fn, args = self._match(self.mapping, web.ctx.path)
    return self._delegate(fn, self.fvars, args)

這個函數就很好理解了,第一行調用的 self._match 是進行路由功能,找到對應的類或者子應用,第二行的 self._delegate 就是調用這個類或者傳遞請求到子應用。

_match函數

_match 函數的定義如下:

def _match(self, mapping, value):
  for pat, what in mapping:
    if isinstance(what, application):  # 位置1
      if value.startswith(pat):
        f = lambda: self._delegate_sub_application(pat, what)
        return f, None
      else:
        continue
    elif isinstance(what, basestring):  # 位置2
      what, result = utils.re_subm('^' + pat + '$', what, value)
    else:  # 位置3
      result = utils.re_compile('^' + pat + '$').match(value)
    if result: # it's a match
      return what, [x for x in result.groups()]
  return None, None

該函數的參數中 mapping 就是 self.mapping ,是URL路由映射表; value 則是 web.ctx.path ,是本次請求路徑。該函數遍歷 self.mapping ,根據映射關係中處理對象的類型來處理:

  • 位置1 ,處理對象是一個 application 實例,也就是一個子應用,則返回一個匿名函數,該匿名函數會調用 self._delegate_sub_application 進行處理。

  • 位置2 ,如果處理對象是一個字符串,則調用 utils.re_subm 進行處理,這裏會把 value (也就是 web.ctx.path )中的和 pat 匹配的部分替換成 what (也就是我們指定的一個URL模式的處理對象字符串),然後返回替換後的結果以及匹配的項(是一個re.MatchObject實例)。

  • 位置3 ,如果是其他情況,比如直接指定一個類對象作爲處理對象。

如果 result 非空,則返回處理對象和一個參數列表(這個參數列表就是傳遞給我們實現的GET等函數的參數)。

_delegate函數

從 _match 函數返回的結果會作爲參數傳遞給 _delegate 函數:

fn, args = self._match(self.mapping, web.ctx.path)
return self._delegate(fn, self.fvars, args)

其中:

  • fn :是要處理當前請求的對象,一般是一個類名。

  • args :是要傳遞給請求處理對象的參數。

  • self.fvars :是實例化 application 時的全局名稱空間,會用於查找處理對象。

_delegate 函數的實現如下:

def _delegate(self, f, fvars, args=[]):
  def handle_class(cls):
    meth = web.ctx.method
    if meth == 'HEAD' and not hasattr(cls, meth):
      meth = 'GET'
    if not hasattr(cls, meth):
      raise web.nomethod(cls)
    tocall = getattr(cls(), meth)
    return tocall(*args)
  def is_class(o): return isinstance(o, (types.ClassType, type))
  if f is None:
    raise web.notfound()
  elif isinstance(f, application):
    return f.handle_with_processors()
  elif is_class(f):
    return handle_class(f)
  elif isinstance(f, basestring):
    if f.startswith('redirect '):
      url = f.split(' ', 1)[1]
      if web.ctx.method == "GET":
        x = web.ctx.env.get('QUERY_STRING', '')
        if x:
          url += '?' + x
      raise web.redirect(url)
    elif '.' in f:
      mod, cls = f.rsplit('.', 1)
      mod = __import__(mod, None, None, [''])
      cls = getattr(mod, cls)
    else:
      cls = fvars[f]
    return handle_class(cls)
  elif hasattr(f, '__call__'):
    return f()
  else:
    return web.notfound()

這個函數主要是根據參數 f 的類型來做出不同的處理:

  • f 爲空,則返回 302 Not Found .

  • f 是一個 application 實例,則調用子應用的 handle_with_processors() 進行處理。

  • f 是一個類對象,則調用內部函數 handle_class 。

  • f 是一個字符串,則進行重定向處理,或者獲取要處理請求的類名後,調用 handle_class 進行處理(我們寫的代碼一般是在這個分支下被調用的)。

  • f 是一個可調用對象,直接調用。

  • 其他情況返回 302 Not Found .

  • 本文來自:Linux教程網


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章