HTTP PUT でファイルアクセス

HTTP のGETとPUTを使った簡単なファイルサーバがテスト用に必要になったのでPythonで書いてみた.Python標準のBasicHTTPServerのハンドラにはPUTもPOSTも実装されていないのだけど,拡張は可能になっている.実際,CGIHTTPServerではPOSTをサポートしている.

    mname = 'do_' + self.command
    if not hasattr(self, mname):
        ...

みたいに書かれていて,ハンドラオブジェクトでdo_XXXXメソッドを実装すると,XXXXメソッドがディスパッチされる.

SimpleHTTPRequestHandlerを拡張して実装してみた.do_POSTとdo_PUTを同じものとして実装してある.SimpleHTTPRequestHandlerを拡張しているので,GETはそのまま使える.実行すると実行パス以下にファイルを書く.'..'とか使うと実行パスよりも上にもかけちゃうのはまずいので,さすがにそれはチェックするようにしてみた.

テストには,QuickPutというツールが使える.

$ python QuickPut.py FILENAME URL

cURLを使ってもできる.

$ curl -O -T FILENAME URL

'-O'を付けているのはHTTP/1.0を使うようにという指定.これがないとデフォルトの1.1になり,Expect: 100-continueが出てしまい,ちょっと待つようになって遅くなる.100 を出すようにもしてみたのだけど,その場合にもちょっとブロックしているように見える.curlが悪いとは思えないので私のプロトコル解釈がどこか間違っているのだろうけど,当面放置.

ソース

"""Puttable HTTP Server.

This module builds on SimpleHTTPServer implementing PUT and POST
to put files. Note this is really dangerous!! Just for test.
"""
__version__ = "0.1"
__all__ = ["SimpleHTTPRequestHandler"]
import os
import sys
import BaseHTTPServer
import SimpleHTTPServer

class PuttableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):

    server_version = "PuttableHTTP/" + __version__

    def do_PUT(self):
        """Serve a PUT request."""
        self.do_POST()

    def do_POST(self):
        """Serve a POST request."""
        self._writeOut(os.path.join(os.getcwd(), self.path[1:]), self.rfile)

    def _copyCont(self, src, dest, length):
        buflen = 10000
        while length > 0:
            b = src.read(min(buflen, length))
            dest.write(b)
            length = length - len(b)

    def _writeOut(self, path, f):
	if path.find('/../') >= 0:
            self.send_response(403, "forbidden")
            return
        length = self.headers.getheader('content-length')
        if not length:
            self.send_response(411, "length required")
            return;
        try:
            existFlag = os.path.exists(path)
            dest = file(path, 'w')
            self._copyCont(f, dest, int(length))
            dest.close()
            if existFlag:
                self.send_response(204, "post succeeded, modified")
                return
            else:
                self.send_response(201, "post succeeded")
                return
        except IOError, msg:
            self.send_response(401, "faield to writefile," + msg)
            return
        
def test(HandlerClass = PuttableHTTPRequestHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    BaseHTTPServer.test(HandlerClass, ServerClass)


if __name__ == '__main__':
    test()