日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[翻译]深入理解Tornado——一个异步web服务器

發(fā)布時間:2025/7/14 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [翻译]深入理解Tornado——一个异步web服务器 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
本人的第一次翻譯,轉(zhuǎn)載請注明出處:http://www.cnblogs.com/yiwenshengmei/archive/2011/06/08/understanding_tornado.html

原文地址:http://golubenco.org/?p=16

這篇文章的目的在于對Tornado這個異步服務(wù)器軟件的底層進(jìn)行一番探索。我采用自底向上的方式進(jìn)行介紹,從輪訓(xùn)開始,向上一直到應(yīng)用層,指出我認(rèn)為有趣的部分。

所以,如果你有打算要閱讀Tornado這個web框架的源碼,又或者是你對一個異步web服務(wù)器是如何工作的感興趣,我可以在這成為你的指導(dǎo)。

通過閱讀這篇文章,你將可以:

  • 自己寫一個Comet架構(gòu)程序的服務(wù)器端部分,即使你是從拷貝別人的代碼開始。
  • 如果你想在Tornado框架上做開發(fā),通過這篇文章你將更好的理解Tornado web框架。
  • 在Tornado和Twisted的爭論上,你將更有見解。

介紹

假設(shè)你還不知道Tornado是什么也不知道為什么應(yīng)該對它感興趣,那我將用簡短的話來介紹Tornado這個項目。如果你已經(jīng)對它有了興趣,你可以跳去看下一節(jié)內(nèi)容。

Tornado是一個用Python編寫的異步HTTP服務(wù)器,同時也是一個web開發(fā)框架。該框架服務(wù)于FriendFeed網(wǎng)站,最近Facebook也在使用它。FriendFeed網(wǎng)站有用戶數(shù)多和應(yīng)用實時性強(qiáng)的特點(diǎn),所以性能和可擴(kuò)展性是很受重視的。由于現(xiàn)在它是開源的了(這得歸功于Facebook),我們可以徹底的對它是如何工作的一探究竟。

我覺得對非阻塞式IO (nonblocking IO) 和異步IO (asynchronous IO ?AIO)很有必要談一談。如果你已經(jīng)完全知道他們是什么了,可以跳去看下一節(jié)。我盡可能的使用一些例子來說明它們是什么。

讓我們假設(shè)你正在寫一個需要請求一些來自其他服務(wù)器上的數(shù)據(jù)(比如數(shù)據(jù)庫服務(wù),再比如新浪微博的open api)的應(yīng)用程序,然后呢這些請求將花費(fèi)一個比較長的時間,假設(shè)需要花費(fèi)5秒鐘。大多數(shù)的web開發(fā)框架中處理請求的代碼大概長這樣:

def?handler_request(self,?request):
????answ?
=?self.remote_server.query(request)?#?this?takes?5?seconds
????request.write_response(answ)

如果這些代碼運(yùn)行在單個線程中,你的服務(wù)器只能每5秒接收一個客戶端的請求。在這5秒鐘的時間里,服務(wù)器不能干其他任何事情,所以,你的服務(wù)效率是每秒0.2個請求,哦,這太糟糕了。?

當(dāng)然,沒人那么天真,大部分服務(wù)器會使用多線程技術(shù)來讓服務(wù)器一次接收多個客戶端的請求,我們假設(shè)你有20個線程,你將在性能上獲得20倍的提高,所以現(xiàn)在你的服務(wù)器效率是每秒接受4個請求,但這還是太低了,當(dāng)然,你可以通過不斷地提高線程的數(shù)量來解決這個問題,但是,線程在內(nèi)存和調(diào)度方面的開銷是昂貴的,我懷疑如果你使用這種提高線程數(shù)量的方式將永遠(yuǎn)不可能達(dá)到每秒100個請求的效率。

如果使用AIO,達(dá)到每秒上千個請求的效率是非常輕松的事情。服務(wù)器請求處理的代碼將被改成這樣:

def?handler_request(self,?request):
????self.remote_server.query_async(request,?self.response_received)?????
def?response_received(self,?request,?answ):????#?this?is?called?5?seconds?later
????request.write(answ)

AIO的思想是當(dāng)我們在等待結(jié)果的時候不阻塞,轉(zhuǎn)而我們給框架一個回調(diào)函數(shù)作為參數(shù),讓框架在有結(jié)果的時候通過回調(diào)函數(shù)通知我們。這樣,服務(wù)器就可以被解放去接受其他客戶端的請求了。

然而這也是AIO不太好的地方:代碼有點(diǎn)不直觀了。還有,如果你使用像Tornado這樣的單線程AIO服務(wù)器軟件,你需要時刻小心不要去阻塞什么,因為所有本該在當(dāng)前返回的請求都會像上述處理那樣被延遲返回。

關(guān)于異步IO,比當(dāng)前這篇過分簡單的介紹更好的學(xué)習(xí)資料請看 The C10K problem。

源代碼

該項目由github托管,你可以通過如下命令獲得,雖然通過閱讀這篇文章你也可以不需要它是吧。

git?clone?git://github.com/facebook/tornado.git

在tornado的子目錄中,每個模塊都應(yīng)該有一個.py文件,你可以通過檢查他們來判斷你是否從已經(jīng)從代碼倉庫中完整的遷出了項目。在每個源代碼的文件中,你都可以發(fā)現(xiàn)至少一個大段落的用來解釋該模塊的doc string,doc string中給出了一到兩個關(guān)于如何使用該模塊的例子。

IOLoop模塊

讓我們通過查看ioloop.py文件直接進(jìn)入服務(wù)器的核心。這個模塊是異步機(jī)制的核心。它包含了一系列已經(jīng)打開的文件描述符(譯者:也就是文件指針)和每個描述符的處理器(handlers)。它的功能是選擇那些已經(jīng)準(zhǔn)備好讀寫的文件描述符,然后調(diào)用它們各自的處理器(一種IO多路復(fù)用的實現(xiàn),其實就是socket眾多IO模型中的select模型,在Java中就是NIO,譯者注)。

可以通過調(diào)用add_handler()方法將一個socket加入IO循環(huán)中:

def?add_handler(self,?fd,?handler,?events):
????
"""Registers?the?given?handler?to?receive?the?given?events?for?fd."""
????self._handlers[fd]?
=?handler
????self._impl.register(fd,?events?
|?self.ERROR)

_handlers這個字典類型的變量保存著文件描述符(其實就是socket,譯者注)到當(dāng)該文件描述符準(zhǔn)備好時需要調(diào)用的方法的映射(在Tornado中,該方法被稱為處理器)。然后,文件描述符被注冊到epoll(unix中的一種IO輪詢機(jī)制,貌似,譯者注)列表中。Tornado關(guān)心三種類型的事件(指發(fā)生在文件描述上的事件,譯者注):READ,WRITE 和 ERROR。正如你所見,ERROR是默認(rèn)為你自動添加的。

self._impl是select.epoll()和selet.select()兩者中的一個。我們稍后將看到Tornado是如何在它們之間進(jìn)行選擇的。

現(xiàn)在讓我們來看看實際的主循環(huán),不知何故,這段代碼被放在了start()方法中:

def?start(self):
????
"""Starts?the?I/O?loop.
????The?loop?will?run?until?one?of?the?I/O?handlers?calls?stop(),?which
????will?make?the?loop?stop?after?the?current?event?iteration?completes.
????
"""
????self._running?
=?True
????
while?True:
????[?...?]
????????
if?not?self._running:
????????????
break
????????[?...?]
????????
try:
????????????event_pairs?
=?self._impl.poll(poll_timeout)
????????
except?Exception,?e:
????????????
if?e.args?==?(4,?"Interrupted?system?call"):
????????????????logging.warning(
"Interrupted?system?call",?exc_info=1)
????????????????
continue
????????????
else:
????????????????
raise
????????
#?Pop?one?fd?at?a?time?from?the?set?of?pending?fds?and?run
????????#?its?handler.?Since?that?handler?may?perform?actions?on
????????#?other?file?descriptors,?there?may?be?reentrant?calls?to
????????#?this?IOLoop?that?update?self._events
????????self._events.update(event_pairs)
????????
while?self._events:
????????????fd,?events?
=?self._events.popitem()
????????????
try:
????????????????self._handlers[fd](fd,?events)
????????????
except?KeyboardInterrupt:
????????????????
raise
????????????
except?OSError,?e:
????????????????
if?e[0]?==?errno.EPIPE:
????????????????????
#?Happens?when?the?client?closes?the?connection
????????????????????pass
????????????????
else:
????????????????????logging.error(
"Exception?in?I/O?handler?for?fd?%d",
??????????????????????????????????fd,?exc_info
=True)
????????????
except:
????????????????logging.error(
"Exception?in?I/O?handler?for?fd?%d",
??????????????????????????????fd,?exc_info
=True)

poll()方法返回一個形如(fd: events)的鍵值對,并賦值給event_pairs變量。由于當(dāng)一個信號在任何一個事件發(fā)生前到來時,C函數(shù)庫中的poll()方法會返回EINTR(實際是一個值為4的數(shù)值),所以"Interrupted system call"這個特殊的異常需要被捕獲。更詳細(xì)的請查看man poll。

在內(nèi)部的while循環(huán)中,event_pairs中的內(nèi)容被一個一個的取出,然后相應(yīng)的處理器會被調(diào)用。pipe 異常在這里默認(rèn)不進(jìn)行處理。為了讓這個類適應(yīng)更一般的情況,在http處理器中處理這個異常是一個更好的方案,但是選擇現(xiàn)在這樣處理或許是因為更容易一些。

注釋中解釋了為什么使用字典的popitem()方法,而不是使用更普遍一點(diǎn)的下面這種做法(指使用迭代,譯者注):

for?fd,?events?in?self._events.items():

原因很簡單,在主循環(huán)期間,這個_events字典變量可能會被處理器所修改。比如remove_handler()處理器。這個方法把fd(即文件描述符,譯者注)從_events字典中取出(extracts,意思是取出并從_events中刪除,譯者注),所以即使fd被選擇到了,它的處理器也不會被調(diào)用(作者的意思是,如果使用for迭代循環(huán)_events,那么在迭代期間_events就不能被修改,否則會產(chǎn)生不可預(yù)計的錯誤,比如,明明調(diào)用了remove_handler()方法刪除了某個<fd, handler>鍵值對,但是該handler還是被調(diào)用了,譯者注)。

(意義不大的)循環(huán)結(jié)束技巧

怎么讓這個主循環(huán)停止是很有技巧性的。self._running變量被用來在運(yùn)行時從主循環(huán)中跳出,處理器可以通過調(diào)用stop()方法把它設(shè)置為False。通常情況下,這就能讓主循環(huán)停止了,但是stop()方法還能被一個信號處理器所調(diào)用,所以,如果1)主循環(huán)正阻塞在poll()方法處,2)服務(wù)端沒有接收到任何來自客戶端的請求3)信號沒有被OS投遞到正確的線程中,你將不得不等待poll()方法出現(xiàn)超時情況后才會返回。考慮到這些情況并不時常發(fā)生,還有poll()方法的默認(rèn)超時時間只不過是0.2秒,所以這種讓主循環(huán)停止的方式還算過得去。

但不管怎樣,Tornado的開發(fā)者為了讓主循環(huán)停止,還是額外的創(chuàng)建了一個沒有名字的管道和對應(yīng)的處理器,并把管道的一端放在了輪詢文件描述符列表中。當(dāng)需要停止時,在管道的另一端隨便寫點(diǎn)什么,這能高效率的(意思是馬上,譯者注)喚醒主循環(huán)在poll()方法處的阻塞(貌似Java NIO的Windows實現(xiàn)就用了這種方法,譯者注)。這里節(jié)選了一些代碼片段:

def?__init__(self,?impl=None):
????[...]
????
#?Create?a?pipe?that?we?send?bogus?data?to?when?we?want?to?wake
????#?the?I/O?loop?when?it?is?idle
????r,?w?=?os.pipe()
????self._set_nonblocking(r)
????self._set_nonblocking(w)
????self._waker_reader?
=?os.fdopen(r,?"r",?0)
????self._waker_writer?
=?os.fdopen(w,?"w",?0)
????self.add_handler(r,?self._read_waker,?self.WRITE)
def?_wake(self):
????
try:
????????self._waker_writer.write(
"x")
????
except?IOError:
????????
pass

實際上,上述代碼中存在一個bug:那個只讀文件描述符r,雖然是用來讀的,但在注冊時卻附加上了WRITE類型的事件,這將導(dǎo)致該注冊實際不會被響應(yīng)。正如我先前所說的,用不用專門找個方法其實沒什么的,所以我對他們沒有發(fā)現(xiàn)這個方法不起作用的事實并不感到驚訝。我在mail list中報告了這個情況,但是尚未收到答復(fù)。

定時器

另外一個在IOLoop模塊中很有特點(diǎn)的設(shè)計是對定時器的簡單實現(xiàn)。一系列的定時器會被以是否過期的形式來維護(hù)和保存,這用到了python的bisect模塊:

def?add_timeout(self,?deadline,?callback):
????
"""Calls?the?given?callback?at?the?time?deadline?from?the?I/O?loop."""
????timeout?
=?_Timeout(deadline,?callback)
????bisect.insort(self._timeouts,?timeout)
????
return?timeout

在主循環(huán)中,所有過期了的定時器的回調(diào)會按照過期的順序被觸發(fā)。poll()方法中的超時時間會動態(tài)的進(jìn)行調(diào)整,調(diào)整的結(jié)果就是如果沒有新的客戶端請求,那么下一個定時器就好像沒有延遲一樣的被觸發(fā)(意思是如果沒有新的客戶端的請求,poll()方法將被阻塞直到超時,這個超時時間的設(shè)定會根據(jù)下一個定時器與當(dāng)前時間之間的間隔進(jìn)行調(diào)整,調(diào)整后,超時的時間會等同于距離下一個定時器被觸發(fā)的時間,這樣在poll()阻塞完后,下一個定時器剛好過期,譯者注)。

選擇select方案

讓我們現(xiàn)在快速的看一下poll和select這兩種select方案的實現(xiàn)代碼。Python已經(jīng)在版本2.6的標(biāo)準(zhǔn)庫中支持了epoll,你可以通過在select模塊上使用hasattr()方法檢測當(dāng)前Python是否支持epoll。如果python版本小于2.6,Tornado將用它自己的基于C的epoll模塊。你可以在tornado/epoll.c文件中找到它源代碼。如果最后這也不行(因為epoll不是每個Linux都有的),它將回退到selec._Select并把_EPoll類包裝成和select.epoll一樣的api接口。在你做性能測試之前,請確定你能使用epoll,因為select在有大量文件描述符情況下的效率非常低。

#?Choose?a?poll?implementation.?Use?epoll?if?it?is?available,?fall?back?to
#
?select()?for?non-Linux?platforms
if?hasattr(select,?"epoll"):
????
#?Python?2.6+?on?Linux
????_poll?=?select.epoll
else:
????
try:
????????
#?Linux?systems?with?our?C?module?installed
????????import?epoll
????????_poll?
=?_EPoll
????
except:
????????
#?All?other?systems
????????import?sys
????????
if?"linux"?in?sys.platform:
????????????logging.warning(
"epoll?module?not?found;?using?select()")
????????_poll?
=?_Select

通過上述閱讀,我們的介紹已經(jīng)涵蓋了大部分IOLoop模塊。正如廣告中介紹的那樣,它是一段優(yōu)雅而又簡單的代碼。

從sockets到流

讓我們來看看IOStream模塊。它的目的是提供一個對非阻塞式sockets的輕量級抽象,它提供了三個方法:

  • read_until(),從socket中讀取直到遇到指定的字符串。這為在讀取HTTP頭時遇到空行分隔符自動停止提供了方便。
  • read_bytes(),從socket中讀取指定數(shù)量的字節(jié)。這為讀取HTTP消息的body部分提供了方便。
  • write(),將指定的buffer寫入socket并持續(xù)監(jiān)測直到這個buffer被發(fā)送。
所有上述的方法都可以通過異步方式在它們完成時觸發(fā)回調(diào)函數(shù)。

write()方法提供了將調(diào)用者提供的數(shù)據(jù)加以緩沖直到IOLoop調(diào)用了它的(指write方法的,譯者注)處理器的功能,因為到那時候就說明socket已經(jīng)為寫數(shù)據(jù)做好了準(zhǔn)備:

def?write(self,?data,?callback=None):
????
"""Write?the?given?data?to?this?stream.
????If?callback?is?given,?we?call?it?when?all?of?the?buffered?write
????data?has?been?successfully?written?to?the?stream.?If?there?was
????previously?buffered?write?data?and?an?old?write?callback,?that
????callback?is?simply?overwritten?with?this?new?callback.
????
"""
????self._check_closed()
????self._write_buffer?
+=?data
????self._add_io_state(self.io_loop.WRITE)
????self._write_callback?
=?callback

該方法只是用socket.send()來處理WRITE類型的事件,直到EWOULDBLOCK異常發(fā)生或者buffer被發(fā)送完畢。

讀數(shù)據(jù)的方法和上述過程正好相反。讀事件的處理器持續(xù)讀取數(shù)據(jù)直到緩沖區(qū)被填滿為止。這就意味著要么讀取指定數(shù)量的字節(jié)(如果調(diào)用的是read_bytes()),要么讀取的內(nèi)容中包含了指定的分隔符(如果調(diào)用的是read_util()):

def?_handle_read(self):
????
try:
????????chunk?
=?self.socket.recv(self.read_chunk_size)
????
except?socket.error,?e:
????????
if?e[0]?in?(errno.EWOULDBLOCK,?errno.EAGAIN):
????????????
return
????????
else:
????????????logging.warning(
"Read?error?on?%d:?%s",
????????????????????????????self.socket.fileno(),?e)
????????????self.close()
????????????
return
????
if?not?chunk:
????????self.close()
????????
return
????self._read_buffer?
+=?chunk
????
if?len(self._read_buffer)?>=?self.max_buffer_size:
????????logging.error(
"Reached?maximum?read?buffer?size")
????????self.close()
????????
return
????
if?self._read_bytes:
????????
if?len(self._read_buffer)?>=?self._read_bytes:
????????????num_bytes?
=?self._read_bytes
????????????callback?
=?self._read_callback
????????????self._read_callback?
=?None
????????????self._read_bytes?
=?None
????????????callback(self._consume(num_bytes))
????
elif?self._read_delimiter:
????????loc?
=?self._read_buffer.find(self._read_delimiter)
????????
if?loc?!=?-1:
????????????callback?
=?self._read_callback
????????????delimiter_len?
=?len(self._read_delimiter)
????????????self._read_callback?
=?None
????????????self._read_delimiter?
=?None
????????????callback(self._consume(loc?
+?delimiter_len))

如下所示的_consume方法是為了確保在要求的返回值中不會包含多余的來自流的數(shù)據(jù),并且保證后續(xù)的讀操作會從當(dāng)前字節(jié)的下一個字節(jié)開始(先將流中的數(shù)據(jù)讀到self.read_buffer中,然后根據(jù)要求進(jìn)行切割,返回切割掉的數(shù)據(jù),保留切割后的數(shù)據(jù)供下一次的讀取,譯者注):

def?_consume(self,?loc):
????result?
=?self._read_buffer[:loc]
????self._read_buffer?
=?self._read_buffer[loc:]
????
return?result

還值得注意的是在上述_handle_read()方法中read buffer的上限——self.max_buffer_size。默認(rèn)值是100MB,這似乎對我來說是有點(diǎn)大了。舉個例子,如果一個攻擊者和服務(wù)端建立了100個連接,并持續(xù)發(fā)送不帶頭結(jié)束分隔符的頭信息,那么Tornado需要10GB的內(nèi)存來處理這些請求。即使內(nèi)存ok,這種數(shù)量級數(shù)據(jù)的復(fù)制操作(比如像上述_consume()方法中的代碼)很可能使服務(wù)器超負(fù)荷。我們還注意到在每次迭代中_handle_read()方法是如何在這個buffer中搜索分隔符的,所以如果攻擊者以小塊形式發(fā)送大量的數(shù)據(jù),服務(wù)端不得不做很多次搜索工作。歸根結(jié)底,你應(yīng)該想要將這個參數(shù)和諧掉,除非你真的很希望那樣(Bottom of line, you might want to tune this parameter unless you really expect requests that big 不大明白怎么翻譯,譯者注)并且你有足夠的硬件條件。

HTTP 服務(wù)器

有了IOLoop模塊和IOStream模塊的幫助,寫一個異步的HTTP服務(wù)器只差一步之遙,這一步就在httpserver.py中完成。

HTTPServer類它自己只負(fù)責(zé)處理將接收到的新連接的socket添加到IOLoop中。該監(jiān)聽型的socket自己也是IOLoop的一部分,正如在listen()方法中見到的那樣:

def?listen(self,?port,?address=""):
????
assert?not?self._socket
????self._socket?
=?socket.(socket.AF_INET,?socket.SOCK_STREAM,?0)
????flags?
=?fcntl.fcntl(self._socket.fileno(),?fcntl.F_GETFD)
????flags?
|=?fcntl.FD_CLOEXEC
????fcntl.fcntl(self._socket.fileno(),?fcntl.F_SETFD,?flags)
????self._socket.setsockopt(socket.SOL_SOCKET,?socket.SO_REUSEADDR,?
1)
????self._socket.setblocking(0)
????self._socket.bind((address,?port))
????self._socket.listen(
128)
????self.io_loop.add_handler(self._socket.fileno(),?self._handle_events,
?????????????????????????????self.io_loop.READ)

除了綁定給定的地址和端口外,上述代碼還設(shè)置了"close on exec"和"reuse address"這兩個標(biāo)志位。前者在應(yīng)用程序創(chuàng)建子進(jìn)程的時候特別有用。在這種情況下,我們不想讓套接字保持打開的狀態(tài)(任何設(shè)置了"close on exec"標(biāo)志位的文件描述符,都不能被使用exec函數(shù)方式創(chuàng)建的子進(jìn)程讀寫,因為該文件描述符在exec函數(shù)調(diào)用前就會被自動釋放,譯者注)。后者用來避免在服務(wù)器重啟的時候發(fā)生“該地址以被使用”這種錯誤時很有用。

正如你所見到的,后備連接所允許的最大數(shù)目是128(注意,listen方法并不是你想象中的“開始在128端口上監(jiān)聽”的意思,譯者注)。這意味著如果有128個連接正在等待被accept,那么直到服務(wù)器有時間將前面128個連接中的某幾個accept了,新的連接都將被拒絕。我建議你在做性能測試的時候?qū)⒃搮?shù)調(diào)高,因為當(dāng)新的連接被拋棄的時候?qū)⒅苯佑绊懩阕鰷y試的準(zhǔn)確性。

在上述代碼中注冊的_handle_events()處理器用來accept新連接,并創(chuàng)建相關(guān)的IOStream對象和初始化一個HTTPConnection對象,HTTPConnection對象負(fù)責(zé)處理剩下的交互部分:

def?_handle_events(self,?fd,?events):
????
while?True:
????????
try:
????????????connection,?address?
=?self._socket.accept()
????????
except?socket.error,?e:
????????????
if?e[0]?in?(errno.EWOULDBLOCK,?errno.EAGAIN):
????????????????
return
????????????
raise
????????
try:
????????????stream?
=?iostream.IOStream(connection,?io_loop=self.io_loop)
????????????HTTPConnection(stream,?address,?self.request_callback,
???????????????????????????self.no_keep_alive,?self.xheaders)
????????
except:
????????????logging.error(
"Error?in?connection?callback",?exc_info=True)

可以看到這個方法在一次迭代中accept了所有正在等待處理的連接。也就是說直到EWOULDBLOCK異常發(fā)生while True循環(huán)才會退出,這也就意味著當(dāng)前沒有需要處理accept的連接了。

HTTP頭的部分的解析工作開始于HTTPConnection類的構(gòu)造函數(shù)__init()__():

def?__init__(self,?stream,?address,?request_callback,?no_keep_alive=False,
?????????????xheaders
=False):
????self.stream?
=?stream
????self.address?
=?address
????self.request_callback?
=?request_callback
????self.no_keep_alive?
=?no_keep_alive
????self.xheaders?
=?xheaders
????self._request?
=?None
????self._request_finished?
=?False
????self.stream.read_until(
"rnrn",?self._on_headers)

如果你很想知道xheaders參數(shù)的意義,請看這段注釋:

如果xheaders為True,我們將支持把所有請求的HTTP頭解析成X-Real-Ip和X-Scheme格式,而原先我們將HTTP頭解析成remote?IP和HTTP?scheme格式。這種格式的HTTP頭在Tornado運(yùn)行于反向代理或均衡負(fù)載服務(wù)器的后端時將非常有用。

_on_headers()回調(diào)函數(shù)實際用來解析HTTP頭,并在有請求內(nèi)容的情況下通過使用read_bytes()來讀取請求的內(nèi)容部分。_on_request_body()回調(diào)函數(shù)用來解析POST的參數(shù)并調(diào)用應(yīng)用層提供的回調(diào)函數(shù):

def?_on_headers(self,?data):
????eol?
=?data.find("rn")
????start_line?
=?data[:eol]
????method,?uri,?version?
=?start_line.split("?")
????
if?not?version.startswith("HTTP/"):
????????
raise?Exception("Malformed?HTTP?version?in?HTTP?Request-Line")
????headers?
=?HTTPHeaders.parse(data[eol:])
????self._request?
=?HTTPRequest(
????????connection
=self,?method=method,?uri=uri,?version=version,
????????headers
=headers,?remote_ip=self.address[0])
????content_length?
=?headers.get("Content-Length")
????
if?content_length:
????????content_length?
=?int(content_length)
????????
if?content_length?>?self.stream.max_buffer_size:
????????????
raise?Exception("Content-Length?too?long")
????????
if?headers.get("Expect")?==?"100-continue":
????????????self.stream.write(
"HTTP/1.1?100?(Continue)rnrn")
????????self.stream.read_bytes(content_length,?self._on_request_body)
????????
return
????self.request_callback(self._request)
def?_on_request_body(self,?data):
????self._request.body?
=?data
????content_type?
=?self._request.headers.get("Content-Type",?"")
????
if?self._request.method?==?"POST":
????????
if?content_type.startswith("application/x-www-form-urlencoded"):
????????????arguments?
=?cgi.parse_qs(self._request.body)
????????????
for?name,?values?in?arguments.iteritems():
????????????????values?
=?[v?for?v?in?values?if?v]
????????????????
if?values:
????????????????????self._request.arguments.setdefault(name,?[]).extend(
????????????????????????values)
????????
elif?content_type.startswith("multipart/form-data"):
????????????boundary?
=?content_type[30:]
????????????
if?boundary:?self._parse_mime_body(boundary,?data)
????self.request_callback(self._request)

將結(jié)果寫回客戶端的工作在HTTPRequest類中處理,你可以在上面的_on_headers()方法中看到具體的實現(xiàn)。HTTPRequest類僅僅將寫回的工作代理給了stream對象。

def?write(self,?chunk):
????
assert?self._request,?"Request?closed"
????self.stream.write(chunk,?self._on_write_complete)

未完待續(xù)?

通過這篇文章,我已經(jīng)涵蓋了從socket到應(yīng)用層的所有方面。這應(yīng)該能給你關(guān)于Tornado是如何工作的一個清晰的理解。總之,我認(rèn)為Tornado的代碼是非常友好的,我希望你也這樣認(rèn)為。

Tornado框架還有很大一部分我們沒有探索,比如wep.py(應(yīng)該是web.py,譯者注)這個實際與你應(yīng)用打交道的模塊,又或者是template engine模塊。如果我有足夠興趣的話,我也會介紹這些部分。可以通過訂閱我的RSS feed來鼓勵我。

轉(zhuǎn)載于:https://www.cnblogs.com/yiwenshengmei/archive/2011/06/08/understanding_tornado.html

總結(jié)

以上是生活随笔為你收集整理的[翻译]深入理解Tornado——一个异步web服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。