wsgiで静的ファイルを扱うサーバーを書く

タイトルの通り、SimpleHTTPServerのように動くwsgi的なアプリケーションを書きました。HTTPRequestHandlerの部分が少し癖があるので、標準のハンドラではこのアプリケーションは動かないと思います。あとはエラー処理もしていないですね。

#!/usr/bin/env python2.6
from myserver import HTTPRequestHandler
import os
def RegularApplication(environ, start_response):
    req = Request(environ, start_response)
    req.send_file(environ['PATH_INFO'])
    return ''
class Request(object):
    def __init__(self, environ, start_response):
        self.environ = environ
        self._start_response = start_response
        self._outheaders = []
        self._write = None
    def send_file(self, path, content_type="text/plain", status=200):
        path = "." + path
        self.send_response(status)
        self.send_header("Content-type", content_type)
        stat = os.stat(path)
        self.send_header("Content-Length", stat.st_size)
        self.send_header("Last-Modified", HTTPRequestHandler.date_time_string())
        self.end_headers()
        f = open(path, "rb")
        self._write(f.read())
    def send_response(self, status):
        self._status = status
    def send_header(self, key, value):
        self._outheaders.append((key,value))
    def end_headers(self):
        self._write = self._start_response(self._status, self._outheaders)
class WSGIRequestHandler(HTTPRequestHandler):
    def handle(self):
        self.raw_requestline = self.rfile.readline()
        if not self.raw_requestline:
            return
        self.parse_request()                                                
        gateway = WSGIGateWay(self, self.get_environ())
        gateway.run(self.server.get_app())
    def get_environ(self):
        environ = self.server.base_environ.copy()
        environ['PATH_INFO'] = self.path
        return environ
class WSGIGateWay(object):
    def __init__(self, handler, environ):        
        self.environ = environ
        self.handler = handler
        self.headers_set = []
        self.headers_sent = []
    def run(self, application):
        self.result = application(self.environ, self.start_response)
        self.finish_response()
    def start_response(self, status, headers):
        self.headers_set = [status, headers]
        return self._write
    def finish_response(self):
        for chunk in self.result:
            self._write(chunk)
    def _write(self, data):
        assert self.headers_set
        if not self.headers_sent:
            status, headers = self.headers_sent = self.headers_set
            self.handler.send_response(status)
            for key, value in headers:
                self.handler.send_header(key, value)
            self.handler.wfile.write("\r\n")
        self.handler.wfile.write(data)
import sys
if __name__ == '__main__':
    from wsgiref import simple_server
    if sys.argv[1:]:
        port = sys.argv[1]
    else:
        port = 5986
    httpd = simple_server.make_server('', port, RegularApplication, handler_class = WSGIRequestHandler)
    sa = httpd.socket.getsockname()
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
    httpd.serve_forever()

myserverモジュールはもともとここで定義していたものを次のように修正しました。HTTPRequestHandlerにもともとあったメソッドのほとんどをBaseHTTPRequestHandlerに移しています。

#!/usr/bin/env python2.6
import shutil
import BaseHTTPServer
import os
import StringIO
class BaseHTTPRequestHandler(object):
    responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
    def __init__(self, request, client_address, server):
        self.server = server
        self.request = request
        self.setup()
        self.handle()
        self.finish()
    def setup(self):
        self.rfile = self.request.makefile('rb', -1)
        self.wfile = self.request.makefile('wb', 0)
    def handle(self):
        raise NotImplementedError
    def finish(self):
        if not self.wfile.closed:
            self.wfile.flush()
        self.wfile.close()
        self.rfile.close()
    def parse_request(self):
        assert(self.raw_requestline[-2:] == '\r\n')
        words = self.raw_requestline[:-2].split()
        assert(3 == len(words))
        self.command, self.path, self.request_version = words
        return True
    def send_response(self, code, message=None):
        if message is None:
            if code in self.responses:
                message = self.responses[code][0]
            else:
                message = ''
        if self.request_version != "HTTP/0.9":
            self.wfile.write("HTTP/1.0 %d %s\r\n" % (code, message))
        self.send_header("Server", self.version_string())
        self.send_header("Date", self.date_time_string())
    def version_string(self):
        return "SimpleHTTP/0.6 Python/2.6.5"
    @staticmethod
    def date_time_string():
        return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
            "Sun", 28, "Nov", 2011, 3, 18, 14)
    def send_header(self, key, value):
        if self.request_version != "HTTP/0.9":
            self.wfile.write("%s: %s\r\n" % (key, value))
    def end_headers(self):
        if self.request_version != "HTTP/0.9":
            self.wfile.write("\r\n")
class HTTPRequestHandler(BaseHTTPRequestHandler):
    def handle(self):
        self.raw_requestline = self.rfile.readline()
        if not self.raw_requestline:
            return
        self.parse_request()
        mname = 'do_' + self.command
        if not hasattr(self, mname):
            return
        method = getattr(self, mname)
        method()
    def do_GET(self):
        f = self.send_head()
        if f is not None:
            shutil.copyfileobj(f, self.wfile)
            f.close()
    def do_HEAD(self):
        f = self.send_head()
        if f is not None:
            f.close()
    def send_head(self):
        path = "." + self.path
        if os.path.isdir(path):
            return self.list_directory(path)
        f = open(path, "rb")
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        fs = os.fstat(f.fileno())
        self.send_header("Content-Length", fs[6])
        self.send_header("Last-Modified", self.date_time_string())
        self.end_headers()
        return f
    def list_directory(self, path):
            f = StringIO.StringIO()
            f.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"><html>\n")
            f.write("<title>Directory listing for %s</title>\n" % self.path)
            f.write("<body>\n")
            f.write("<h2>Directory listing for %s</h2>\n" % self.path)
            f.write("<hr>\n<ul>\n")
            for name in os.listdir(path):
                f.write("<li><a href=\"%s\">%s</a></li>\n" % (name, name))
            f.write("</ul>\n<hr>\n</body>\n</html>\n")
            self.send_response(200)
            self.send_header("Content-type", "text/html") 
            self.send_header("Content-Length", f.tell())
            self.end_headers()
            f.seek(0)
            return f
if __name__ == '__main__':
    import SimpleHTTPServer
    SimpleHTTPServer.test(HandlerClass=HTTPRequestHandler)