http.serverでCGIを動かす

今回までhttp.serverの話です。

前回の記事でカスタムハンドラーを作成する方法を紹介しましたのでPythonを使って動的にサイトを作ることもできるようになりましたが、これ以外にもCGIを使って動的なサイトを作ることもできます。
参考: Pythonのhttp.serverモジュールでカスタムハンドラーを実装する方法

要するに普通にPythonファイルをドキュメントルートに設置しておいて、それを動かせるわけですね。

一番シンプルな方法は、 http.serverをコマンドで起動する際に –cgi オプションをつけることです。デフォルトでは、ドキュメントルート直下の、 /cgi-bin と /htbin の 二つのディレクトリに配置されたファイルはCGIとして処理されるようになります。

この二つのディレクトリに固定されるのは、class http.server.CGIHTTPRequestHandler の、cgi_directories ってプロパティにそう指定されているからです。逆にこれ以外のディレクトリに.pyファイルを配置していてもhtmlファイルと同じようにただそのファイルの中身が返されます。

やってみましょう。

cgi-binというディレクトリを作成して、その直下に sample-cgi.py というファイル名で以下のスクリプトを書いておきます。

#!/usr/bin/env python

print("Content-Type: text/html; charset=utf-8\n")
print("<html><body>")
print("<h1>CGIスクリプト実行!</h1>")
print("</body></html>")

そして、このファイルにchmod 744 で実行権限をつけておきます。これ重要です。

そして、http.serverを起動します。

 % python -m http.server --cgi

こうすると localhost:8000/cgi-bin/sample-cgi.py にブラウザでアクセスすると、
CGIスクリプト実行! の文字が表示されます。

CGIで作成するとファイル名とURLがそのままシンプルに対応していくのでいくつも作る場合はシンプルで良いですね。

実は、class http.server.CGIHTTPRequestHandler というのを使うと、CGIディレクトリの指定とかがもっと柔軟に行えるのですが、http.server使ってそこまで凝ったことをすることもないんじゃないかなぁと思うので簡潔ですが今回の記事はここまでとします。

Pythonのhttp.serverモジュールでカスタムハンドラーを実装する方法

前回の記事に続いて、http.serverの話です。せっかくPythonを使ってWebサーバーを立てるわけですからファイルの内容を表示する静的サイトだけでなく、クエリパラメーターやフォームからPOSTされたデータを処理して表示する動的サイトの作り方を軽く紹介しておきます。

ただ、前回の記事でも書きました通り、http.server自体がプロダクション環境に適さない簡易的なものなのであくまでもちょっとした手元のツール等での利用に止めることを推奨します。

カスタムハンドラーの作成

http.serverのBaseHTTPRequestHandlerを継承してカスタムハンドラーを作成することで、特定の処理に対して独自の処理を行えます。

例えば、以下の内容で、sample1.py というファイルを作ってみましょう。

from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/hello":
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write("こんにちは!".encode("utf-8"))
        else:
            super().do_GET()

PORT = 8000
with HTTPServer(("", PORT), MyHandler) as httpd:
    print(f"Serving on port {PORT}")
    httpd.serve_forever()

そして、このファイルを実行します。

% python sample1.py

そうすると、`http://localhost:8000/hello` にアクセすると、こんにちは! のメッセージが表示されます。あとはプログラムで出力したい文字列を作成すれば任意のhtmlを返せますし、テンプレートファイルを読み込んでそれを表示すると言ったこともできます。

クエリパラメータの処理

次は、クエリパラメーターを受け取ってそれに応じた表示をするようにしてみましょう。

ファイル名は sample2.py等で作ります。実行方法は同じようにpythonコマンドにファイル名を渡すだけです。

from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed_path = urllib.parse.urlparse(self.path)
        query_params = urllib.parse.parse_qs(parsed_path.query)
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=utf-8")
        self.end_headers()
        response = f"Path: {parsed_path.path}, Query parameters: {query_params}"
        self.wfile.write(response.encode("utf-8"))


PORT = 8000
with HTTPServer(("", PORT), MyHandler) as httpd:
    print(f"Serving on port {PORT}")
    httpd.serve_forever()

pathとパラメーターを受け取ってそれを表示するようにしてみました。先ほどの例と同様に、
% python sample2.py で起動して、 http://localhost:8000/hello?name=%E3%82%86%E3%81%86%E3%81%9F%E3%82%8D%E3%81%86 にアクセスすると、
Path: /hello, Query parameters: {‘name’: [‘ゆうたろう’]}
という表示が得られます。

上記のスクリプトでパスとパラメーターが取得できているのであとはそれを自由に活用するコードを書くだけです。

POSTリクエストの処理(フォームデータの処理)

最後にポストされたデータの処理方法を書いておきます。

これはポストするフォームも必要なのでそちらから用意します。
index.html という名前で次のファイルを作っておいてください。

<!DOCTYPE html>
<html>
<head>
    <title>Form Submission</title>
</head>
<body>
    <form action="/submit" method="post">
        <label for="name">Name:</label>
        <input type="text" id="name" name="name"><br>
        <label for="age">Age:</label>
        <input type="text" id="age" name="age"><br>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

そして作成するpythonファイル、 sample3.pyを用意します。

from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            with open("index.html", "rb") as file:
                self.wfile.write(file.read())
        else:
            self.send_response(404)
            self.end_headers()

    def do_POST(self):
        if self.path == "/submit":
            content_length = int(self.headers["Content-Length"])
            post_data = self.rfile.read(content_length)
            data = urllib.parse.parse_qs(post_data.decode("utf-8"))
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            response = f"Received: {data}"
            self.wfile.write(response.encode("utf-8"))
        else:
            self.send_response(404)
            self.end_headers()

PORT = 8000
with HTTPServer(("", PORT), MyHandler) as httpd:
    print(f"Serving on port {PORT}")
    httpd.serve_forever()

これで、 localhost:8000 にアクセスすると、 do_GETメソッドが実行されてindex.htmlのファイルの中身(フォーム)が表示され、フォームにデータをPOSTすると、do_POSTメソッドが実行されてフォームで送信した内容が表示されます。

簡単ではありますが、以上がhttp.serverモジュールを利用したカスタムハンドラーの作成やクエリパラメーターの処理、POSTリクエストの処理方法でした。