Python 源码之SimpleHttpServer 2016-04-10 # Python 源码之SimpleHttpServer [TOC] 这篇是细度Python 源码SimpleHTTPServer 模块之后的笔记,在["Python 网络编程之 SocketServer"](http://midday.me/article/f8e1f2fd8c22487d8a20a243188b6dfe)这篇简单的解读了SocketServer模块的源码,但发现文章还是讲得不够清晰,这篇文章也会从结构上做些总结,因为HTTPServer 是建立在TCPserver 之上的。 ### 模拟实现SimpleHTTPServer #### SimpleHTTPServer简介 SimpleHTTPServer 是 Python Standard Library 之一, 用法和功能很简单 ``` import SimpleHTTPServer import SocketServer PORT = 8000 Handler = SimpleHTTPServer.SimpleHTTPRequestHandler httpd = SocketServer.TCPServer(("", PORT), Handler) print "serving at port", PORT httpd.serve_forever() ``` 还有更简单的方式就是 ``` python -m SimpleHTTPServer 8000 ``` 两种方法都会起一个Server 用浏览器访问就会默认列出当前目录下的所有文件和文件夹。 #### 功能模拟 既然SimpleHTTPServer 是建立在TCPserver 之上的,自然就可以用socket 来实现。这样做的好处是,可以完全不考虑源码在代码结构上的设计,Python 的结构设计还是很好的,从最底层的BaseServer 到TcpServer 到HTTPServer 整个读完还是很模糊,开始对理解会造成一种障碍。为什么要这样一层层继承。为了更好的理解,只有在动手尝试之后会有更深刻的体会(就像你读我的文章和我自己读完全是不一样的)。用socket 先不去考虑HTTP 协议之类的东西,做完之后再去读源码就理解得更加深刻. ``` import socket import urllib import cgi import os import select class SimpleHttpServerTest(): def __init__(self, server_address): self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server.bind(server_address) self.server.listen(5) def server_forever(self): while True: r, w, e = select.select([self], [], [], 0.5) if self in r: self.handle_one_request() def fileno(self): return self.server.fileno() def list_directory(self, path, wfile): try: list = os.listdir(path) except os.error: return None list.sort(key=lambda a :a.lower()) wfile.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">') wfile.write("<html>\n<title>Directory listing for %s</title>\n" % path) wfile.write("<body>\n<h2>Directory listing for %s</h2>\n" % path) wfile.write("<hr>\n<ul>\n") for name in list: fullname = os.path.join(path, name) displayname = linkname = name if os.path.isdir(fullname): displayname = name + "/" linkname = name + "/" if os.path.islink(fullname): displayname = name + "@" wfile.write('<li><a href="%s">%s</a>\n' % (urllib.quote(linkname), cgi.escape(displayname))) wfile.write("</ul>\n<hr>\n</body>\n</html>\n") return def translate_path(self, path): path = path.split('?',1)[0] path = path.split('#',1)[0] # Don't forget explicit trailing slash when normalizing. Issue17324 trailing_slash = path.rstrip().endswith('/') words = path.split('/') words = filter(None, words) path = os.getcwd() for word in words: drive, word = os.path.splitdrive(word) head, word = os.path.split(word) if word in (os.curdir, os.pardir): continue path = os.path.join(path, word) if trailing_slash: path += '/' return path def handle_one_request(self): (clientsocket, address) = self.server.accept() rfile = clientsocket.makefile('rb', -1) wrile = clientsocket.makefile('wb', 0) raw_requestline = rfile.readline(65537) raw_requestline.rstrip('\r\n') command, path, version = raw_requestline.split() path = self.translate_path(path) if os.path.isdir(path): self.list_directory(path, wrile) else: try: f = open(path, 'rb') wrile.write(f.read()) except Exception, e: print e pass if __name__ == '__main__': server = SimpleHttpServerTest(('127.0.0.1',7777)) server.server_forever() ``` 上面就这段代码就是实现的功能,效果如下图  和SimpleHTTPServer 的区别是不支持文件下载,当点击一个.py文件的时候会把所有的代码输出到页面上,因为这里的server 是用write 来输出的,没有实现HTTP协议,去浏览器的控制台会发现,浏览器报错很多。写完这个Demo 对HTTP 和TCP ,socket ,之间的关系好像又深了许多。TcpServer 会一直连接,这里的Demo 也没断开连接,也告诉浏览器断开连接,其实是浏览器自行断开了.在SimpleHTTPServer 的代码里我们就能看到HTTPServer 会告诉浏览器断开连接同时还会报错。 ### 源码 基本所有以Server 结尾的名称如BaseServer, TCPServer ,HTTPServer 大都提供了些接口,和整个代码框架会在Server里面,而且从BaseServer就基本明白了,Server 会有一个属性叫Handler 如StreamRequestHandler,BaseHTTPRequestHandler 所有对连接的处理都是在Handler 里面实现的,HTTP 协议之类的都是如此,HTTPServer 和TCPServer 的源码基本一样,只是前者重写了server_bind方法,其他所有和HTTP 相关的东西都是在Handler 里面实现的。之前还见到ThreadingMixIn,ForkingMixIn 这样的类,其实他们就是重写BaseServer 提供的接口来实现一些其他的功能而已,当你自己写一个Server 同时继承TCPServer 和ForkingMixIn 的时候,TCPServer 的process_request 方法就会是ForkingMini实现的从而实现了并行的。 SimpleHTTPServer.py 的代码总共236行 只有一个Handler 定义如下 ``` class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): ``` 它继承了**BaseHTTPServer.BaseHTTPRequestHandler** HTTP 协议相关的东西不在这里,它只实现我Demo 中的功能,获得路径下的所有文件。 BaseHTTPRequestHandler 在BaseHTTPServer.py 中, ``` class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ``` 具体协议的东西我不想扯,就是按照某种规则告诉浏览器端该干什么就好了,规则是什么有兴趣的去读RFC文档吧。读到这里要自己实现一个简单的WebServer也不难了,可从socket 开始。