Twisted Network Programming Essentials(中文渣翻)

第一章 介绍Twisted

1.1 开始

在你会用Twisted建立app之前,你需要下载安装Twisted和它的环境。这一章的主要任务就是帮助你学会安装Twisted。

Twisted需要python2.6或者2.7。支持python3的版本还在构建中。

安装Twisted

首先:你需要下载Twisted。下载和说明以及相应的版本都可以在https://twistedmatrix.com/trac/找到(建议各位去官网看看)。想要启动Twisted里面的其他功能,你需要安装一些额外的包。

linux上安装

所有流行的Linux发布版本都内置了一个python-twisted安装包和对应版本的环境支持。如果要在dpkg-based系统上安装,用

  apt-get install python-twisted

正在rmp-based系统,用

  yum install python-twisted

就这么简单。

如果你要使用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()

 

posted @ 2018-12-12 00:55  LoseNine  阅读(798)  评论(0编辑  收藏  举报