成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

web.py源碼分析: application(1)

edgardeng / 637人閱讀

摘要:本文主要分析的是庫的這個模塊中的代碼。將結(jié)果轉(zhuǎn)換成一個迭代器。函數(shù)函數(shù)的定義如下位置位置位置該函數(shù)的參數(shù)中就是,是路由映射表則是,是本次請求路徑。位置,如果是其他情況,比如直接指定一個類對象作為處理對象。

本文主要分析的是web.py庫的application.py這個模塊中的代碼??偟膩碚f,這個模塊主要實現(xiàn)了WSGI兼容的接口,以便應(yīng)用程序能夠被WSGI應(yīng)用服務(wù)器調(diào)用。WSGI是Web Server Gateway Interface的縮寫,具體細(xì)節(jié)可以查看WSGI的WIKI頁面

接口的使用 使用web.py自帶的HTTP Server

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

import web

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

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

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

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

URL路由表

一個web.application實例app

調(diào)用app.run()

其中,app.run()的調(diào)用是初始化各種WCGI接口,并啟動一個內(nèi)置的HTTP服務(wù)器和這些接口對接,代碼如下:

def run(self, *middleware):
    return wsgi.runwsgi(self.wsgifunc(*middleware))
與WSGI應(yīng)用服務(wù)器對接

如果你的應(yīng)用要與WSGI應(yīng)用服務(wù)器對接,比如uWSGI,gunicorn等,那么應(yīng)用入口的代碼就要換一種寫法了:

import web

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

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

在這種場景下,應(yīng)用的代碼不需要啟動HTTP服務(wù)器,而是實現(xiàn)一個WSGI兼容的接口供WSGI服務(wù)器調(diào)用。web.py框架為我們實現(xiàn)了這樣的接口,你只需要調(diào)用application = app.wsgifunc()就可以了,這里所得到的application變量就是WSGI接口(后面分析完代碼你就會知道了)。

WSGI接口的實現(xiàn)分析

分析主要圍繞著下面兩行代碼進(jìn)行:

app = web.application(urls, globals())
application = app.wsgifunc()
web.application實例化

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

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

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相關(guān)功能的代碼略去了。其他的代碼主要作了如下幾個事情:

self.init_mapping(mapping):初始化URL路由映射關(guān)系。

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

初始化URL路由映射關(guān)系
def init_mapping(self, mapping):
    self.mapping = list(utils.group(mapping, 2))

這個函數(shù)還調(diào)用了一個工具函數(shù),效果是這樣的:

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

如果用戶初始化時傳遞的元組是這樣的,那么調(diào)用init_mapping之后:

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

后面框架在進(jìn)行URL路由時,就會遍歷這個列表。

添加處理器
    self.add_processor(loadhook(self._load))
    self.add_processor(unloadhook(self._unload))

這兩行代碼添加了兩個處理器:self._loadself._unload,而且還對這兩個函數(shù)進(jìn)行了裝飾。處理器的是用在HTTP請求處理前后的,它不是真正用來處理一個HTTP請求,但是可以用來作一些額外的工作,比如官方教程里面有提到的給子應(yīng)用添加session的做法,就是使用了處理器:

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

app.add_processor(web.loadhook(session_hook))

處理器的定義和使用都是比較復(fù)雜的,后面專門講。

wsgifunc函數(shù)

wsgifunc的執(zhí)行結(jié)果是返回一個WSGI兼容的函數(shù),并且該函數(shù)內(nèi)部實現(xiàn)了URL路由等功能。

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

    return wsgi

除開內(nèi)部函數(shù)的定義,wsgifunc的定義就是這么簡單,如果沒有實現(xiàn)任何中間件,那么就是直接返回其內(nèi)部定義的wsgi函數(shù)。

wsgi函數(shù)

該函數(shù)實現(xiàn)了WSGI兼容接口,同時也實現(xiàn)了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

下面來仔細(xì)分析一下這個函數(shù):

    self._cleanup()
    self.load(env)
    

self._cleanup()內(nèi)部調(diào)用utils.ThreadedDict.clear_all(),清除所有的thread local數(shù)據(jù),避免內(nèi)存泄露(因為web.py框架的很多數(shù)據(jù)都會保存在thread local變量中)。

self.load(env)使用env中的參數(shù)初始化web.ctx變量,這些變量涵蓋了當(dāng)前請求的信息,我們在應(yīng)用中有可能會使用到,比如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]

這一段主要是調(diào)用self.handle_with_processors(),這個函數(shù)會對請求的URL進(jìn)行路由,找到合適的類或子應(yīng)用來處理該請求,也會調(diào)用添加的處理器來做一些其他工作(關(guān)于處理器的部分,后面專門講)。對于處理的返回結(jié)果,可能有三種方式:

返回一個可迭代對象,則進(jìn)行安全迭代處理。

返回其他值,則創(chuàng)建一個列表對象來存放。

如果拋出了一個HTTPError異常(比如我們使用raise web.OK("hello, world")這種方式來返回結(jié)果時),則將異常中的數(shù)據(jù)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進(jìn)行字符串化處理,得到HTTP Response的body部分。然后根據(jù)WSGI的規(guī)范作如下兩個事情:

調(diào)用start_resp函數(shù)。

result結(jié)果轉(zhuǎn)換成一個迭代器。

現(xiàn)在你可以看到,之前我們提到的application = app.wsgifunc()就是將wsgi函數(shù)賦值給application變量,這樣應(yīng)用服務(wù)器就可以采用WSGI標(biāo)準(zhǔn)和我們的應(yīng)用對接了。

處理HTTP請求

前面分析的代碼已經(jīng)說明了web.py框架如何實現(xiàn)WSGI兼容接口的,即我們已經(jīng)知道了HTTP請求到達(dá)框架以及從框架返回給應(yīng)用服務(wù)器的流程。那么框架內(nèi)部是如何調(diào)用我們的應(yīng)用代碼來實現(xiàn)一個請求的處理的呢?這個就需要詳細(xì)分析剛才忽略掉的處理器的添加和調(diào)用過程。

loadhook和unloadhook裝飾器

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

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

這個函數(shù)返回一個函數(shù)processor,它會確保先調(diào)用你提供的處理器函數(shù)h,然后再調(diào)用后續(xù)的操作函數(shù)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

這個函數(shù)也返回一個processor,它會先調(diào)用參數(shù)傳遞進(jìn)來的handler,然后再調(diào)用你提供的處理器函數(shù)。

handle_with_processors函數(shù)
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)

這個函數(shù)挺復(fù)雜的,最核心的部分采用了遞歸實現(xiàn)(我感覺不遞歸應(yīng)該也能實現(xiàn)同樣的功能)。為了說明清晰,采用實例說明。

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

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

所以,現(xiàn)在的self.processors是下面這個樣子的:

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

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

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

函數(shù)執(zhí)行的起點是位置1,調(diào)用其內(nèi)部定義函數(shù)process(processors)。

如果位置2判斷處理器列表不為空,則進(jìn)入if內(nèi)部。

位置3調(diào)用本次需要執(zhí)行的處理器函數(shù),參數(shù)為一個lambda函數(shù),然后返回。

如果位置2判斷處理器列表為空,則執(zhí)行self.handle(),該函數(shù)真正的調(diào)用我們的應(yīng)用代碼(下面會講到)。

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

self.processors = [load_processor, unload_processor]

位置1進(jìn)入代碼后,在位置2會判斷還有處理器要執(zhí)行,會走到位置3,此時要執(zhí)行代碼是這樣的:

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

load_processor函數(shù)是一個經(jīng)過loadhook裝飾的函數(shù),因此其定義在執(zhí)行時是這樣的:

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

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

return unload_processor(lambda: process([]))

unload_processor函數(shù)是一個經(jīng)過unloadhook裝飾的函數(shù),因此其定義在執(zhí)行時是這樣的:

def unload_processor(lambda: process([])):
    try:
        result = process([])  # 參數(shù)傳遞進(jìn)來的lambda函數(shù)
        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

現(xiàn)在會先執(zhí)行process([])函數(shù),并且走到位置4(調(diào)用self.handle()的地方),從而得到應(yīng)用的處理結(jié)果,然后再調(diào)用本處理器的處理函數(shù)self._unload()

總結(jié)一下執(zhí)行的順序:

self._load()

self.handle()

self._unload()

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

handle函數(shù)

講了這么多,才講到真正要調(diào)用我們寫的代碼的地方。在所有的load處理器執(zhí)行完之后,就會執(zhí)行self.handle()函數(shù),其內(nèi)部會調(diào)用我們寫的應(yīng)用代碼。比如返回個hello, world之類的。self.handle的定義如下:

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

這個函數(shù)就很好理解了,第一行調(diào)用的self._match是進(jìn)行路由功能,找到對應(yīng)的類或者子應(yīng)用,第二行的self._delegate就是調(diào)用這個類或者傳遞請求到子應(yīng)用。

_match函數(shù)

_match函數(shù)的定義如下:

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

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

位置1,處理對象是一個application實例,也就是一個子應(yīng)用,則返回一個匿名函數(shù),該匿名函數(shù)會調(diào)用self._delegate_sub_application進(jìn)行處理。

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

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

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

_delegate函數(shù)

_match函數(shù)返回的結(jié)果會作為參數(shù)傳遞給_delegate函數(shù):

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

其中:

fn:是要處理當(dāng)前請求的對象,一般是一個類名。

args:是要傳遞給請求處理對象的參數(shù)。

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

_delegate函數(shù)的實現(xiàn)如下:

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

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

f為空,則返回302 Not Found.

f是一個application實例,則調(diào)用子應(yīng)用的handle_with_processors()進(jìn)行處理。

f是一個類對象,則調(diào)用內(nèi)部函數(shù)handle_class。

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

f是一個可調(diào)用對象,直接調(diào)用。

其他情況返回302 Not Found.

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/37498.html

相關(guān)文章

  • newrelic python agent 源碼分析-1

    摘要:是應(yīng)用性能管理監(jiān)控解決方案提供商。目錄是列出的命令腳本所在目錄。包含文件如下的函數(shù)是命令執(zhí)行的入口。而對于硬件信息的檢測則由進(jìn)行。文檔地址源碼仔細(xì)看下去,太復(fù)雜了。下一篇再分析一個請求到結(jié)束探針工作的完整過程吧。 Newrelic 是APM(Application Performance Management)(應(yīng)用性能管理/監(jiān)控)解決方案提供商。項目中,通常用它來追蹤應(yīng)用的性能。最近...

    szysky 評論0 收藏0
  • tornado源碼解析之IOLoop

    摘要:最大的特點就是其支持異步,所以它有著優(yōu)異的性能。的代碼結(jié)構(gòu)可以在其官網(wǎng)了解,本文著重分析的實現(xiàn)。事件驅(qū)動模型的大致思路的方法用于啟動事件循環(huán)。行文比較草率,如有錯誤和不足之處,敬請指正。 0. 簡介 tornado是一個用Python語言寫成的Web服務(wù)器兼Web應(yīng)用框架,由FriendFeed公司在自己的網(wǎng)站FriendFeed中使用,被Facebook收購以后框架以開源軟件形式開放...

    Lsnsh 評論0 收藏0
  • web.py源碼分析: 模板(1)

    摘要:模板函數(shù)到底長什么樣下面我們就可以來看看模板函數(shù)到底長什么樣了。當(dāng)然,首先得創(chuàng)建一個模板文件??偨Y(jié)通過打印中間結(jié)果和分析代碼,我們已經(jīng)大概知道了的模板是如何轉(zhuǎn)化成內(nèi)容的。下一篇文章會闡述模板的各種語法所對應(yīng)的動態(tài)函數(shù)內(nèi)容。 web.py模板的實現(xiàn)原理 web.py的模板實現(xiàn)利用了Python的可執(zhí)行對象的動態(tài)特性:根據(jù)模板內(nèi)容和渲染函數(shù)的參數(shù)創(chuàng)建一個函數(shù),該函數(shù)執(zhí)行的時候會返回一個Te...

    Rocko 評論0 收藏0
  • web.py源碼分析: 模板(2)

    摘要:上一篇文章源碼分析模板說明了的模板的大致工作原理。本文重點講述模板支持的語法是如何轉(zhuǎn)換生成函數(shù)的。模板的名稱統(tǒng)一是。模板代碼斷行模板內(nèi)容函數(shù)內(nèi)容從結(jié)果來看,模板中的斷行只是為了不再結(jié)果中插入一個多余的換行符而已。 上一篇文章web.py源碼分析: 模板(1)說明了web.py的模板的大致工作原理。本文重點講述web.py模板支持的語法是如何轉(zhuǎn)換生成__template__函數(shù)的。 we...

    figofuture 評論0 收藏0
  • web.py源碼分析: 模板(3)

    摘要:前兩篇文章主要說明了的模板系統(tǒng)將模板文件處理后得到的結(jié)果函數(shù)。生成函數(shù)的代碼這個是模板生成過程中最長最復(fù)雜的一段,會應(yīng)用到的分析功能以及動態(tài)編譯功能。參數(shù)都是一個,表示還未解析的模板內(nèi)容。 前兩篇文章主要說明了web.py的模板系統(tǒng)將模板文件處理后得到的結(jié)果:__template__()函數(shù)。本文主要講述模板文件是如何變成__template__()函數(shù)的。 Render和frende...

    OnlyMyRailgun 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<