pythonでシンプルなHTTPサーバを作る

# 目的 #
この記事はpythonで最もシンプルなコードでテスト用スタブHTTPサーバを作成する方法を紹介します。

# 背景 #
プロジェクト開発の時に、別チームが開発するサーバへHTTP通信する場合があります。しかし、開発進捗など理由で相手のプロジェクトが使えない時に、こちらのシステムを先に制作または試験の指示が来たことがあります。では、セキュリティやパフォーマンスなど考慮せず、最低のコストで、一時的なスタブを作成する方法はありますか。

# 解決案 #

1. スタブの機能
GETリクエストを受信し、HTMLページを戻します。
POSTリクエストを受信し、特定値によって、事前に用意する結果をレスポンスとして戻します。

1. 実装
BaseHTTPRequestHandlerを拡張し、GETとPOSTを処理するメソッドを作る

    ~~~python
    import os
    import sys
    import urllib.parse
    import html

    from http.server import BaseHTTPRequestHandler
    from http.server import HTTPServer
    from http import HTTPStatus

    PORT = 8000

    #-------------------------------------------------------
    data = {}
    data["1234123412341234"] = "test1,test2,123,455,24,5,6,,4,,4344356,,34,ert"
    data["0000000000000000"] = "testdata2"
    #-------------------------------------------------------

    class StubHttpRequestHandler(BaseHTTPRequestHandler):
        server_version = "HTTP Stub/0.1"

        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)

        def do_GET(self):
            enc = sys.getfilesystemencoding()
            title = "HTTP Stub"

            r = []
            r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
                     '"http://www.w3.org/TR/html4/strict.dtd">')
            r.append('<html>\n<head>')
            r.append('<meta http-equiv="Content-Type" '
                     'content="text/html; charset=%s">' % enc)
            r.append('<title>%s</title>\n</head>' % title)
            r.append('<body>\n<h1>%s</h1>' % title)
            r.append('<hr>\n<ul>')
            r.append("Stub Opened.")
            r.append('</ul>\n<hr>\n</body>\n</html>\n')
            encoded = '\n'.join(r).encode(enc, 'surrogateescape')

            self.send_response(HTTPStatus.OK)
            self.send_header("Content-type", "text/html; charset=%s" % enc)
            self.send_header("Content-Length", str(len(encoded)))
            self.end_headers()

            self.wfile.write(encoded)     
    
        def do_POST(self):
            enc = sys.getfilesystemencoding()

            length = self.headers.get('content-length')
            nbytes = int(length)
            rawPostData = self.rfile.read(nbytes)
            decodedPostData = rawPostData.decode(enc)
            postData = urllib.parse.parse_qs(decodedPostData)

            pan = postData["PAN"]

            resultData = []
            for p in pan:
                resultData.append(data[p])
        
            encoded = '\n'.join(resultData).encode(enc)
            self.send_response(HTTPStatus.OK)
            self.send_header("Content-type", "text/plain; charset=%s" % enc)
            self.send_header("Content-Length", str(len(encoded)))
            self.end_headers()

            self.wfile.write(encoded)

    handler = StubHttpRequestHandler
    httpd = HTTPServer(('',PORT),handler)
    httpd.serve_forever()
    ~~~

1. 検証
まずはGETしてみます。ブラウザでアクセスして、ページが正常に表示できるはずです。
次にPOSTを確認します。

    ~~~python
    import urllib.request
    import urllib.parse

    # 特定HEADER送信の例です。このサンプルに未使用
    reqAddtionalHeaders = {
        'X-Request=Process':"01"
    }

    data = urllib.parse.urlencode({"PAN":"1234123412341234"})
    data = data.encode('utf-8')

    req = urllib.request.Request("http://localhost:8000", data,reqAddtionalHeaders)

    with urllib.request.urlopen(req) as f:
        print(f.read().decode('utf-8'))
    ~~~

# 検証環境 #

    Windows10
    Python3.7

# 最後 #

同機能なものは他の言語、例えばC#でのWCFかASP.NET、あるいはJAVAでのspringbootなどはもちろん、数多くのフレームワークができます。しかし、テスト用の一時的なもので、セキュリティ問題もエラー処理問題も不要な場合、やはりPythonが一番だと思います。環境の配置はほとんど必要なく、開発も短時間でできます。