美文网首页网络编程魔法Tornado
Python SimpleHTTPServer.py 源码分析

Python SimpleHTTPServer.py 源码分析

作者: 人世间 | 来源:发表于2015-06-01 14:45 被阅读4480次

    前面分析BaseServer和BaseHTTPServer,可以知道BaseHTTPRequestHandler中handle_one_request方法将会通过自省的方式,调用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将会在BaseHTTPRequestHandler的子类中实现。我们已经知道,但凡以Base开头的class,都需要被继承,然后在其子类中实现相关的方法。

    SimpleHTTPServer

    构建一个简单的HTTP服务,需要继承HTTPServer,同时requesthandler也需要继承BaseHTTPRequestHandler。python已经实现了一个例子,那就是SimpleHTTPServer。因此分析SimpleHTTPServer来查看如何使用前面的一些类构建http服务。

    曾经为了表示python的简洁优雅,经常会举这样的例子,python可以一行代码开启一个服务器。

    $ python -m SimpleHTTPServer
    

    这里的SimpleHTTPServer就是实现了HTTPServer的模块。

    SimpleHTTPServer通过调用BaseHTTPServer模块的test方法做为入口。

    def test(HandlerClass = SimpleHTTPRequestHandler,
             ServerClass = BaseHTTPServer.HTTPServer):
        BaseHTTPServer.test(HandlerClass, ServerClass)
    

    test方法做了两件事,第一件就是使用HTTPServer接受一个监听地址和requestClass参数,创建了一个实例对象,调用server_forever方法开启服务。

    SimpleHTTPRequestHandler

    根据之前的分析,使用httpserver的服务,我们只需要继续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 分别实现了http的get请求和head请求的处理。他们调用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分析得到客户请求的网路路径。构造head的mime元信息并发送到客户端,然后返回一个打开path的文件句柄。

    copyfile

    do_GET的下一步就是通过 copyfile方法,将客户请求的path的文件数据写入到缓冲可写文件中,发送给客户端。

    list_directory

    SimpleHTTPServer模块还提供了list_directory方法,用于响应path是一个目录,而不是文件的情况。

    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 ,就能向客户端发送reponse。

    自定义http服务

    定义一个CustomHTTPRequestHadnler继承自BaseHTTPRequestHandler。在其内实现do_GET 方法来处理get请求。

    然后再定义一个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
    ➜  ~
    

    控制台会打出访问的log。

    127.0.0.1 - - [01/Jun/2015 11:42:33] "GET / HTTP/1.1" 200 -
    

    从socket的建立,select的IO模式,再到Server和Handler的组合构建服务。我们已经熟悉了python的基本网络编程。python的web开发中,更多是使用WSGI协议。实现该协议的还有 uWSGI和gunicorn等库。相比那些库,python内部提供了一个wsgiref模块,实现了一个简单wsgi服务--simple_server。

    接下来将会通过分析simple_server,更好的掌握WSGI协议。

    相关文章

      网友评论

      • 阿群1986:'Content-type', 'text/html'可以改为'text/plain'
      • dc3491ba0d17:前面分析BaseServer和BaseHTTPServer
        没找到这篇文章呢

      本文标题:Python SimpleHTTPServer.py 源码分析

      本文链接:https://www.haomeiwen.com/subject/dchdqttx.html