Twisted Network Programming Essentials(中文渣翻)

第一章 介紹Twisted

1.1 開始

在你會用Twisted創建app以前,你須要下載安裝Twisted和它的環境。這一章的主要任務就是幫助你學會安裝Twisted。html

Twisted須要python2.6或者2.7。支持python3的版本還在構建中。python

安裝Twisted

首先:你須要下載Twisted。下載和說明以及相應的版本均可以在https://twistedmatrix.com/trac/找到(建議各位去官網看看)。想要啓動Twisted裏面的其餘功能,你須要安裝一些額外的包。react

linux上安裝linux

全部流行的Linux發佈版本都內置了一個python-twisted安裝包和對應版本的環境支持。若是要在dpkg-based系統上安裝,用git

  apt-get install python-twistedgithub

正在rmp-based系統,用web

  yum install python-twisted數據庫

就這麼簡單。django

若是你要使用Twisted的SSL或者SSH功能,能夠用python-openssl和python-crypto來獲取。編程

Windows上安裝

Twisted對於Windows有32位和64位,若是你不肯定,就安裝32位的。

去官網上下載Twisted吧https://twistedmatrix.com/trac/。

測試你的Twisted

爲了驗證你的Twisted沒有損壞,在python下這樣寫

import twisted

print(twisted.__version__)

驗證你已經安裝了pyOpenSSL來使用Twisted的SSL功能,用如下代碼測試

import OpenSSL
import twisted.internet.ssl
twisted.internet.ssl.SSL

若是你沒有看到報錯,你已經成功地爲你的Twisted添加了SSL支持。

若是你爲了使用Twisted的SSH已經安裝了PyCrypto,能夠這樣驗證

import Crypto
import twisted.conch.ssh.transport
twisted.conch.ssh.transport.md5

若是沒有看到錯誤就一切OK。

恭喜你,你已經知道了怎麼安裝Twisted開始你的編程之路了。

1.2創建一個基礎的客戶端和服務器

學習Twisted應用的最好方法就是實踐一些小例子。這一章會爲你介紹reactor循環,信息傳輸和在客戶端服務器上執行TCP協議。

A 基於TCP的回顯客戶端和服務器

請瀏覽一下examples2-1和2-2。服務器創建了TCO鏈接並且監聽了固定端口,並且爲它受到的全部信息進行回顯。客戶端負責鏈接服務器,發送消息,接受迴應和斷開鏈接。

#Example 2-1 echoserver.py
from twisted.internet import protocol,reactor

class Echo(protocol.Protocol):
def dataReceived(self, data):
print('receive: ',data)
self.transport.write(data)

class EchoFactory(protocol.Factory):
def buildProtocol(self, addr):
print('addr:',addr)
return Echo()

reactor.listenTCP(8001,EchoFactory())
reactor.run()
#Example 2-2 echoclient.py
from twisted.internet import reactor,protocol

class EchoClient(protocol.Protocol):
def connectionMade(self):
print('send msg')
self.transport.write(b"Hello,world!")
def dataReceived(self, data):
print('receive msg:',data)
self.transport.loseConnection()

class EchoClientFactory(protocol.ClientFactory):
def buildProtocol(self, addr):
return EchoClient()
def clientConnectionLost(self, connector, reason):
print("conn lost")
reactor.stop()
def clientConnectionFailed(self, connector, reason):
print('conn failed')
reactor.stop()
reactor.connectTCP("127.0.0.1",8001,EchoClientFactory())
reactor.run()

爲了測試這兩個腳本,首先在命令行裏運行echoserver.py。這樣就會在本地8001端口上運行一個TCP服務。以後在第二個命令行裏運行echoclient.py。

addr: IPv4Address(type='TCP', host='127.0.0.1', port=61264)
receive:  b'Hello,world!'
send msg
receive msg: b'Hello,world!'
conn lost

啊哈,你已經完成了你的第一個異步事件驅動的Twisted應用。讓咱們看看它的每一步的具體實現是怎樣的吧。

事件驅動程序

回顯服務器和客戶端都是事件驅動程序,更流行的說法是,Twistd是一個異步驅動引擎,這是什麼意思呢?

在一個異步驅動程序裏,程序運行由外部事件驅動。最明顯的特徵是每當事件來臨經過一個loop循環和使用callback來觸發行爲。把這個結構和其餘兩種相同的模型相比:單線程和多線程。

經過執行任務來顯示三個模型的不一樣。程序有三個任務須要完成,每個任務都須要等待IO結束,在這以前會阻塞。

在單線程裏,任務是線性執行的。若是一個任務阻塞,全部的人物都必須等待,這顯然是糟糕的方法。

在多線程裏,三個阻塞任務在三個線程裏執行,能夠在一個或者多個處理器上運行。這樣容許一些線程阻塞,另外一些運行,顯然比單線程有效率。然而,必須編寫代碼維護多個線程併發訪問共享資源,不然會出現一些莫名其妙的BUG。

異步版本把三個任務交叉在一個線程內,在執行IO或者其餘耗時的操做時,會經過loop回調註冊的事件,在IO完成的時候繼續執行。事件雲鬟輪詢事件,而且在事件來臨的時候分發給等待着他們的回調函數。這容許程序在不使用額外線程的狀況下取得進展。

異步驅動同時享受了多線程的並行和單線程的簡單操做。

Reactor反應堆

Twisted的核心其實就是反應堆事件輪詢。reactor知曉網絡,文件系統和計時器的事件。它會等待而且分發這些事件給相應的處理器。Twisted負責把特定的行爲抽象化,而且正確地使用底層的非阻塞API。Twisted存在一個通用接口來讓網絡堆棧中任何位置的事件很容易地得到相應。

reactor本質

while True:
    timeout=time_until_next_timed_event()
    events=wait_for_events(timeout)
    events+=timed_events_until(now())
    for event in events:
        event.process()

在咱們上邊編寫的客戶端和服務器中,reactor經過監聽TCP和鏈接TCP來負責註冊回調,以便在8001端口上能夠從TCP套接字讀取數據時候獲得通知。

在這些回調被註冊以後,咱們開始了反應堆的事件循環reactor.run()。一旦開始,reactor就會調度事件一直到獲得反映或者一直運行下去,除非你stop它。

Transports通訊

一個transport表明着網絡兩端的鏈接。Transports展現了鏈接的細節:舉個例子,這是面向tcp,udp,unix套接字換你是串行接口的實例?Transports執行了ITransport接口,它有如下方法:

write

  以非阻塞的方式把數據寫入物理鏈接

writeSequence

  以字符串列表的方式寫入物理鏈接。在使用面向行的協議的時候有效。

loseConnection

  寫入全部數據,以後斷開鏈接

getPeer

  得到鏈接端的地址

getHost

  和getPeer同樣,可是返回的是本地的地址

在回顯的例子裏,兩端發送數據用了write方法。客戶端在接受到消息以後終止了連接。

Protocols

protocols展示了怎樣異步運行程序。Twisted內置了許多流行的協議,包括HTTP,TELNET,DNS,IMAP。協議執行了IProtocol接口,它有如下方法:

makeConnection

  經過兩端的傳輸建立一個鏈接

connectionMade

  鏈接到另外一個端點的時候調用

dataReceived

  接收到數據的時候調用

connectionLost

  斷開鏈接的時候調用

在咱們的回顯例子裏用了protocol.Protocol做爲基類。connectTCP建立了一個TCP鏈接並且爲接收數據註冊了回調方法。

Protocol Factories

持久性的數據被保存在工廠裏,它繼承protocol.Factory或者ClientFactory。裏邊的buildrotocol方法爲每個新的鏈接建立一個協議,並且被註冊到reactor裏邊。

對工廠和協議的解耦讓一個類型的transport擁有許多協議成爲可能,並且便於測試。

 A TCP諺語服務器和客戶端

讓咱們再次寫一些複雜代碼來討論前面的核心思想

Eample2-3的諺語服務器添加了一個初始諺語。從客戶端接收消息,以後會發送給客戶端它以前發送的諺語。

Example2-4建立了TCP鏈接,每個都會增長服務器的隊列數。

#2-3
from twisted.internet.protocol import Factory,connectionDone
from twisted.internet import reactor,protocol

class QuoteProtocol(protocol.Protocol):
    def __init__(self,factory):
        self.factory=factory
    def connectionMade(self):
        self.factory.numConnection+=1
    def dataReceived(self, data):
        print("Number %d connection"%self.factory.numConnection)
        print("Receive:%s Sending:%s"%(data,self.getQuote()))
        self.transport.write(self.getQuote())
        self.updateQuote(data)
    def connectionLost(self, reason=connectionDone):
        self.factory.numConnection-=1
    def getQuote(self):
        return self.factory.quote
    def updateQuote(self,quote):
        self.factory.quote=quote

class QuoteFactory(Factory):
    numConnection=0
    def __init__(self,quote=None):
        self.quote=quote or b"Hello world"
    def buildProtocol(self, addr):
        return QuoteProtocol(self)
reactor.listenTCP(8080,QuoteFactory())
reactor.run()
#2-4
from twisted.internet import reactor,protocol
class QuoteProtocol(protocol.Protocol):
    def __init__(self,factory):
        self.factory=factory
    def connectionMade(self):
        self.sendQuote()
    def sendQuote(self):
        self.transport.write((self.factory.quote).encode('ascii'))
    def dataReceived(self, data):
        print("Received quote:",data)
        self.transport.loseConnection()

class QuoteClientFactory(protocol.ClientFactory):
    def __init__(self,quote):
        self.quote=quote
    def buildProtocol(self, addr):
        return QuoteProtocol(self)
    def clientConnectionFailed(self, connector, reason):
        print("conn failed :",reason)
        maybeStopReactor()
    def clientConnectionLost(self, connector, reason):
        print("conn lose:",reason)
        maybeStopReactor()
def maybeStopReactor():
    global quote_counter
    quote_counter-=1
    if not quote_counter:
        reactor.stop()
quotes=[
    "You snooze you lose",
    'The early bird gets the worm',
    'Carpe diem'
]
quote_counter=len(quotes)
for quote in quotes:
    reactor.connectTCP("localhost",8080,QuoteClientFactory(quote))
reactor.run()

分別在兩個命令行裏開啓服務器和客戶端,你會看到以下狀況

Number 2 connection
Receive:b'You snooze you lose' Sending:b'Hello world'
Number 3 connection
Receive:b'The early bird gets the worm' Sending:b'You snooze you lose'
Number 1 connection
Receive:b'Carpe diem' Sending:b'The early bird gets the worm'

Received quote: b'Hello world'
conn lose: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]
Received quote: b'You snooze you lose'
conn lose: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]
Received quote: b'The early bird gets the worm'
conn lose: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]

上邊的例子很突出地展現了 Twisted在c/s服務中的一些特色。

1.持久性的配置在工廠裏。

由於每個新的鏈接都會創建一個協議實例,協議不能包含持久的配置,必須用到工廠裏的信息。在每個服務中,當前的鏈接數被i存儲在numConnections裏。通常狀況下,工廠的buildProtocol方法不會作任何事,只會返回一個協議的實例。在一些簡單的例子裏,Twisted提供了捷徑,只須要定義協議類就能夠了,默認會執行BuildProtocol。以下:

from twisted.internet import protocol
class QuteFactory(protocol.Factory):
    numConnection=0
    protocol=QuoteProtocol
    
    def __init__(self,quote=None):
        pass

2.協議能夠檢索鏈接被中斷的緣由

緣由能夠經過clientConnectionLOst和clientConnectionFailed得到。

3.客戶端能夠同時鏈接到服務器

爲了作到這個行爲,這隻須要反覆調用connectTCP,就像上邊的例子。

協議狀態機

協議有許多的狀態並且能夠被展現給客戶端。Example2-5是一個聊天服務器,它執行了一個小的狀態機。繼承了LineReceiver類,這個類是一個很便捷的類,它讓按行讀取變得很容易。使用這個line協議的時候,客戶端須要在發送的信息後邊加上換行符\n。

#2-5
from twisted.internet import reactor,protocol
from twisted.protocols.basic import LineReceiver

class ChatProtocol(LineReceiver):
    def __init__(self,factory):
        self.factory=factory
        self.name=None
        self.state='REGISTER'
    def connectionMade(self):
        self.sendLine(b"What is your name?")
    def connectionLost(self, reason=protocol.connectionDone):
        if self.name in self.factory.users:
            del self.factory.users[self.name]
            self.broadcastMessage("%s has left the channel"%(self.name,))
    def lineReceived(self, line):
        if self.state=='REGISTER':
            self.handle_REGISTER(line)
        else:
            self.handle_CHAT(line)
    def handle_REGISTER(self,name):
        if name in self.factory.users:
            self.sendLine(b"Name token,choose another")
            return
        self.sendLine(b"welcome %s"%name)
        self.broadcastMessage("%s has joined the channel"%name)
        self.name=name
        self.factory.users[name]=self
        self.state="CHAT"
    def handle_CHAT(self,message):
        message="<%s>%s"%(self.name,message)
        self.broadcastMessage(message)
    def broadcastMessage(self,message):
        for name,protocol in self.factory.users.iteritems():
            if protocol!=self:
                protocol.sendLine(message)

class ChatFactory(protocol.Factory):
    def __init__(self):
        self.users={}
    def buildProtocol(self, addr):
        return ChatProtocol(self)
reactor.listenTCP(8001,ChatFactory())
reactor.run()

開啓這個服務器,再開啓三個客戶端,你會看到以下

如你所見,回顯,諺語,聊天服務器都是很是類似的!共享的方面有:

  1. 定義一個protocol類,繼承LineReceiver或者Protocol
  2. 定義一個工廠類,繼承Factory或者ClientFactory。爲每個鏈接建立一個協議實例。
  3. 客戶端使用reactor.connectTCP來初始化一個服務器鏈接。每當新數據經過套接字到達而進行處理的時候,調用connecttTCP註冊回調來告知您的協議。
  4. 通訊不會開始,除非你reactor.run()。這回開啓反應堆循環。

若是想了解其餘的,能夠上官網查看其餘實例。

1.3使用Deferreds編寫異步代碼

回調是事件驅動編程的基礎,也是reactor通知應用程序事件到達的凡是。隨着事件驅動程序的發展,爲程序的事件處理進行成功或者失敗的案例會愈來愈複雜。沒有註冊適當的回調會讓程序阻塞從而永遠不會發生事件處理,而且錯誤可能經過應用程序從網絡堆棧向上傳遞。

Twisted提供了優雅的處理回調方法Deferred。這一章會爲你提供一些練習。

有必要消除你的一些誤解:

  • Deferreds不會幫你寫異步代碼
  • Deferreds不會自動讓代碼異步或者不阻塞。爲了得到異步功能,它須要註冊回調。

Deferred對象的結構

Deferreds有一些callback鏈,一個對成功,一個對失敗。Deferreds開始的時候有兩個空的chains。你須要題啊尿一對callbacks和errbacks來讓Deferred能夠運行。當一個異步結果出來,Deferred在「開火」的狀態,回調會被喚醒。

Example3-1建立了一個回調並且使用了addCallback方法來註冊mycallback。d.callback讓d開始運行而且喚醒回調鏈,它只包含了mycallback。

Example3-1

from twisted.internet.defer import Deferred

def mycallback(result):
    print(result)

d=Deferred()
d.addCallback(mycallback)
d.callback("ok")

Example3-2建立了一個Defered d並且使用了addErrback方法來註冊到errback鏈條

Example3-2

from twisted.internet.defer import Deferred

def myerrback(failure):
    print(failure)

d=Deferred()
d.addErrback(myerrback)
d.errback("errback")

一個異步的事件或許會有許多步,每個都須要一對callbacks和errbacks。舉個例子,一個web請求或許須要被髮序列化,格式化,而後數據庫插入,這些步驟的每一步均可能失敗。Deferreds使得在一個地方管理這些多級成功失敗變得容易。

要使用延遲註冊多個級別的回調和回退,只須要按照你但願使用addCallback和addErrback調用他們的順序來把他們附加到回調鏈上。如Example3-3.一個Deferred回調鏈返回的成功或者錯誤結果會做爲i第一個參數傳遞給下一個回調。

from twisted.internet.defer import Deferred

def addbold(res):
    return "<b>%s</b>"%res
def addital(res):
    return "<i>%s</i>"%res
def prinHtml(res):
    print(res)

d=Deferred()
d.addCallback(addbold)
d.addCallback(addital)
d.addCallback(prinHtml)
d.callback("hello")

<i><b>hello</b></i>

請注意,在addCallback註冊回調也會爲該級別的errback鏈條註冊一個經過。相似,向addErrback註冊一個errback也會爲回調鏈的同級別註冊一個經過。鏈子的長度是同樣的。

Deferreds也提供了同時註冊的方法,以下:

from twisted.internet.defer import Deferred

d=Deferred()
d.addCallbacks(mycallback,myerrback)
d.callback("come on")

在reactor使用回調

既然咱們已經學會了在reactor以外使用回調,讓咱們學學在裏邊怎麼用吧。

Example3-4檢索標題,而後對他進行處理,要麼將他轉化爲HTML,而後打印他;要麼由於標題太長,把錯誤打印出來。

from twisted.internet.defer import Deferred
from twisted.internet import reactor

class HandlineRetriever:
def processHeadline(self,handline):
print('執行process')
if len(handline)>50:
self.d.errback(
"the headline is too long"
)
else:
self.d.callback(handline) #開始回調1
def _toHTML(self,res):
print('執行html')
return "<h1>%s</h1>"%res
def getHeadline(self,input):
self.d=Deferred()
self.d.addCallback(self._toHTML) #回調2
reactor.callLater(5, self.processHeadline, input) #5秒後調用processHeadline
return self.d #返回數據
def printData(res): #回調3
print('打印數據')
print(res)
reactor.stop()
def printError(failure):
print(failure)
reactor.stop()
h=HandlineRetriever()
d=h.getHeadline("Breaking news:Twisted")
d.addCallbacks(printData,printError)
reactor.run()

由於咱們提供的例子小於50長,因此HeadlineReteriver觸發回調鏈,調用_toHTML,而後調用printData,打印標題。

Example3-4使用了一個強大的reactor方法叫作callLater,這樣你就能夠按照計劃執行事件。

當咱們用下邊的命令替換反應器的前三行會發生什麼?

h=HandlineRetriever()
d=h.getHeadline("123456789"*6)
d.addCallbacks(printData,printError)
執行process
too long
[Failure instance: Traceback (failure with no frames): <class '__main__.myerr'>: too long
]

這個版本中,HeadlineRetriver遇到了headline太長的狀況而後觸發了errback鏈:對於第一個_toHTML會經過,第二個錯誤會觸發。

練習:這些Derfered鏈怎麼工做?

from twisted.internet.defer import Deferred

def callback1(res):
print('call 1 ',res)
return res
def callback2(res):
print('call 2 ',res)
return res
def callback3(res):
raise Exception('call 3')
def errback1(fail):
print('err 1 ',fail)
return fail
def errback2(fail):
raise Exception('err 2')
def errback3(fail):
print('err 3 ',fail)
return "every thing is fin now"
d=Deferred()
d.addCallback(callback1)
d.addCallback(callback2)
d.addCallback(callback3)
d.callback("test")
d=Deferred()
d.addCallback(callback1)
d.addCallback(callback2)
d.addCallback(callback3)
d.addErrback(errback3)
d.callback("test")
d=Deferred()
d.addErrback(errback1)
d.errback("test")
class myerr(BaseException):
    def __init__(self,err):
        print(err)
d=Deferred()
d.addErrback(errback1)
d.errback(myerr("test"))

addCallbacks內幕

雖然你已經練習了一些例子,可是有一個微妙的點須要指出來:addCallbacks不一樣於addCallback和addErrback的調用順序。

什麼不一樣?

addCallbacks

  註冊一個回調在回調鏈,註冊一個回退在回退鏈,是一個水平的。

addCallback

  註冊一個回調在回調鏈並且本水平的回退鏈會自動pass

addErrback

  註冊一個回退在回退鏈並且本水平的回調會自動pass

換句話說,使用addCallbacks註冊的回調和錯誤不會交互。addCallbacks的異常處理不能處理回調的錯誤:在回調鏈N級別的錯誤的異常由N+1回退處理。

Defereds的關鍵點

這一節重申了一些關鍵的點:

1.Deferred經過callback或者errback開始運行

2.Deferred只能夠被執行一次。嘗試執行第二次會獲得一個已執行的錯誤

3.在callback鏈條中級別N發生的錯誤,會在錯誤鏈條的級別N+1處理。

若是一個callback或者errback產生了一個異常或者返回了錯誤在級別N,那麼級別N+1的errback就會被執行。若是沒有errback程序會中斷而且出現錯誤。

若是級別N沒有引起異常或者返回錯誤,程序會運行到N+1.若是一個errback沒有產生錯誤,控制器會回到callback鏈條。

4.callback的結果做爲該鏈條的第一個參數傳遞。這就是容許對結果進行鏈接處理的緣由。不要忘記返回回調結果。

5.若是傳遞給errback的對象沒有失敗,那麼他首先會包裝。這包括觸發Deferred傳遞給回退鏈的對象和回調引起的異常,回調把控制切換到回退處理。

addBoth

把相同的callback天即到callback和errback。相似的邏輯是異常捕捉try/except。

1.4網絡服務

from twisted.protocols import basic
from twisted.internet import protocol,reactor

class HTTPEchoProtocol(basic.LineReceiver):
    def __init__(self):
        self.lines=[]
    def lineReceived(self, line):
        if not line:
            self.sendResponse()
        self.lines.append(line.decode('ascii'))
    def sendResponse(self):
        self.sendLine("HTTP/1.1 200 OK".encode('ascii'))
        self.sendLine("".encode('ascii'))
        responseBody="You said:\r\n\r\n"+'\r\n'.join(self.lines)
        self.transport.write(responseBody.encode('ascii'))
        self.transport.loseConnection()
class HTTPECHFactory(protocol.ServerFactory):
    def buildProtocol(self, addr):
        return HTTPEchoProtocol()

reactor.listenTCP(8002,HTTPECHFactory())
reactor.run()

解析HTTP請求

HTTP請求由twisted.web.http.Request表示。咱們能夠經過子類http.Request並重寫它的流程來處理。下邊的例子繼承了http.Request來獲取資源中的一個:一個HTML頁面和一個avout頁面,一個404頁面。

from twisted.internet import reactor
from twisted.web import http

class MyrequestHandle(http.Request):
    resources={
        "/":'<h1>Home</h1>Home page',
        "/about":"<h1>About</h1>all aboue me",
    }
    def process(self):
        self.responseHeaders.setRawHeaders("Content-Type", ["text/html",])
        path=(self.path).decode('ascii')
        if self.resources.get(path,None) is not None:
            self.write((self.resources[path]).encode('ascii'))
        else:
            self.setResponseCode(http.NOT_FOUND)
            self.write("<h1>Not Found</h1>".encode('ascii'))
        self.finish()
class MYhttp(http.HTTPChannel):
    requestFactory = MyrequestHandle
class MyhttpFactory(http.HTTPFactory):
    def buildProtocol(self, addr):
        return MYhttp()
reactor.listenTCP(8003,MyhttpFactory())
reactor.run()

和以往同樣,咱們須要一個工廠類實例化咱們的協議而且開始reacctor循環。在這個例子裏,沒有直接繼承protocol.Protocol,咱們利用了底層API的優勢http.HTTPChannel,它繼承自basic.LineReceiver,而且已經實現了HTTP請求的結構和HTTPRFCS所須要的行爲。

咱們的MyHTTP協議指定了如何經過他的requestFactory實例變量設爲MyRequestHandler來處理請求,MyRequestHandler子類是http.Request。Request的Process方法是必須在子類中重寫的。HTTP相應碼是200,除非用setResponseCode覆蓋HTTP相應代碼,就像咱們在請求404的時候同樣。

處理GET請求

既然咱們已經很好地掌握了HTTP協議的結構和底層API的實現原理,咱們就能夠轉向高級用法了。

靜態資源

web服務器一般會提供靜態資源

from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File

resource=File("/var/www/mysie")
factory=Site(resource)
reactor.listenTCP(8005,factory)
reactor.run()

這個級別上,咱們不須要擔憂HTTP的細節。相反,咱們使用站點,它的子類爲http.HTTPFactory,而且管理HTTP會話來爲咱們分配資源。一個站點使用其管理的資源進行初始化

 resource必須提供一個IResource接口,它會描述resource如何render以及子resource如何訪問。在這個例子裏,咱們會用一個文件資源初始化站點。

提示:twisted.web包含了許多執行資源的方法。除了FILE,還能夠得到文件夾展現和錯誤展現頁,代理頁或者XMLRPC。

這個站點用reactor註冊。

 1 from twisted.internet import reactor
 2 from twisted.web.server import Site
 3 from twisted.web.static import File
 4 
 5 root=File("../")
 6 #路徑和名字結尾必須相同
 7 root.putChild("/學習/test.html",File("../學習/test.html"))
 8 root.putChild("TCP",File("../TCP"))
 9 factory=Site(root)
10 reactor.listenTCP(8004,factory)
11 reactor.run()

使用上邊的就能夠直接跳轉到html。

也能夠訪問文件夾。

動態內容服務

提供動態內容看起來和提供靜態資源相似。最大的區別是,您將不會再使用File這樣的現有資源,而是使用Resource子類來提供動態內容。

 1 from twisted.internet import reactor
 2 from twisted.web.resource import Resource
 3 from twisted.web.server import Site
 4 
 5 import time
 6 
 7 class ClickPage(Resource):
 8     isLeaf = True #設置爲是否能夠訪問這個資源
 9     def render_GET(self,request):
10         print(request)
11         data="The time is %s"%time.ctime()
12         return data.encode('ascii')
13 
14 resource=ClickPage()
15 factory=Site(resource)
16 reactor.listenTCP(8005,factory)
17 reactor.run()

ClockPage是Recource的一個子類。咱們執行了render——method來支持每個HTTP請求。在這個例子裏咱們只提供了GET請求,你能夠支持其餘的方法。

 呈現的方法被傳遞個客戶端。這不是twisted.web.http.Request的實例。它是twisted.webb.server.Request的實例,它繼承自http.Request而且通曉應用層的思想,好比會話管理和跳轉呈現。

render_GET返回GET請求。在這個例子裏,咱們反悔了字符串時間。

isLeaf實例變量描述了資源是否有子資源。設置爲false會有404.

動態調度

咱們知道了如何提供動態資源。下一步是動態返回請求,在URL上提供不一樣資源。

 1 from twisted.internet import reactor
 2 from twisted.web.resource import Resource,NoResource
 3 from twisted.web.server import Site
 4 
 5 from calendar import calendar
 6 
 7 class YearPage(Resource):
 8     isLeaf = True #只要有數字就行,也就是最後一個了
 9     def __init__(self,year):
10         super(YearPage, self).__init__()
11         self.year=year
12     def render_GET(self,request):
13         data="<html><body><pre>%s</pre></body></html>"%calendar(self.year)
14         return data.encode('ascii')
15 
16 class CalendarHome(Resource):
17     #isLeaf = True 有isLeaf,getChild就永遠不會調用
18     #若是咱們但願這個類既有分支又有方法,必須重寫getChild
19     def getChild(self, name, request):
20         name=name.decode('ascii')
21 
22         if name=="":
23             return self
24         if name.isdigit():
25             return YearPage(int(name))
26         else:
27             return NoResource()
28     def render_GET(self,request):
29         data="<h1>welcome</h1>"
30         return data.encode('ascii')
31 root=CalendarHome()
32 factory=Site(root)
33 reactor.listenTCP(8005,factory)
34 reactor.run()

根資源是CalendarHome,它繼承自Resource來指定如何查找子資源和如何呈現本身。

CalendarHome.getChild展現瞭如何經過一個URL找到指定資源。若是沒有多餘的部分,CalendarHome返回自身來跳轉到GET方法。

建立既有render也有子資源的資源。注意CalendarHome沒有把isLeaf設置爲True。

通常狀況下只有葉子的資源纔會被展現。能夠經過isLeaf設置爲True或者由於當遍歷資源樹的時候,URL耗盡,該資源就是咱們所在的位置。可是,當isLeaf設置爲True的時候,GetChild永遠不會執行。所以,當有子資源的時候,isLeaf不能設置爲True。

若是咱們想讓CalendarHome既能夠跳轉也有子資源,咱們必須重寫getChild1方法來指定資源。

在CalendarHome.getChild中,if name==''咱們反悔了它自身來得到跳轉。若是沒有這種條件,訪問後有404.4

Redirect

咱們須要使用Twisted.web.util.redirectTo構建重定向,而不是在給定的URL資源上呈現資源。將要重定向的URL組件和仍然須要呈現的請求做爲參數。

1 from datetime import datetime
2 from twisted.web.util import redirectTo
3 def render_GET(self, request):
4 return redirectTo(datetime.now().year, request)

處理POST請求。

 1 from twisted.internet import reactor
 2 from twisted.web.resource import Resource
 3 from twisted.web.server import Site
 4 
 5 import cgi
 6 
 7 class FormPage(Resource):
 8     isLeaf = True
 9     def render_GET(self,request):
10         tem=b"""
11                 <html>
12         <body>
13         <form method="POST" action="/">
14         <input name="form-field" type="text" />
15         <input type="submit" />
16         </form>
17         </body>
18         </html>
19                 """
20         return tem
21     def render_POST(self,request):
22         print(request)
23         post_data=request.args[b"form-field"][0]
24         post_data=(cgi.escape(post_data.decode('ascii'))).encode('ascii')
25         tmp=b"""
26             <html>
27         <body>You submitted: %s</body>
28         </html>
29             """%post_data
30         return tmp
31 factory = Site(FormPage())
32 reactor.listenTCP(8005, factory)
33 reactor.run()

render_POST從request.args中提取用戶輸入的文本,並用cgi.escape消毒。

異步響應

在目前全部的Twisted服務中,咱們假設了服務器能夠馬上響應用戶,而無需昂貴的檢索計算。當響應阻塞會發生什麼。

 1 from twisted.internet import reactor
 2 from twisted.web.resource import Resource,NoResource
 3 from twisted.web.server import Site
 4 
 5 import time
 6 
 7 class BusyPage(Resource):
 8     isLeaf = True
 9     def render_GET(self,request):
10         time.sleep(5)
11         data="Finally done at {}".format(time.localtime())
12         return data.encode('ascii')
13 
14 f=Site(BusyPage())
15 reactor.listenTCP(8005,f)
16 reactor.run()

咱們會調用deferred,而不須要引入線程。

接下來會展現如何使用Deferred來代替阻塞。deferLater代替阻塞。以後,不會阻塞資源,render_GET馬上返回NOT_done_yet,釋放服務器資源來處理其餘請求。

 1 from twisted.internet import reactor
 2 from twisted.internet.defer import Deferred
 3 from twisted.web.resource import Resource
 4 from twisted.internet.task import deferLater
 5 from twisted.web.server import Site,NOT_DONE_YET
 6 
 7 import time
 8 
 9 class BusySite(Resource):
10     isLeaf = True
11     def _delayRead(self,request):
12         print('inner')
13         time.sleep(5)
14         request.write(b"Finallt down")
15         request.finish()
16 
17     def  render_GET(self,request):
18         d=deferLater(reactor,0,lambda :request)
19         d.addCallback(self._delayRead)
20         # d=Deferred()
21         # reactor.callLater(0,self._delayRead,request)
22         #到了這裏當即釋放線程,可讓下一個進來
23         print('end')
24         return NOT_DONE_YET
25 factory=Site(BusySite())
26 reactor.listenTCP(8006,factory)
27 reactor.run()

Twisted事實上不是django之類的框架,而是用來構建框架的,好比github上的klein。

第五節 客戶端

打印web資源。

twisted.web.client.getPage異步檢索URL上的指定資源。他返回一個Deferred,會以字符串的形式激發回調。

 1 from twisted.internet import reactor
 2 from twisted.web.client import getPage
 3 import sys
 4 
 5 def printPage(res):
 6     print(res)
 7 
 8 def printErr(err):
 9     print(err)
10 
11 def stop(res):
12     reactor.stop()
13 
14 url=b'https://www.baidu.com'
15 d=getPage(url)
16 d.addCallbacks(printPage,printErr)
17 d.addBoth(stop)
18 
19 reactor.run()

也能夠用getPage創建POST,好比。getPage(url,method='post',postdata='my data')

getPage一樣支持使用cookies,重定向和改變請求頭。

def __init__(self, url, method=b'GET', postdata=None, headers=None,
agent=b"Twisted PageGetter", timeout=0, cookies=None,
followRedirect=True, redirectLimit=20,
afterFoundGet=False):

下載web資源

twisted.web.client.downloadPage異步下載頁面資源。

 1 from twisted.internet import reactor
 2 from twisted.web.client import downloadPage
 3 import sys
 4 
 5 def printErr(res):
 6     print(res)
 7 
 8 def stop(res):
 9     reactor.stop()
10 
11 url=b'https://www.baidu.com'
12 d=downloadPage(url,'baidu.html')
13 d.addCallback(printErr)
14 d.addBoth(stop)

把頁面下載到指定file。

agent代理

getPage和downloadPage很是有用,可是主要的Twisted HTTP客戶端API是Agent,它的拓展性很高。

 1 from twisted.internet import reactor
 2 from twisted.internet.defer import Deferred
 3 from twisted.internet.protocol import Protocol,connectionDone
 4 from twisted.web.client import Agent
 5 
 6 class ResourcePrint(Protocol):
 7     def __init__(self,finish):
 8         self.finish=finish
 9 
10     def dataReceived(self, data):
11         print(data)
12 
13     def connectionLost(self, reason=connectionDone):
14         self.finish.callback(None)
15 
16 def printResource(response):
17     finished=Deferred()
18     response.deliverBody(ResourcePrint(finished))
19     return finished
20 
21 def printErr(fail):
22     print(fail)
23 
24 def stop(res):
25     reactor.stop()
26 
27 agent=Agent(reactor)
28 d=agent.request('GET',b'http://www.baidu.com')
29 d.addCallbacks(printResource,printErr)
30 d.addBoth(stop)
31 
32 reactor.run()

agent版本須要更多的工做,可是更加通用,讓咱們分析一下設計的步驟。

1.初始化一個twisted.web.client.Agent。由於代理須要處理鏈接,它必須初始化reactor。

2.使用HTTP請求方法發出HTTP請求。它至少須要HTTP方法和URL。在成功的狀況下。agent.request返回一個deferred,使用Response觸發。

3.向agent.Request返回的延遲返回的回調註冊,以便在響應體可用處response.deliverBody處理響應體。由於響應是chunks經過網絡的,因此咱們須要一個協議來處理收到的數據,而且在完成交付的時候通知咱們。爲此,咱們建立了一個Protocol子類來調用ResorcePrinter。

4.一旦鏈接結束,中止reactor。爲了作到這個,咱們註冊回調來進行stop。

檢索響應元數據

agent.Request返回的Deferred中的Response對象包含許多有用的HTTP數據。

 1 from twisted.internet import reactor
 2 from twisted.web.client import Agent
 3 from twisted.web.http_headers import Headers
 4 
 5 def printHeaders(response):
 6     print('start')
 7     print(response)
 8     for header,value in response.headers.getAllRawheaders():
 9         print(header,value)
10 
11 def printErr(res):
12     print(res)
13 
14 def stop(res):
15     reactor.stop()
16 
17 agent=Agent(reactor)
18 headers=Headers({
19     'User-Agent':['Twisted webbot'],
20     'Content-Type':['text/x-greeting']
21 })
22 d=agent.request('GET',b'http://www.bilibili.com',headers=headers)
23 d.addCallbacks(printHeaders,printErr)
24 d.addBoth(stop)
25 
26 reactor.run()

使用Agent傳輸數據

爲了使用POST,咱們須要建立一個producer,提供IBodyProducer接口,這個接口會在agent須要的時候創造POST數據。

爲了提供IBodyProducer接口,Twisted使用zope.interface.implements實現這個接口。一個類必選執行以下的方法,以及一個Length屬性,它跟蹤producer最終將生成數據長度。

startProducing

stopProducing

pauseProducing

resumeProducing

在這個例子裏,咱們能夠構造一個簡單的StringProducer,它在調用startProducing的時候只把POST數據寫入等待的消費者。Stringroducer做爲bodyProducer參數傳遞給agent.request。

 1 from twisted.internet import reactor
 2 from twisted.internet.defer import Deferred,succeed
 3 from twisted.internet.protocol import Protocol
 4 from twisted.web.client import Agent
 5 from twisted.web.iweb import IBodyProducer
 6 
 7 from zope.interface import implements
 8 
 9 class StringProducer:
10     implements(IBodyProducer)
11     def __init__(self,body):
12         self.body=body
13         self.length=len(body)
14 
15     def startProducing(self,consumer):
16         consumer.write(self.body)
17         return succeed(None)
18     def pauseProducing(self):
19         pass
20     def stopProducing(self):
21         pass
22 class ResourcePrinter(Protocol):
23     def __init__(self,finish):
24         self.finish=finish
25     def dataReceived(self, data):
26         print(data)
27     def connectionLost(self, reason):
28         print(reason)
29 
30 def printResource(response):
31     finished=Deferred()
32     response.deliverBody(ResourcePrinter(finished))
33     return finished
34 
35 def printErr(res):
36     print(res)
37 
38 def stop(res):
39     reactor.stop()
40 
41 agent=Agent(reactor)
42 body=StringProducer(b'python')
43 d=agent.request('POST',b'http://www.baidu.com',bodyProducer=body)
44 d.addCallback(printResource,printErr)
45 d.addBoth(stop)
46 
47 reactor.run()
相關文章
相關標籤/搜索