Twisted网络编程必备(5)

转自:http://www.yybug.com/read-htm-tid-15324.html

4.4 管理资源等级

 

WEB应用中的路径通常使用分级目录管理。例如如下URL:

http://example.com/people

http://example.com/people/charles

http://example.com/people/charles/contact

这里可以很清楚的看出等级划分。页面/people/charles是/people的子页面,而页面/people/charles/contact是/people/charles的子页面。等级中的每个页面都有特定的意义:/people/charles是一个人,而/people/charles/contact是一项数据,charles的数据。

WEB服务器的缺省行为是将PATH等级映射到磁盘上的文件。客户端的每次请求都是对应特定的资源,服务器查找文件并定位磁盘路径,然后将文件内容或者可执行文件的执行结果作为响应。在WEB应用中可以人为的生成一个对应路径文件的内容。例如,你的数据并没有存储在磁盘上,而是在一个关系数据库或者另一个服务器上。或者你想要在请求时自动创建资源。类似这种情况,最好的办法是为等级浏览创建自己的逻辑。

编写自己的资源管理逻辑可以帮助你管理安全。而不是打开WEB服务器的整个目录,你可以有选择的控制哪些文件是可以访问的。

 

4.4.1 下面如何做?

 

twisted.web.resource和twisted.web.static还有twisted.web.server模块提供了比twisted.web.http.Resource更高层次的请求管理类,你可以使用这些来设置一个WEB服务器来处理多种逻辑等级的资源。例子4-4使用了这些类来建立十六进制颜色代码。请求资源/color/hex,hex是十六进制的颜色代码,你可以得到一个背景为#hex的页面。对应每一种可能出现的颜色可能,服务器动态创建资源。

代码
   
     
from twisted.web import resource,static,server
class ColorPage(resource.Resource):
def __init__ (self,color):
self.color
= color
def render(self,request):
return """
<html>
<head>
<title>Color: %s</title>
<link type='text/css' href='/styles.css' rel='Stylesheet' />
</head>
<body style='background-color: #%s'>
<h1>This is #%s.</h1>
<p style='background-color: white'>
<a href='/color/'>Back</a>
</p>
</body>
</html>
""" % (self.color, self.color, self.color)

class ColorRoot(resource.Resource):
def __init__ (self):
resource.Resource.
__init__ (self)
self.requestedColors
= []
self.putChild(
'' ,ColorIndexPage(self.requestColors))
def render(self,request):
# redirect /color -> /color/
request.redirect(request.path + ' / ' )
return ' please use /colors/ instead. '
def getChild(self,path,request):
if path not in self.requestedColors:
self.requestedColors.append(path)
return ColorPage(path)
class ColorIndexPage(resource.Resource):
def __init__ (self,requestColorsList):
resource.Resource.
__init__ (self)
self.requestedColors
= requestedColorsList
def render(self,request):
request.write(
"""
<html>
<head>
<title>Colors</title>
<link type='text/css' href='/styles.css' rel='Stylesheet' />
</head>
<body>
<h1>Colors</h1>
To see a color, enter a url like
<a href='/color/ff0000'>/color/ff0000</a>. <br />
Colors viewed so far:
<ul>
""" )
for color in self.requestedColors:
request.write(
" <li><a href= " / blog /% s " style='color: #%s'>%s</a></li> " % (
color, color, color))
request.write(
"""
</ul>
</body>
</html>
""" )
return ""
class HomePage(resource.Resource):
def render(self,request):
return """
<html>
<head>
<title>Colors</title>
<link type='text/css' href='/styles.css' rel='Stylesheet' />
</head>
<body>
<h1>Colors Demo</h1>
What's here:
<ul>
<li><a href='/color'>Color viewer</a></li>
</ul>
</body>
</html>
"""

if __name__ == ' __main__ ' :
from twisted.internet import reactor
root
= resource.Resource()
root.putChild(
'' ,HomePage())
root.putChild(
' color ' ,ColorRoot())
root.putChild(
' styles.css ' ,static.File( ' styles.css ' ))
site
= server.Site(root)
reactor.listenTCP(
8000 ,site)
reactor.run()

 

例子4-4引用了静态文件。所以需要在resourcetree.py脚本目录下创建一个styles.css文件。内容如下:

代码
   
     
body {
font-family
: Georgia, Times, serif ;
font-size
: 11pt ;
}
h1
{
margin
: 10px 0 ;
padding
: 5px ;
background-color
: black ;
color
: white ;
}
a
{
font-family
: monospace ;
}
p
{
padding
: 10px ;
}

 

运行resourcetree.py脚本,将会在8000端口启动一个WEB服务器。下面是服务器全部可用路径:

/        主页

/css    虚拟的CSS资源

/css/styles.css    静态文件styles.css

/colors/ 颜色查看页面

/colors/hexcolor 按照背景色为#hexcolor的页面

尝试通过http://localhost:8000/colors/00abef来访问,将会得到背景色为#00abef的页面,大约是亮蓝色。

可以随便试试其他颜色。同样可以进入http://localhost:8000/,选择可选答案。

4.4.2 它们如何工作?

 

例子4.4从twisted.web包中引入了几个类:resource.Resource、static.File、server.Site。每个resource.Resource对象做两件事。首先,定义请求的资源如何处理。第二,定义请求子资源的Resource对象。

例如查看类ColorRoot。在这个类的实例稍后被加入了/colors这个等级资源。初始化时,ColorRoot使用putChild方法插入ColorIndexPage这个资源座位''资源。这意味着所有对/colors/的请求都由ColorIndexPage对象来处理。

你可以把他们想象为等价的,但是/stuff和/stuff/是不同的。浏览器在解释相对路径时,对是否加上斜线的处理方法是不同的。在第一个例子中,对"otherpage"的请求会解释为"http://example.com/otherpage",在第二个例子中解释为"http://example.com/stuff/otherpage"。

如果你不清楚(explicit)服务器代码,这个问题可能会再次郁闷你。最好是预先设计好是否需要在URI末尾加上斜线,并重定向请求。Resource类将会简化这些操作。如果设置了addSlash属性为True,一个Resource会自动在找不到对应资源时自动在URL末尾添加斜线来再次查找。

4.4 管理资源等级

 

4.6 运行HTTP代理服务器

 

除了HTTP服务器和客户端以外,twisted.web还包含了HTTP代理服务器的支持。一个代理服务器是一个服务器和一个客户端。他接受来自客户端的请求(作为服务器)并将他们转发到服务器(作为客户端)。然后将响应发送回客户端。HTTP代理服务器可以提供很多有用的服务:缓存、过滤和使用情况报告。下面的例子展示了如何使用Twisted构建一个HTTP代理服务器。

 

4.6.1 下面如何做?

 

twisted.web包包含了twisted.web.proxy,这个模块包含了HTTP代理服务器。例子4-7构建了一个简单的代理服务器。

 

代码
   
     
from twisted.web import proxy,http
from twisted.internet import reactor
from twisted.python import log
import sys
log.startLogging(sys.stdout)
class ProxyFactory(http.HTTPFactory):
protocol
= proxy.Proxy
reactor.listenTCP(
8001 ,ProxyFactory())
reactor.run()

 

运行simpleproxy.py脚本将会在8001端口启动代理服务器。在浏览器中设置这个代理服务器可以作为代理进行测试。对log.startLogging的调用将会把HTTP日志信息记录在stdout中,并可以直接查看。

 

$ python simpleproxy.py

2005/06/13 00:22 EDT [-] Log opened.

2005/06/13 00:22 EDT [-] __main__.ProxyFactory starting on 8001

... ...

 

这虽然给出了一个代理服务器,但是实际上没什么用处。例子4-8提供了更多的功能,可以跟踪最常使用的网页。

 

代码
   
     
import sgmllib.re
from twisted.web import proxy,http
import sys
from twisted.python import log
log.startLogging(sys.stdout)
WEB_PORT
= 8000
PROXY_PORT
= 8001
class WordParser(sgmllib.SGMLParser):
def __init__ (self):
sgmllib.SGMLParser.
__init__ (self)
self.chardata
= []
self.inBody
= False
def start_body(self,attrs):
self.inBody
= True
def end_body(self):
self.inBody
= False
def handle_data(self,data):
if self.inBody:
self.chardata.append(data)
def getWords(self):
# 解出单词
wordFinder = re.compile(r ' \w* ' )
words
= wordFinder.findall( "" .join(self.chardata))
words
= filter( lambda word: word.strip(), words)
print " WORDS ARE " , words
return words
class WordCounter(object):
ignoredWords
= " the a of in from to this that and or but is was be can could i you they we at " .split()
def __init__ (self):
self.words
= ()
def addWords(self,words):
for word in words:
word
= word.lower()
if not word in self.ignoredWords:
currentCount
= self.words.get(word,0)
self.words[word]
= currentCount + 1
class WordCountProxyClient(proxy.ProxyClient):
def handleHeader(self,key,value):
proxy.ProxyClient.handleHeader(self,key,value)
if key.lower() == " content-type " :
if value.split( ' ; ' )[0] == ' text/html ' :
self.parser
= WordParser()
def handleResponsePart(self,data):
proxy.ProxyClient.handleResponsePart(self,data)
if hasattr(self, ' parser ' ):
self.parser.feed(data)
def handleResponseEnd(self):
proxy.ProxyClient.handleResponseEnd(self)
if hasattr(self, ' parser ' ):
self.parser.close()
self.father.wordCounter.addWords(self.parser.getWords())
del (self.parser)
class WordCountProxyClientFactory(proxy.ProxyClientFactory):
def buildProtocol(self,addr):
client
= proxy.ProxyClientFactory.buildProtocol(self,addr)
# 升级proxy.proxyClient对象到WordCountProxyClient
client. __class__ = WordCountProxyClient
return client
class WordCountProxyRequest(proxy.ProxyRequest):
protocols
= { ' http ' :WordCountProxyClientFactory)
def __init__ (self,wordCounter, * args):
self.wordCounter
= wordCounter
proxy.ProxyRequest.
__init__ (self, * args)
class WordCountProxy(proxy.Proxy):
def __init__ (self,wordCounter):
self.wordCounter
= wordCounter
proxy.Proxy.
__init__ (self)
def requestFactory(self, * args)
return WordCountProxyRequest(self.wordCounter, * args)
class WordCountProxyFactory(http.HTTPFactory):
def __init__ (self,wordCount):
self.wordCounter
= wordCounter
http.HTTPFactory.
__init__ (self)
def buildProtocol(self,addr):
protocol
= WordCountProxy(self.wordCounter)
return protocol
# 使用WEB接口展示记录的接口
class WebReportRequest(http.Request):
def __init__ (self,wordCounter, * args):
self.wordCounter
= wordCounter
http.Request.
__init__ (self, * args)
def process(self):
self.setHeader(
" Content-Type " , ' text/html ' )
words
= self.wordCounter.words.items()
words.sort(
lambda (w1,c1),(w2,c2): cmp(c2,c1))
for word,count in words:
self.write(
" <li>%s %s</li> " % (word,count))
self.finish()
class WebReportChannel(http.HTTPChannel):
def __init__ (self,wordCounter):
self.wordCounter
= wordCounter
http.HTTPChannel.
__init__ (self)
def requestFactory(self, * args):
return WebReportRequest(self.wordCounter, * args)
class WebReportFactory(http.HTTPFactory):
def __init__ (self,wordCounter):
self.wordCounter
= wordCounter
http.HTTPFactory.
__init__ (self)
def buildProtocol(self,addr):
return WebReportChannel(self.wordCounter)
if __name__ == ' __main__ ' :
from twisted.internet import reactor
counter
= WordCounter()
prox
= WordCountProxyFactory(counter)
reactor.listenTCP(PROXY_PORT,prox)
reactor.listenTCP(WEB_PORT,WebReportFactory(counter))
reactor.run()

 

运行wordcountproxy.py将浏览器的代理服务器设置到8001端口。浏览其他站点,或者访问http://localhost:8000/,将可以看到访问过的站点的单词频率。

 

4.6.2 它是如何工作的?

 

在例子4-8中有很多个类,但大多数是连接用的。只有很少的几个做实际工作。最开始的两个类WordParser和WordCounter,用于从HTML文档中解出单词符号并计算频率。第三个类WordCountProxy客户端包含了查找HTML文档并调用WordParser的任务。

因为代理服务器同时作为客户端和服务器,所以需要使用大量的类。有一个ProxyClientFactory和ProxyClient,提供了Factory/Protocol对来支持客户端连接。为了响应客户端的连接,需要使用ProxyRequest,它是HTTPFactory的子类,还有Proxy,是http.HTTPChannel的子类。它们对建立一个普通的HTTP服务器是有必要的:HTTPFactory使用Proxy作它的协议(Protocol),而代理Proxy HTTPChannel使用ProxyRequest作为它的RequestFactory。如下是客户端发送请求的事件处理流程:

·客户端建立到代理服务器的连接。这个连接被HTTPFactory所处理。

·HTTPFactory.buildProtocol会创建一个Proxy对象用来对客户端发送和接收数据。

·当客户端发送请求时,Proxy创建ProxyRequest来处理。

·ProxyRequest查找客户端请求的服务器。并创建ProxyClientFactory并调用reactor.connectTCP来通过factory连接服务器。

·一旦ProxyClientFactory连接到服务器,就会创建ProxyClient这个Protocol对象来发送和接收数据。

·ProxyClient发送原始请求。作为响应,它将客户端发来的请求发送给服务器。这是通过调用self.father.transport.write实现的。self.father是一个Proxy对象,正在管理着客户端连接对象。

这是一大串类,但实际上是分工明确的将工作由一个类传递到另一个类。但这是很重要的。为代理模块的每一个类提供一个子类,你可以完成每一步的控制。

整个例子4-8中最重要的一个技巧。ProxyClientFactory类有一个buildProtocol方法,可以包装好,供ProxyClient作为Protocol。它并不提供任何简单的方法来提供你自己的ProxyClient子类。解决办法是使用Python的__class__属性来替换升级ProxyClient对象来放回ProxyClientFactory.buildProtocol,这些将ProxyClient改变为WordCountProxyClient。

作为代理服务器的附加功能。例子4-8提供了标准的WEB服务器,在8000端口,可以显示从代理服务器来的当前单词统计数量。由此可见,在你的应用中包含一个内嵌的HTTP服务器是多么的方便,这种方式可以很好的提供给远程来显示相关状态信息。

你可能感兴趣的:(网络编程)