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)