HTTPリクエストハンドラを書く

PythonのSimpleHTTPServerはクライアントからのreqestを受け取るとそれをRequestHandlerClassに渡してインスタンス化します。これはSocketServerモジュール中のBaseServerを見ると分かります。

    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
        self.RequestHandlerClass(request, client_address, self)

意外かもしれませんが、ServerがRequestHandlerを扱うのはここだけです。つまり、このような引数をとるコンストラクタさえ実装してしまえば、どんなクラスでもRequestHandlerClassになれます。が、clientに文字列を送ることを考えれば

class HTTPRequestHandler(object):
    responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
    def __init__(self, request, client_address, server):
        self.request = request
        self.setup()
        self.handle()
        self.finish()
    def setup(self):
        self.wfile = self.request.makefile('wb', 0)
    def finish(self):
        if not self.wfile.closed:
            self.wfile.flush()
        self.wfile.close()
    def handle(self):
      self.wfile.write("Hello World\r\n")

みたいな処理をするClassがRequestHandlerになります。HTTPプロトコルのことを考慮すればヘッダーに情報を送らないといけないでしょうし、リクエストを受信してparseする必要もあります。そのようなことをできるように最低限書くと次のようになります。

#!/usr/bin/env python2.6
#myserver.py
import shutil
import BaseHTTPServer
import os
class HTTPRequestHandler(object):
    responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
    def __init__(self, request, client_address, 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 finish(self):
        if not self.wfile.closed:
            self.wfile.flush()
        self.wfile.close()
        self.rfile.close()
    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 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"
    def date_time_string(self):
        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")
    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):
        f = open(self.path[1:], "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
if __name__ == '__main__':
    import SimpleHTTPServer
    SimpleHTTPServer.test(HandlerClass=HTTPRequestHandler)

エラー処理をほとんどしていませんし、ヘッダーへ送っている情報も嘘っぱちですが"./myserver.py 1128"などとしてwebブラウザで"http://localhost:1128/myserver.py"を開けばmyserver.pyの中身が見れます。テストは前回のものを参考に書きました。