介绍html
在过去20几年里,网络已经在各个方面改变了咱们的生活,可是它的核心却几乎没有什么改变。多数的系统依然遵循着Tim Berners-Lee在上个世纪发布的规则。大多数的web服务器都在用一样的方式处理消息python
背景web
多数在web上的服务器都是运行在IP协议标准上。在这协议家族里面咱们关心的成员就是TCP,这个协议使得计算机之间的通讯看起来像是在读写文件。shell
项目经过套接字来使用IP通讯。每一个套接字都是一个点对点的通讯信道,一个套接字包含IP地址,端口来标识具体的机器。IP地址包含4个8Bit的数字,好比174.136.14.108;DNS将这些数字匹配到更加容易识别的名字好比aosabook.org,这样更加便于人们记住。数据库
HTTP是一种能够在IP之上传输数据的方式。HTTP很是简单:客户端在套接字链接上发送一个请求指示须要什么样的信息,而后服务端就发送响应。数据能够是从硬盘上的文件拷贝过来,程序动态生成,或者是二者结合编程
HTTP请求中最重要的就是文本:任何项目均可以创造或者解析一个文本。为了便于理解,文本有图中所示的部分浏览器
HTTP方法通常采用”GET”(去获取信息)或者”POST”(去提交表单数据或者上传文件)。URL指明了客户端想要的;通常是硬件上文件的路径,好比/research/experiments.html
,可是这一切都取决于服务器端如何去作。
HTTP
版本通常是
"HTTP/1.0"
或者
"HTTP/1.1"
;咱们并不关心这二者的差异。
安全
HTTP
的头是像下面的成对键值:
服务器
Accept: text/htmlAccept-Language: en, frIf-Modified-Since: 16-May-2005
和哈希表中的键值不同的是,键值在
HTTP
头中能够出现任意的次数。这就使得请求能够去指定它愿意接受的几种类型。
cookie
最后,请求的主体是与请求相关联的任何额外数据。这些将被用在经过表单提交数据,上传文件等等。
在最后一个标头和主体的开始之间必须有空白行以表示标头的结束。
一个被称为
Content-length
的头,用来告诉在请求数据中指望读取多数个字节。
HTTP
响应也和
HTTP
请求是同样的格式
版本
,头信息和主体都是一样的格式。状态码是一个数字用来指示请求处理时发生了什么:
200
意味着正常工做,
404
意味着没有找到,其余的码也有不一样的意思。
对于这章节
,咱们只须要知道
HTTP
的其余两件事。
第一个就是无状态
:每一个请求都处理本身的,而且服务器端。服务器不会记住当前请求和下一个请求之间的内容。若是应用想跟踪好比用户身份的信息,就必须本身处理。
一般采用的方法是用
cookie
,
cookie
是服务器发送给客户端的字符流,而后客户端返回给服务器。当一个用户须要实如今不一样请求之间保持状态的时候,服务器会建立
cookie
,存储在数据库里,而后发送给浏览器。每次浏览器把
cookie
值发送回来的时候,服务器都会用来去查找信息来知道用户在干什么。
第二个咱们须要了解关于
HTTP
的就是
URL
能够经过提供参数来提供更多的信息
。好比,若是咱们在使用搜索引擎,咱们必须指定搜索术语。咱们能够加入到
URL
的路径中,可是咱们通常都是加入到
URL
的参数中。咱们在
URL
中增长
?,
后面跟随
key=value
而且用
&
符号分割来达到这个目的。好比
URL
http://www.google.ca?q=Python
就告诉
Google
去搜索
Python
相关的网页。键值是字母
q
,值是
Python
。更长的查询
http://www.google.ca/search?q=Python&client=Firefox
告诉
Google
咱们正在使用
Firefox
等等
。咱们能够传输任何咱们须要的参数。可是使用哪个,如何解释这些参数取决于应用。
固然
,若是
?
和
&
特殊的字符,那么必须有一种方法去规避,
正如必须有一种方法将双引号字符放入由双引号分隔的字符串中同样
。
URL
的编码标准用
%
后面跟
2
个字节码的方式来表示特殊字符,用
+
来代替空格。因此为了在
Google
上搜索”
grade=A+”
,咱们可使用的
URL
为
http://www.google.ca/search?q=grade+%3D+A%2B
建立
sockets
,构建
HTTP
请求,解析响应是很是枯燥的事情。因此人们更可能是使用库函数来完成大部分的工做。
Python
附带了一个
urllib2
的库,可是它暴露了不少人根本不关心的管道。
Request
库是能够替代
urllib2
而且更加好使用的库。下面是一个从
AOA
网站下载网页的例子。
import requestsresponse = requests.get('http://aosabook.org/en/500L/web-server/testpage.html')print 'status code:', response.status_codeprint 'content length:', response.headers['content-length']print response.textstatus code: 200content length: 61<html><body><p>Test page.</p></body></html>
requests.get
发送一个
HTTP GET
请求到服务器而后返回一个包含响应的对象。对象的
status_code
成员是响应的状态码;
content_length
成员是响应数据的长度,
text
是真是的数据
(
在这个例子中,是
HTTP
网页
)
你好
,web
如今咱们准备去写第一个
简单的
web
服务器。
1
等待某人链接到服务器上而且发送一个请求
2
解析请求
3
指出要求获取的东西
4
获取数据(或者动态的产生)
5
将数据格式化为
HTML
格式
6
发送回去
1,2,6
步对于各类不一样的应用来讲都是同样的,
Python
标准库有一个模块称为
BaseHTTPServer
为咱们作完成这些。咱们须要完成的是步骤
3
到步骤
5.
这一部分只须要不多的工做
这个函数假设被容许web服务器正在运行的目录或者目录下的任何文件(经过os.getcwd来获取)。程序会将URL中包含的路径和当前的路径组装起来(URL中的路径放在self.path变量中,初始化的时候都是’/’)来获得用户须要的文件路径 若是路径不存在,或者不是个文件,函数将会经过产生并捕获一个异常来报告错误。若是路径和文件匹配,则会调用handle_file函数来读取并返回内容。这个函数读取文件而且使用send_content来发送给客户端
import BaseHTTPServerclass RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):'''Handle HTTP requests by returning a fixed 'page'.'''# Page to send back.Page = '''\<html><body><p>Hello, web!</p></body></html>'''# Handle a GET request.def do_GET(self):self.send_response(200)self.send_header("Content-Type", "text/html")self.send_header("Content-Length", str(len(self.Page)))self.end_headers()self.wfile.write(self.Page)if __name__ == '__main__':
serverAddress = ('', 8080)
server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)
server.serve_forever()
if __name__ == '__main__':serverAddress = ('', 8080)server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)server.serve_forever()BaseHTTPRequestHandler库会解析传入的HTTP请求而后决定里面包含的方法。若是方法是GET,类就会调用do_GET的函数。咱们本身的类RequestHandler重写了这个方法来动态生成网页:文本text存储在类级别的参数page,Page将会在发送了200响应码后发送给客户端,Content-Type头告诉客户端用HTML的方式来解析数据以及网页的长度(end_headers方法在咱们的头和网页之间插入空白行)可是RequestHandler并非整个的工程:咱们依然须要最后的三行启动服务器。第一行用一个元组的方式来定义服务器的地址:空字符意味着运行在本机上,8080是端口。而后咱们用整个地址和RequestHandler做为参数来建立BaseHTTPServer.HTTPServer实例,而后让程序永远运行(在实际中,除非用Control-C中止整个程序)若是咱们在命令行中运行整个项目,不会显示任何东西$ python server.py若是咱们在浏览器中输入http://localhost:8080,咱们会在浏览器中获得以下的显示Hello, web!在shell中将会看到127.0.0.1 - - [24/Feb/2014 10:26:28] "GET / HTTP/1.1" 200 -127.0.0.1 - - [24/Feb/2014 10:26:28] "GET /favicon.ico HTTP/1.1" 200 -第一行是直截了当的:由于咱们并无要求获取具体的文件,浏览器要求获取”/”(服务器运行的根目录)。第二行出现是由于浏览器自动发送第二个请求去获取图片文件/favicon.ico,它将在地址栏中显示为图标。显示数值让咱们修改下web服务器使得能够显示在HTTP请求中的内容(未来在调试的过程当中咱们常常会作这件事,因此咱们先练习下)为了保持咱们的代码干净,咱们将发送和建立页面分开class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):# ...page template...def do_GET(self):page = self.create_page()self.send_page(page)def create_page(self):# ...fill in...def send_page(self, page):# ...fill in...send_page的代码和以前的同样def send_page(self, page):self.send_response(200)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(page)))self.end_headers()self.wfile.write(page)想要显示的网页模板是一个字符串
,其中包含了
HTML
表格以及一些格式化的占位符
Page = '''\
<html>
<body>
<table>
<tr> <td>Header</td> <td>Value</td> </tr>
<tr> <td>Date and time</td> <td>{date_time}</td> </tr>
<tr> <td>Client host</td> <td>{client_host}</td> </tr>
<tr> <td>Client port</td> <td>{client_port}s</td> </tr>
<tr> <td>Command</td> <td>{command}</td> </tr>
<tr> <td>Path</td> <td>{path}</td> </tr>
</table>
</body>
</html>
'''
填充的方法以下
:
def create_page(self):
values = {
'date_time' : self.date_time_string(),
'client_host' : self.client_address[0],
'client_port' : self.client_address[1],
'command' : self.command,
'path' : self.path
}
page = self.Page.format(**values)
return page
程序的主体并无改变
:和以前同样,建立了一个
HTTPServer
类实例
,其中包含地址和请求,而后服务器就永远工做。若是咱们开始运行而且从浏览器中发送请求
http://localhost:8080/something.html
。咱们将获得:
Date and time Mon, 24 Feb 2014 17:17:12 GMT
Client host 127.0.0.1
Client port 54548
Command GET
Path /something.html
即便
something.html
网页不在网页上,咱们也没有发现
404
异常。这是由于服务器只是一个程序,
当收到请求时,它能够作任何它想作的事:发送回前一个请求中命名的文件,提供随机选择的维基百科页面,或者咱们对它进行编程的任何其余内容。 静态网页 下一步就是从硬盘上的网页开始启动而不是随机产生一个。咱们能够重写do_GET想要显示的网页模板是一个字符串,其中包含了HTML表格以及一些格式化的占位符Page = '''\<html><body><table><tr> <td>Header</td> <td>Value</td> </tr><tr> <td>Date and time</td> <td>{date_time}</td> </tr><tr> <td>Client host</td> <td>{client_host}</td> </tr><tr> <td>Client port</td> <td>{client_port}s</td> </tr><tr> <td>Command</td> <td>{command}</td> </tr><tr> <td>Path</td> <td>{path}</td> </tr></table></body></html>'''填充的方法以下:def create_page(self):values = {'date_time' : self.date_time_string(),'client_host' : self.client_address[0],'client_port' : self.client_address[1],'command' : self.command,'path' : self.path}page = self.Page.format(**values)return page程序的主体并无改变:和以前同样,建立了一个HTTPServer类实例,其中包含地址和请求,而后服务器就永远工做。若是咱们开始运行而且从浏览器中发送请求http://localhost:8080/something.html。咱们将获得:Date and time Mon, 24 Feb 2014 17:17:12 GMTClient host 127.0.0.1Client port 54548Command GETPath /something.html即便something.html网页不在网页上,咱们也没有发现404异常。这是由于服务器只是一个程序,def do_GET(self):try:# Figure out what exactly is being requested.full_path = os.getcwd() + self.path# It doesn't exist...if not os.path.exists(full_path):raise ServerException("'{0}' not found".format(self.path))# ...it's a file...elif os.path.isfile(full_path):self.handle_file(full_path)# ...it's something we don't handle.else:raise ServerException("Unknown object '{0}'".format(self.path))# Handle errors.except Exception as msg:self.handle_error(msg)
def handle_file(self, full_path):
try:
with open(full_path, 'rb') as reader:
content = reader.read()
self.send_content(content)
except IOError as msg:
msg = "'{0}' cannot be read: {1}".format(self.path, msg)
self.handle_error(msg)
注意到咱们用二进制的方式来打开文件--’rb’中的’b’. 这样Python就不会帮咱们经过过改变看起来像Windows行结尾的字节序列。而且在运行的时候,将整个的文件读进内存是个很糟糕的主意。像视频文件有多是好几个G的大小。可是处理那样的状况不在本章节的考虑以内。 为了完成这个类,咱们还须要写一个异常处理方法以及错误报告的网页模板
Error_Page = """\<html><body><h1>Error accessing {path}</h1><p>{msg}</p></body></html>"""def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content)
这个程序能够工做了,可是咱们仔细看会发现问题。问题在与老是返回200的状态码,即便被请求的的网页不存在。是的,在这种状况下,发送回的页面包含错误信息,可是浏览器不能阅读英文,因此也不知道request是成功仍是失败。为了让这种状况更清晰,咱们须要修改handle_error和send_content。# Handle unknown objects.
def handle_error(self, msg):
content = self.Error_Page.format(path=self.path, msg=msg)
self.send_content(content, 404)
# Send actual content.
def send_content(self, content, status=200):
self.send_response(status)
self.send_header("Content-type", "text/html")
self.send_header("Content-Length", str(len(content)))
self.end_headers()
self.wfile.write(content)
在一个文件没被找到的时候咱们没有抛出ServerException
异常
,而是产生了一个错误的页面。
ServerException
是为了在咱们本身搞错的时候发送一个内部错误的信号。
handle_error
建立的异常网页,只会在用户发生错误的时候发生。好比发送
URL
中的文件并不存在。
显示目录
# Handle unknown objects.def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content, 404)# Send actual content.def send_content(self, content, status=200):self.send_response(status)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(content)))self.end_headers()self.wfile.write(content)ServerException异常,而是产生了一个错误的页面。ServerException是为了在咱们本身搞错的时候发送一个内部错误的信号。handle_error建立的异常网页,只会在用户发生错误的时候发生。好比发送URL中的文件并不存在。显示目录下一步,咱们将教会服务器当URL是一个目录而不是文件的时候显示路径的内容。咱们还能够走远一点在路径中去寻找index.html文件并显示出来,而且在文件不存在的时候显示路径的内容。可是在do_GET中创建这些规则将会是个错误,由于所获得的方法将是一长串控制特殊行为的if语句。正确的解决方法是退后并解决通常性问题,那就是指出URL将要发生的动做。下面是对do_GET的重写。def do_GET(self):try:# Figure out what exactly is being requested.self.full_path = os.getcwd() + self.path# Figure out how to handle it.for case in self.Cases:handler = case()if handler.test(self):handler.act(self)break# Handle errors.except Exception as msg:self.handle_error(msg)第一步都是同样的:指出请求的全路径。尽管如此,代码仍是看起来不同,不是一堆的内联测试,这个版本查找存储在列表中的事件集合。每一个事件对象都有2个方法:test,用来告诉咱们是否能够处理这个请求以及act,用来实际执行动做。一旦咱们找到了正确的事件,咱们就开始处理请求而且跳出循环。下面三个对象事件从新塑造了服务器的行为:class case_no_file(object):'''File or directory does not exist.'''def test(self, handler):return not os.path.exists(handler.full_path)def act(self, handler):raise ServerException("'{0}' not found".format(handler.path))class case_existing_file(object):'''File exists.'''def test(self, handler):return os.path.isfile(handler.full_path)def act(self, handler):handler.handle_file(handler.full_path)class case_always_fail(object):'''Base case if nothing else worked.'''def test(self, handler):return Truedef act(self, handler):raise ServerException("Unknown object '{0}'".format(handler.path))在RequestHandler类的开始的时候,咱们将将创建事件处理列表。class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):'''If the requested path maps to a file, that file is served.If anything goes wrong, an error page is constructed.'''Cases = [case_no_file(),case_existing_file(),case_always_fail()]...everything else as before...如今服务器代码变得愈来愈复杂:代码行数从74变成了99,还有一个额外的间接级别且没有函数。当咱们回到本章开始的任务,并试图教咱们的服务器在index.html页面上提供一个目录(若是有的话)以及目录列表(若是没有的话)时,就会获得好处。以前的处理以下:class case_directory_index_file(object):'''Serve index.html page for a directory.'''def index_path(self, handler):return os.path.join(handler.full_path, 'index.html')def test(self, handler):return os.path.isdir(handler.full_path) and \os.path.isfile(self.index_path(handler))def act(self, handler):handler.handle_file(self.index_path(handler))index_path方法构建到index.html的路径;将其放入case处理程序能够防止主RequestHandler中的混乱,测试检查路径是不是包含index.html页面的目录,act请求主请求程序去为该网页提供服务。RequestHandler惟一的变化是在Cases列表中添加case_directory_index_file对象。Cases = [case_no_file(),case_existing_file(),case_directory_index_file(),case_always_fail()]若是路径中不包含index.html网页?测试和上面的同样,仅仅是插入了一个not语句,可是act方法如何处理?它应该作什么class case_directory_no_index_file(object):'''Serve listing for a directory without an index.html page.'''def index_path(self, handler):return os.path.join(handler.full_path, 'index.html')def test(self, handler):return os.path.isdir(handler.full_path) and \not os.path.isfile(self.index_path(handler))def act(self, handler):???看起来像是咱们将本身逼入了墙角。从逻辑上来讲,act方法应该建立,返回路径列表,可是咱们的代码不容许这样:RequestHandler.do_GET调用act,可是并无指望去处理和返回值。如今,让咱们在RequestHandler加一个方法去生成路径列表,而后从事件的处理器act中去调用。class case_directory_no_index_file(object):'''Serve listing for a directory without an index.html page.'''# ...index_path and test as above...def act(self, handler):handler.list_dir(handler.full_path)class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):# ...all the other code...# How to display a directory listing.Listing_Page = '''\<html><body><ul>{0}</ul></body></html>'''def list_dir(self, full_path):try:entries = os.listdir(full_path)bullets = ['<li>{0}</li>'.format(e)for e in entries if not e.startswith('.')]page = self.Listing_Page.format('\n'.join(bullets))self.send_content(page)except OSError as msg:msg = "'{0}' cannot be listed: {1}".format(self.path, msg)self.handle_error(msg)CGI协议固然,多数的人都不想去编辑web服务器的源代码来增长新的功能。为了避免给开发者增长更多的工做量,服务器老是支持称为CGI的机制,这为服务器提供了一种标准的方法去运行外部程序来知足需求。好比,加入咱们想服务器可以在HTML网页上显示当地时间。咱们能够在程序中增长几行代码from datetime import datetimeprint '''\<html><body><p>Generated {0}</p></body></html>'''.format(datetime.now())为了让服务器运行程序,咱们增长了事件处理器:class case_cgi_file(object):'''Something runnable.'''def test(self, handler):return os.path.isfile(handler.full_path) and \handler.full_path.endswith('.py')def act(self, handler):handler.run_cgi(handler.full_path)测试样例:这个路径是不是以.py结尾?若是是,RequestHandler运行这个程序def run_cgi(self, full_path):cmd = "python " + full_pathchild_stdin, child_stdout = os.popen2(cmd)child_stdin.close()data = child_stdout.read()child_stdout.close()self.send_content(data)这样很是的不安全:若是有人知道了服务器上的Python文件路径,咱们就容许去运行这些程序而没有去关心传入了些什么数据,是否包含了死循环或者其余的。先无论上面的这些,咱们的核心观点很简单:1 在子进程中运行程序2 捕获子进程发送到标准输出的任何数据3 将输出发送回触发请求的客户端完成的CGI程序比这个更加负责----总的来讲,它容许服务器将URL中的参数传递给正在运行的程序,可是这些细节并不影响系统的架构。RequestHandler有一个初始函数,handle_file用来处理内容。咱们如今以list_dir和run_cgi的形式增长了2个特殊的事件。这三个方法并不属于当前的位置,由于它们是被其余地方调用解决办法很简单:为全部的事件处理建立一个父类,若是其余方法被多个处理器共享使用就将其移入到类中。当咱们完成的时候,RequestHandler类看起来以下:class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):Cases = [case_no_file(),case_cgi_file(),case_existing_file(),case_directory_index_file(),case_directory_no_index_file(),case_always_fail()]# How to display an error.Error_Page = """\<html><body><h1>Error accessing {path}</h1><p>{msg}</p></body></html>"""# Classify and handle request.def do_GET(self):try:# Figure out what exactly is being requested.self.full_path = os.getcwd() + self.path# Figure out how to handle it.for case in self.Cases:if case.test(self):case.act(self)break# Handle errors.except Exception as msg:self.handle_error(msg)# Handle unknown objects.def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content, 404)# Send actual content.def send_content(self, content, status=200):self.send_response(status)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(content)))self.end_headers()self.wfile.write(content)事件处理器的父类以下:class base_case(object):'''Parent for case handlers.'''def handle_file(self, handler, full_path):try:with open(full_path, 'rb') as reader:content = reader.read()handler.send_content(content)except IOError as msg:msg = "'{0}' cannot be read: {1}".format(full_path, msg)handler.handle_error(msg)def index_path(self, handler):return os.path.join(handler.full_path, 'index.html')def test(self, handler):assert False, 'Not implemented.'def act(self, handler):assert False, 'Not implemented.'处理存在文件的代码以下:class case_existing_file(base_case):'''File exists.'''def test(self, handler):return os.path.isfile(handler.full_path)def act(self, handler):self.handle_file(handler, handler.full_path)
版本
,头信息和主体都是一样的格式。状态码是一个数字用来指示请求处理时发生了什么:
200
意味着正常工做,
404
意味着没有找到,其余的码也有不一样的意思。
对于这章节
,咱们只须要知道
HTTP
的其余两件事。
第一个就是无状态
:每一个请求都处理本身的,而且服务器端。服务器不会记住当前请求和下一个请求之间的内容。若是应用想跟踪好比用户身份的信息,就必须本身处理。
一般采用的方法是用
cookie
,
cookie
是服务器发送给客户端的字符流,而后客户端返回给服务器。当一个用户须要实如今不一样请求之间保持状态的时候,服务器会建立
cookie
,存储在数据库里,而后发送给浏览器。每次浏览器把
cookie
值发送回来的时候,服务器都会用来去查找信息来知道用户在干什么。
第二个咱们须要了解关于
HTTP
的就是
URL
能够经过提供参数来提供更多的信息
。好比,若是咱们在使用搜索引擎,咱们必须指定搜索术语。咱们能够加入到
URL
的路径中,可是咱们通常都是加入到
URL
的参数中。咱们在
URL
中增长
?,
后面跟随
key=value
而且用
&
符号分割来达到这个目的。好比
URL
http://www.google.ca?q=Python
就告诉
Google
去搜索
Python
相关的网页。键值是字母
q
,值是
Python
。更长的查询
http://www.google.ca/search?q=Python&client=Firefox
告诉
Google
咱们正在使用
Firefox
等等
。咱们能够传输任何咱们须要的参数。可是使用哪个,如何解释这些参数取决于应用。
固然
,若是
?
和
&
特殊的字符,那么必须有一种方法去规避,
正如必须有一种方法将双引号字符放入由双引号分隔的字符串中同样
。
URL
的编码标准用
%
后面跟
2
个字节码的方式来表示特殊字符,用
+
来代替空格。因此为了在
Google
上搜索”
grade=A+”
,咱们可使用的
URL
为
http://www.google.ca/search?q=grade+%3D+A%2B
建立
sockets
,构建
HTTP
请求,解析响应是很是枯燥的事情。因此人们更可能是使用库函数来完成大部分的工做。
Python
附带了一个
urllib2
的库,可是它暴露了不少人根本不关心的管道。
Request
库是能够替代
urllib2
而且更加好使用的库。下面是一个从
AOA
网站下载网页的例子。
import requestsresponse = requests.get('http://aosabook.org/en/500L/web-server/testpage.html')print 'status code:', response.status_codeprint 'content length:', response.headers['content-length']print response.textstatus code: 200content length: 61<html><body><p>Test page.</p></body></html>
requests.get
发送一个
HTTP GET
请求到服务器而后返回一个包含响应的对象。对象的
status_code
成员是响应的状态码;
content_length
成员是响应数据的长度,
text
是真是的数据
(
在这个例子中,是
HTTP
网页
)
你好
,web
如今咱们准备去写第一个
简单的
web
服务器。
1
等待某人链接到服务器上而且发送一个请求
2
解析请求
3
指出要求获取的东西
4
获取数据(或者动态的产生)
5
将数据格式化为
HTML
格式
6
发送回去
1,2,6
步对于各类不一样的应用来讲都是同样的,
Python
标准库有一个模块称为
BaseHTTPServer
为咱们作完成这些。咱们须要完成的是步骤
3
到步骤
5.
这一部分只须要不多的工做
当收到请求时,它能够作任何它想作的事:发送回前一个请求中命名的文件,提供随机选择的维基百科页面,或者咱们对它进行编程的任何其余内容。 静态网页 下一步就是从硬盘上的网页开始启动而不是随机产生一个。咱们能够重写do_GET 这个函数假设被容许web服务器正在运行的目录或者目录下的任何文件(经过os.getcwd来获取)。程序会将URL中包含的路径和当前的路径组装起来(URL中的路径放在self.path变量中,初始化的时候都是’/’)来获得用户须要的文件路径 若是路径不存在,或者不是个文件,函数将会经过产生并捕获一个异常来报告错误。若是路径和文件匹配,则会调用handle_file函数来读取并返回内容。这个函数读取文件而且使用send_content来发送给客户端 注意到咱们用二进制的方式来打开文件--’rb’中的’b’. 这样Python就不会帮咱们经过过改变看起来像Windows行结尾的字节序列。而且在运行的时候,将整个的文件读进内存是个很糟糕的主意。像视频文件有多是好几个G的大小。可是处理那样的状况不在本章节的考虑以内。 为了完成这个类,咱们还须要写一个异常处理方法以及错误报告的网页模板 这个程序能够工做了,可是咱们仔细看会发现问题。问题在与老是返回200的状态码,即便被请求的的网页不存在。是的,在这种状况下,发送回的页面包含错误信息,可是浏览器不能阅读英文,因此也不知道request是成功仍是失败。为了让这种状况更清晰,咱们须要修改handle_error和send_content。 在一个文件没被找到的时候咱们没有抛出 import BaseHTTPServerclass RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):'''Handle HTTP requests by returning a fixed 'page'.'''# Page to send back.Page = '''\<html><body><p>Hello, web!</p></body></html>'''# Handle a GET request.def do_GET(self):self.send_response(200)self.send_header("Content-Type", "text/html")self.send_header("Content-Length", str(len(self.Page)))self.end_headers()self.wfile.write(self.Page)if __name__ == '__main__':serverAddress = ('', 8080)server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)server.serve_forever()BaseHTTPRequestHandler库会解析传入的HTTP请求而后决定里面包含的方法。若是方法是GET,类就会调用do_GET的函数。咱们本身的类RequestHandler重写了这个方法来动态生成网页:文本text存储在类级别的参数page,Page将会在发送了200响应码后发送给客户端,Content-Type头告诉客户端用HTML的方式来解析数据以及网页的长度(end_headers方法在咱们的头和网页之间插入空白行)可是RequestHandler并非整个的工程:咱们依然须要最后的三行启动服务器。第一行用一个元组的方式来定义服务器的地址:空字符意味着运行在本机上,8080是端口。而后咱们用整个地址和RequestHandler做为参数来建立BaseHTTPServer.HTTPServer实例,而后让程序永远运行(在实际中,除非用Control-C中止整个程序)若是咱们在命令行中运行整个项目,不会显示任何东西$ python server.py若是咱们在浏览器中输入http://localhost:8080,咱们会在浏览器中获得以下的显示Hello, web!在shell中将会看到127.0.0.1 - - [24/Feb/2014 10:26:28] "GET / HTTP/1.1" 200 -127.0.0.1 - - [24/Feb/2014 10:26:28] "GET /favicon.ico HTTP/1.1" 200 -第一行是直截了当的:由于咱们并无要求获取具体的文件,浏览器要求获取”/”(服务器运行的根目录)。第二行出现是由于浏览器自动发送第二个请求去获取图片文件/favicon.ico,它将在地址栏中显示为图标。显示数值让咱们修改下web服务器使得能够显示在HTTP请求中的内容(未来在调试的过程当中咱们常常会作这件事,因此咱们先练习下)为了保持咱们的代码干净,咱们将发送和建立页面分开class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):# ...page template...def do_GET(self):page = self.create_page()self.send_page(page)def create_page(self):# ...fill in...def send_page(self, page):# ...fill in...send_page的代码和以前的同样def send_page(self, page):self.send_response(200)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(page)))self.end_headers()self.wfile.write(page)想要显示的网页模板是一个字符串,其中包含了HTML表格以及一些格式化的占位符Page = '''\<html><body><table><tr> <td>Header</td> <td>Value</td> </tr><tr> <td>Date and time</td> <td>{date_time}</td> </tr><tr> <td>Client host</td> <td>{client_host}</td> </tr><tr> <td>Client port</td> <td>{client_port}s</td> </tr><tr> <td>Command</td> <td>{command}</td> </tr><tr> <td>Path</td> <td>{path}</td> </tr></table></body></html>'''填充的方法以下:def create_page(self):values = {'date_time' : self.date_time_string(),'client_host' : self.client_address[0],'client_port' : self.client_address[1],'command' : self.command,'path' : self.path}page = self.Page.format(**values)return page程序的主体并无改变:和以前同样,建立了一个HTTPServer类实例,其中包含地址和请求,而后服务器就永远工做。若是咱们开始运行而且从浏览器中发送请求http://localhost:8080/something.html。咱们将获得:Date and time Mon, 24 Feb 2014 17:17:12 GMTClient host 127.0.0.1Client port 54548Command GETPath /something.html即便something.html网页不在网页上,咱们也没有发现404异常。这是由于服务器只是一个程序,def do_GET(self):try:# Figure out what exactly is being requested.full_path = os.getcwd() + self.path# It doesn't exist...if not os.path.exists(full_path):raise ServerException("'{0}' not found".format(self.path))# ...it's a file...elif os.path.isfile(full_path):self.handle_file(full_path)# ...it's something we don't handle.else:raise ServerException("Unknown object '{0}'".format(self.path))# Handle errors.except Exception as msg:self.handle_error(msg)def handle_file(self, full_path):try:with open(full_path, 'rb') as reader:content = reader.read()self.send_content(content)except IOError as msg:msg = "'{0}' cannot be read: {1}".format(self.path, msg)self.handle_error(msg)Error_Page = """\<html><body><h1>Error accessing {path}</h1><p>{msg}</p></body></html>"""def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content)# Handle unknown objects.def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content, 404)# Send actual content.def send_content(self, content, status=200):self.send_response(status)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(content)))self.end_headers()self.wfile.write(content)ServerException异常,而是产生了一个错误的页面。ServerException是为了在咱们本身搞错的时候发送一个内部错误的信号。handle_error建立的异常网页,只会在用户发生错误的时候发生。好比发送URL中的文件并不存在。显示目录