智慧 + 毅力 = 无所不能

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

导航

Twisted网络编程必备(4)

Posted on 2010-11-22 16:51  Bill Yuan  阅读(3890)  评论(0编辑  收藏  举报

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

4.0 WEB服务器

 

即使是很保守的说,现在的很多软件是基于WEB开发的。人们将大量时间花费在WEB浏览器上面,包括阅读HTML页面、电子邮件、管理日志、进入数据库的记录、更新Wiki页面和写weblog。

即使你不打算写严格的WEB应用,WEB界面也更加容易提供适合于跨平台的UI。在你的应用中包含轻量级的WEB服务器将会提供更多的附属功能。这一章将会展示如何使用Twisted开发一个WEB服务器,并介绍你一些构建WEB应用的方法。当然还提供了HTTP代理服务器。

这一章提供了一些HTTP协议的常识。这些一些构建WEB服务器所必须的知识。实际上,本书所涉及的HTTP知识还远远不够,还需要如《HTTP: The Definitive Guide》等等的书,还有不可替代的HTTP的RFC文档RFC2616(http://www.faqs.org/rfcs/rfc2616.html)。

 

4.1 响应HTTP请求

 

HTTP是一个简单的协议接口。客户端发送请求,服务器端发送响应,然后关闭连接。你可以自己实现一个HTTP的Protocol来练习接收连接,读取请求,并发送HTTP格式的响应。

 

4.1.1 下面如何做?

 

每个HTTP请求开始于单行的HTTP方法,紧接着是URL,然后是HTTP版本。随后是一些行的头字段。一个空行标志着头字段的结束。头字段后面就是请求主体,例如提交的HTML表单数据。

如下是一个HTTP请求的例子。这个请求询问服务器通过GET方法获取www.example.com/index.html资源,并使用HTTP版本1.1。

GET /index.html HTTP/1.1

Host: www.example.com

服务器响应的第一行告知客户端响应的HTTP版本和状态码。有如请求一样,响应包含了头字段,并用空行隔开消息主体。如下是HTTP响应的例子:

HTTP/1.1 200 OK

Content-Type: text/plain

Content-Length: 17

Connection: Close

 

Hello HTTP world!

设置简单的HTTP服务器,需要写一个Protocol允许客户端连接。查找空行标志着头字段的结束。然后发送HTTP响应,例子4-1展示了简单的HTTP实现。

 

代码
from twisted.protocols import basic
from twisted.internet import protocol,reactor
class HttpEchoProtocol(basic,LineReceiver):
def __init__(self):
self.lines
=[]
self.gotRequest
=False
def lineReceived(self,line):
self.lines.append(line)
if not line and not self.gotRequest:
self.sendResponse()
self.gotRequest
=True
def sendResponse(self):
responseBody
="You said: \r\n\r\n"+"\r\n".join(self.lines)
self.sendLine(
"HTTP/1.0 200 OK")
self.sendLine(
"Content-Type: text/plain")
self.sendLine(
"Content-Length: %i"%len(responseBody))
self.sendLine(
"")
self.transport.write(responseBody)
self.transport.loseConnection()
f
=protocol.ServerFactory()
f.protocol
=HttpEchoProtocol
reactor.listenTCP(
8000,f)
reactor.run()

 

 

运行webecho.py脚本启动服务器。你可以看到服务器运行在http://localhost:8000。你可以获得请求的回显,即原请求的报文。

 

4.1.2 它们如何工作?

 

HTTPEchoProtocol懂得如何响应每一个请求。从客户端收到的数据将会存储在self.lines中。当看到一个空行时,可以知道头字段结束了。它发送回一个HTTP响应。第一行包含了HTTP版本和状态码,在这种情况下,200是成功("OK"是附加的便于阅读的状态码描述)。下一对行是Content-Type和Content-Length头字段,用于告知客户端内容格式和长度。HTTPEchoProtocol发送一个空行来结束头字段,然后发送响应主体,回显客户端请求报文。

4.2 解析HTTP请求

 

HTTPEchoProtocol类提供了有趣的HTTP入门,但是距离使用功能还很遥远。它并不去分析请求头和资源位置,HTTP方法也只支持一种。如果想要建立一个真正的WEB服务器,你可以用一种更好的方式来解析和响应请求。下面的实现展示这些。

 

4.2.1 下面如何做?

 

写twisted.web.http.Request的子类并重载process方法来处理当前请求。Request对象已经包含了process需要调用的HTTP请求所有信息,所以只需要决定如何响应。例子4-2展示了如何运行基于http.Request的HTTP服务器。

 

代码
from twisted.web import http
class MyRequestHandler(http.Request):
pages
={

'/':'<h1>Home</h1>Home Page',

'/test':'<h1>Test</h1>Test Page',

}
def process(self):
if self.pages.has_key(self.path):
self.write(self.pages[self.path])
else:
self.setResponseCode(http.NOT_FOUND)
self.write(
"<h1>Not Found</h1>Sorry, no such page.")
self.finish()
class MyHttp(http.HTTPChannel):
requestFactory
=MyRequestHandler
class MyHttpFactory(http.HTTPFactory):
protocol
=MyHttp
if __name__=='__main__':
from twisted.internet import reactor
reactor.listenTCP(
8000,MyHttpFactory())
reactor.run()

 

 

运行requesthandler.py将会在8000端口启动一个WEB服务器。可以同时阅览主页(http://localhost:8000/)和测试页/test(http://localhost:8000/test)。如果你指定了想要访问其他页面,将会得到错误信息。

4.2.2 它们如何工作?

 

http.Request类解析了进入的HTTP请求并提供了制造响应的接口。在例子4-2中,MyRequestHandler是http.Request的一个子类提供了process方法。process方法将会被请求调用并接收。有责任在产生一个响应之后调用self.finish()来指出响应完成了。MyRequestHandler使用path属性来寻找请求路径。并在pages字典中指定匹配路径。如果匹配成功,MyRequestHandler使用write方法发送响应文本到响应。

注意write仅在写入响应的部分实体主体时才使用,而不是在生成原始HTTP响应时。setResponseCode方法可以用于改变HTTP状态码。twisted.web.http模块提供了所有HTTP状态码的定义,所以可用http.NOT_FOUND来代替404错误。

Tip:Request.setResponseCode带有一个可选的第二个参数,一个易读的状态消息。你可以感觉到很方便于twisted.web.http模块包含了内置列表描述普通的状态码,也就是缺省使用的。

Request类也提供setHeader方法来添加响应头字段。MyRequestHandler使用setHeader来设置Content-Type头为text/html,这个设置告诉浏览器对响应主体使用HTML格式。

twisted.web.http模块提供了两种附加类允许将Request的子类转换成功能性的WEB服务器。HTTPChannel类是一个Protocol,可以创建Request对象来应付每个连接。创建HTTPChannel时使用你的Request子类,重载requestFactory类属性。HTTPFactory是一个ServerFactory,添加附加特性,包含日志方法加上Request对象,这种日志格式包括Apache和其他多种日志格式。

4.3 处理POST数据和HTML表单

 

前面的实验展示了通过客户端请求生成静态HTML。这个实验展示了允许书写代码控制响应的生成,并且处理HTML表单提交的数据。

 

4.3.1 下面如何做?

 

写一个函数来处理Request对象并产生响应。设置字典来映射每一个可用路径到WEB站点来让函数出路路径请求。使用Request.args字典存取提交的HTML表单数据。例子4-3展示了生成单页HTML表单的WEB服务器,另一个页面是通过表单数据显示的。

 

代码
from twisted.web import http
def renderHomePage(request):
colors
='red','blue','green'
flavors
='vanilla','chocolate','strawberry','coffee'
request.write(
"""
<html>
<head>
<title>Form Test</title>
</head>
<body>
<form action="posthandler" method="POST">
Your Name:
<p>
<input type="text" name="name">
</p>
What's your favorite color?
<p>
""")
for color in colors:
request.write(
"<input type='radio' name='color' value='%s'>%s<br/>"%(
color,color.capitalize()))
request.write(
"""
</p>
What kinds of ice cream do you like?
<p>
""")
for flavor in flavors:
request.write(
"<input type='checkbox' name='flavor' value='%s'>%s<br/>"%(
flavor,flavor.capitalize()))
request.write(
"""
</p>
<input type='submit'/>
</form>
</body>
</html>
""")

request.finish()
def handlePost(request):
request.write(
"""
<html><head><title>Posted Form Datagg</title>
</head>
<body>
<h1>Form Data</h1>
""")
for key,values in request.args.items():
request.write(
"<h2>%s</h2>"%key)
request.write(
"<ul>")
for value in values:
request.write(
"<li>%s</li>"%value)
request.write(
"</ul>")
request.write(
"""
</body></html>
""")
request.finish()
class FunctionHandleRequest(http.Request):
pageHandlers
={
'/':renderHomePage,
'/posthandler':handlePost,
}
def process(self):
self.setHeader(
"Content-Type","text/html")
if self.pageHandlers.has_key(self.path):
handler
=self.pageHandlers[self.path]
handler(self)
else:
self.setResponseCode(http.NOT_FOUND)
self.write(
"<h1>Not Found</h1>Sorry, no such page.")
self.finish()
class MyHttp(http.HTTPChannel):
requestFactory
=FunctionHandledRequest
class MyHttpFactory(http.HTTPFactory):
protocol
=MyHttp
if __name__=='__main__':
from twisted.internet import reactor
reactor.listenTCP(
8000,MyHttpFactory())
reactor.run()

 

 

运行formhandler.py脚本。将会在8000端口运行WEB服务器。进入http://localhost:8000可以找到表单主页。按照如下填写一些字段信息。

然后点击提交按钮,你的浏览器将会发送表单数据到页面formhandler使用HTTP的POST请求。当它接受到表单数据时,formhandler回展示提交过的字段和值。

4.3.2 它们是如何工作的?

 

例子4-3定义了两个函数来处理请求,renderHomePage和handlePost。FunctionHandleRequest是Request的子类,其属性pageHandler定义了路径映射功能。process方法查找路径,并尝试在pageHandlers中匹配路径。如果匹配成功,则FunctionHandleRequest传递自身到匹配函数,并且由对方负责处理;如果匹配失败,则返回404 Not Found响应。

renderHomePage函数设置处理器到/,站点的根路径。它生成的HTML表单将会提交数据到页面/formhandler。这个处理器函数/formhandler是handlePost,将会响应页面列表并提交数据。handlePost遍历值Request.args,这个字典属性包含了请求提交的所有数据。

Tip:在这种情况下,发送的表单数据在HTTP POST请求的主体中。当请求发送HTTP GET时,Request.args将会包含所有提交的URI查询字段值。你可以修改这个行为,通过改变表单生成器renderHomePage的method属性,从POST到GET,重启服务器,就可以重新提交表单。

一个HTML表单可以有多个字段具有相同的名字。例如,表单4-3中允许选中多个复选框,所有的名字都是flavor。不像很多其他的框架,http.Request并不对你隐藏什么:代替了映射字段名到字符串,Request.args映射每个字段值到列表。如果你知道要取哪一个值,那么可以只获取列表的第一个值。