前面分析BaseServer和BaseHTTPServer咧最,可以知道BaseHTTPRequestHandler中handle_one_request方法將會(huì)通過自省的方式制轰,調(diào)用HTTP客戶端請求的方法余蟹。
def handle_one_request(self):
try:
# 省略... ...
method = getattr(self, mname)
method()
self.wfile.flush() #actually send the response if not already done.
except socket.timeout, e:
# 省略... ...
顯然method將會(huì)在BaseHTTPRequestHandler的子類中實(shí)現(xiàn)迎瞧。我們已經(jīng)知道哮缺,但凡以Base開頭的class苟穆,都需要被繼承,然后在其子類中實(shí)現(xiàn)相關(guān)的方法稿黄。
SimpleHTTPServer
構(gòu)建一個(gè)簡單的HTTP服務(wù)喊衫,需要繼承HTTPServer,同時(shí)requesthandler也需要繼承BaseHTTPRequestHandler杆怕。python已經(jīng)實(shí)現(xiàn)了一個(gè)例子格侯,那就是SimpleHTTPServer。因此分析SimpleHTTPServer來查看如何使用前面的一些類構(gòu)建http服務(wù)财著。
曾經(jīng)為了表示python的簡潔優(yōu)雅联四,經(jīng)常會(huì)舉這樣的例子,python可以一行代碼開啟一個(gè)服務(wù)器撑教。
$ python -m SimpleHTTPServer
這里的SimpleHTTPServer就是實(shí)現(xiàn)了HTTPServer的模塊正林。
SimpleHTTPServer通過調(diào)用BaseHTTPServer模塊的test方法做為入口驶忌。
def test(HandlerClass = SimpleHTTPRequestHandler,
ServerClass = BaseHTTPServer.HTTPServer):
BaseHTTPServer.test(HandlerClass, ServerClass)
test方法做了兩件事售滤,第一件就是使用HTTPServer接受一個(gè)監(jiān)聽地址和requestClass參數(shù),創(chuàng)建了一個(gè)實(shí)例對象亿卤,調(diào)用server_forever方法開啟服務(wù)。
SimpleHTTPRequestHandler
根據(jù)之前的分析鹿霸,使用httpserver的服務(wù)排吴,我們只需要繼續(xù)BaseHTTPRequestHandler,并提供自省的method方法即可懦鼠。
class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
server_version = "SimpleHTTP/" + __version__
def do_GET(self):
f = self.send_head()
if f:
self.copyfile(f, self.wfile)
f.close()
def do_HEAD(self):
f = self.send_head()
if f:
f.close()
do_GET 和 do_HEAD 分別實(shí)現(xiàn)了http的get請求和head請求的處理钻哩。他們調(diào)用send_head方法:
def send_head(self):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
if not self.path.endswith('/'):
self.send_response(301)
self.send_header("Location", self.path + "/")
self.end_headers()
return None
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
try:
f = open(path, 'rb')
except IOError:
self.send_error(404, "File not found")
return None
self.send_response(200)
self.send_header("Content-type", ctype)
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
self.end_headers()
return f
send_head 方法通過uri的path分析得到客戶請求的網(wǎng)路路徑。構(gòu)造head的mime元信息并發(fā)送到客戶端肛冶,然后返回一個(gè)打開path的文件句柄街氢。
copyfile
do_GET的下一步就是通過 copyfile方法,將客戶請求的path的文件數(shù)據(jù)寫入到緩沖可寫文件中睦袖,發(fā)送給客戶端珊肃。
list_directory
SimpleHTTPServer模塊還提供了list_directory方法,用于響應(yīng)path是一個(gè)目錄馅笙,而不是文件的情況伦乔。
def list_directory(self, path):
try:
list = os.listdir(path)
except os.error:
self.send_error(404, "No permission to list directory")
return None
list.sort(key=lambda a: a.lower())
f = StringIO()
displaypath = cgi.escape(urllib.unquote(self.path))
f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
f.write("<hr>\n<ul>\n")
for name in list:
fullname = os.path.join(path, name)
displayname = linkname = name
# Append / for directories or @ for symbolic links
if os.path.isdir(fullname):
displayname = name + "/"
linkname = name + "/"
if os.path.islink(fullname):
displayname = name + "@"
# Note: a link to a directory displays with @ and links with /
f.write('<li><a href="%s">%s</a>\n'
% (urllib.quote(linkname), cgi.escape(displayname)))
f.write("</ul>\n<hr>\n</body>\n</html>\n")
length = f.tell()
f.seek(0)
self.send_response(200)
encoding = sys.getfilesystemencoding()
self.send_header("Content-type", "text/html; charset=%s" % encoding)
self.send_header("Content-Length", str(length))
self.end_headers()
return f
由此可見,處理客戶端的請求董习,只需要使用 send_reponse评矩, send_header 和 end_headers ,就能向客戶端發(fā)送reponse阱飘。
自定義http服務(wù)
定義一個(gè)CustomHTTPRequestHadnler繼承自BaseHTTPRequestHandler。在其內(nèi)實(shí)現(xiàn)do_GET 方法來處理get請求虱颗。
然后再定義一個(gè)CustomHTTPServer繼承自HTTPServer沥匈,它接受CustomHTTPRequestHadnler作為自己的handler。簡單的代碼如下:
# -*- coding: utf-8 -*-
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
class CustomHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write("hello world\r\n")
class CustomHTTPServer(HTTPServer):
def __init__(self, host, port):
HTTPServer.__init__(self, (host, port), CustomHTTPRequestHandler)
def main():
server = CustomHTTPServer('127.0.0.1', 8000)
server.serve_forever()
if __name__ == '__main__':
main()
使用curl訪問可以得到
? ~ curl http://127.0.0.1:8000
hello world
? ~
控制臺(tái)會(huì)打出訪問的log忘渔。
127.0.0.1 - - [01/Jun/2015 11:42:33] "GET / HTTP/1.1" 200 -
從socket的建立高帖,select的IO模式,再到Server和Handler的組合構(gòu)建服務(wù)畦粮。我們已經(jīng)熟悉了python的基本網(wǎng)絡(luò)編程散址。python的web開發(fā)中,更多是使用WSGI協(xié)議宣赔。實(shí)現(xiàn)該協(xié)議的還有 uWSGI和gunicorn等庫预麸。相比那些庫,python內(nèi)部提供了一個(gè)wsgiref模塊儒将,實(shí)現(xiàn)了一個(gè)簡單wsgi服務(wù)--simple_server吏祸。
接下來將會(huì)通過分析simple_server,更好的掌握WSGI協(xié)議钩蚊。