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()
相关文章
相关标签/搜索