Python Tips: Python で https サーバーを動かしたい

Python で https サーバーを動かす方法を紹介します。 正確には、自己署名証明書(いわゆる「オレオレ証明書」)を使った、開発用・確認用の簡易サーバーをローカルで動かす方法について紹介します。

この方法は本番環境・公開環境では使わないようにしてください。

まず最初に通常の http サーバーを動かす方法をおさらいします。

http サーバーを動かす

python -m http.server

Python で http サーバーを動かす最もかんたんな方法は、 Python 本体に同梱の http.server モジュールを実行するやり方です。 python コマンドの -m オプションで http.server モジュールを指定して実行します:

python -m http.server

デフォルトではポート 8000 が使用されます。 このコマンドを実行した後にブラウザで http://localhost:8000 にアクセスするとウェブサーバーが動いていることが確認できます。

引数でポート番号を指定することもできます。

# ポート 8001 を使用する
python -m http.server 8001

このコマンドを実行してブラウザから http://localhost:8001 にアクセスすると、次のようなログがターミナルに出力されます:

python -m http.server 8001

Serving HTTP on :: port 8001 (http://[::]:8001/) ...
::ffff:127.0.0.1 - - [07/Mar/2021 17:24:29] "GET / HTTP/1.1" 200 -
::ffff:127.0.0.1 - - [07/Mar/2021 17:24:29] "GET /favicon.ico HTTP/1.1" 404 -

その他利用可能なオプションについては --help で確認することができます:

python -m http.server --help
usage: server.py [-h] [--cgi] [--bind ADDRESS]
                 [--directory DIRECTORY]
                 [port]

positional arguments:
  port                  Specify alternate port [default: 8000]

optional arguments:
  -h, --help            show this help message and exit
  --cgi                 Run as CGI Server
  --bind ADDRESS, -b ADDRESS
                        Specify alternate bind address
                        [default: all interfaces]
  --directory DIRECTORY, -d DIRECTORY
                        Specify alternative directory
                        [default:current directory]

Python の公式ドキュメントでも説明されているので興味のある方は読んでみてください。

socketserver.TCPServer

メッセージを出力するなどプラスアルファの処理をしたい場合は python -m http.server は使えないので、 socketserver.TCPServer を使う 10 行ほどのコードを書きます。 Python の公式ドキュメントには次のサンプルが載っています:

import http.server
import socketserver

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

このコードをファイルに保存して実行すれば、 python -m http.server と同様に簡易の http サーバーが走らせられます。

http.server.HTTPServer

socketserver.TCPServer の代わりに http.server.HTTPServer を使うこともできます。 http.server.HTTPServersocketserver.TCPServer を親に持つクラスです。

http.server.HTTPServer を使えば socketserverimport はいらなくなるので、コードが少しだけシンプルになります:

import http.server

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

with http.server.HTTPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

http.server.HTTPServer のソースコードは GitHub で確認できます:

これでおさらいは終わりです。 ここから https サーバーの説明です。

https サーバーを動かす

SSL/TLS のある https サーバーを動かすには証明書が必要なので、まずは証明書を用意します。

私の手元の macOS に入っている LibreSSL では、コマンドひとつで自己署名証明書が生成できました:

openssl req -x509 -new -days 365 -nodes \
  -keyout localhost.pem \
  -out localhost.pem \
  -subj "/CN=localhost"

コマンドが成功すると、次のようなテキストが出力されてカレントディレクトリに localhost.pem ファイルが生成されます。

Generating a 2048 bit RSA private key
........+++
...............................+++
writing new private key to 'localhost.pem'
-----

他の OS を使っている場合は、その OS に合った方法で証明書を生成します。

つづいて、この証明書を使って https サーバーを動かします。 次のコードをファイルに保存します。

run_server.py:

import ssl
from http.server import HTTPServer, SimpleHTTPRequestHandler

PORT = 443
CERTFILE = "./localhost.pem"

Handler = SimpleHTTPRequestHandler

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(CERTFILE)

with HTTPServer(("", PORT), Handler) as httpd:
    print("serving at address", httpd.server_address, "using cert file", CERTFILE)
    httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
    httpd.serve_forever()

ここでは、 ssl.SSLContext を使って証明書を読み込み HTTPServer の ソケットを置き換えています。

上で作成した証明書 localhost.pem と同じディレクトリでこのスクリプトを実行すれば https サーバーが起動します:

python run_server.py
serving at address ('0.0.0.0', 443) using cert file ./localhost.pem

その後ブラウザで https:/localhost を開くとサーバーが動いていることが確認できるはずです(自己署名証明書を使っているため、最初ブラウザに警告 ↓ が出ます)。

Chrome で自己署名証明書でエラー

ちなみに、 ssl.SSLContext は使わず ssl.wrap_socket() という関数を使うよりシンプルなやり方もありますが、その方法は Python 3.7 から deprecated となっているので、記事執筆時点では上のやり方をするのがよさそうです。

以上です。

ちなみに、 Python 本体に同梱の標準ライブラリだけを使う場合は上の方法がよいですが、そのような縛りが特に無い場合は便利なライブラリ( Gunicorn + Flask など)に頼った方がかんたんそうだと思います。

本記事のテーマに関連した Gist やリポジトリも作ったので興味のある方は参考にしてみてください:

参考