智慧 + 毅力 = 无所不能

正确性、健壮性、可靠性、效率、易用性、可读性、可复用性、兼容性、可移植性...

导航

Twisted网络编程必备(5)

Posted on 2010-11-22 17:03  Bill Yuan  阅读(2995)  评论(0编辑  收藏  举报

转自: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服务器是多么的方便,这种方式可以很好的提供给远程来显示相关状态信息。