前回、フォームのデータをCGIのPythonスクリプトでsys.stdin.read()
とos.environ
を使って受け取りました。
Dockerで、Alpine3.5 + Apache2.4 + Python3.6の環境を作って、フォームのデータをCGIで受け取ってみた - メモ的な思考的な
今回は標準モジュールcgi
にあるFieldStorage
使ってデータを受け取ってみます。
目次
環境
なお、Dockerfileは前回のものを流用します。
HTMLファイルは前回のものを流用し、action
だけを対象のPythonスクリプトに差し替えます。
Dockerまわりのコマンドは以下の通りです。
# Docker上のApacheでCGIとして動かすために、Dockerと共有するローカルファイルのパーミッションを変更 $ chmod 755 cgi_module.py $ chmod 755 cgi_file_upload.py $ chmod 755 cgi_file_upload_with.py # Dockerイメージのビルド $ docker image build -t alpine:python3_httpd24_cgi_module . # Dockerコンテナの起動 $ docker container run -p 8081:80 --name cgi_module -v `pwd`/htdocs/:/usr/local/apache2/htdocs -v `pwd`/cgi/:/usr/local/apache2/cgi-bin/ alpine:python3_httpd24_cgi_module
cgi.FieldStorageを使ったフォームデータの受け取り
データの読み込み
cgi.FieldStorage
のインスタンスを生成することで、標準入力などからデータを読み込めます。
cgi_module.py
#!/usr/bin/python3 import cgi # HTTPレスポンスヘッダ print('Content-Type: text/plain;charset=utf-8\n') print('') form = cgi.FieldStorage() print(form) # => FieldStorage(None, None, [MiniFieldStorage('quantity', '1個'), # MiniFieldStorage('hidden_valude', '隠しデータ')])
また、フォームに入力していない要素も取得したい場合は、
form = cgi.FieldStorage(keep_blank_values=True) print(form) # => FieldStorage(None, None, [MiniFieldStorage('subject', ''), # MiniFieldStorage('quantity', '1個'), # MiniFieldStorage('hidden_valude', '隠しデータ'), # MiniFieldStorage('memo', '')])
と、FieldStorageのインスタンス生成時の引数としてkeep_blank_values=True
を指定します。
ただ、上記を見る限り、未選択のcheckboxやradio button、select multipleは取得できないようです。
なお、FieldStorageのインスタンス生成については、公式ドキュメントにある
標準入力または環境変数からフォームの内容を読み出します (どちらから読み出すかは、複数の環境変数の値が CGI 標準に従ってどのように設定されているかで決まります)。インスタンスが標準入力を使うかもしれないので、インスタンス生成を行うのは一度だけにしなければなりません。
21.2. cgi — CGI (ゲートウェイインタフェース規格) のサポート — Python 3.6.1 ドキュメント
という点に注意します。
また、FieldStorageインスタンス生成後にsys.stdin.read()
を使うと、
form = cgi.FieldStorage() print('stdin:\n{}'.format(sys.stdin.read())) # => stdin:
と、何も取得できません。
一方(今回のケースだけかもしれませんが)、os.environ
については
form = cgi.FieldStorage() print('os.environ:\n') for k, v in os.environ.items(): print('{}: {}'.format(k, v)) # => # HTTP_HOST: localhost:8081 # HTTP_CONNECTION: keep-alive # ...
と、環境変数の値が取得できました。
FieldStorageの属性やメソッド
属性やメソッドを使ってみると、
form = cgi.FieldStorage() print(form.type) # => application/x-www-form-urlencoded print(form.headers) # => {'content-type': 'application/x-www-form-urlencoded', 'content-length': '94'} print(form.keys()) # => ['quantity', 'memo', 'hidden_valude', 'subject']
となりました。
フォームのフィールド値の取得
例えば、「自分で持ち帰る」チェックボックスにチェックを入れた場合、
form = cgi.FieldStorage() print(form['takeout']) # => MiniFieldStorage('takeout', '自分で持ち帰る') print(form['takeout'].value) # => 自分で持ち帰る
と、辞書の添字アクセスと同じようにして、フィールドの値を取得できます。
なお、添字アクセスで存在しないキーを指定した場合、
form = cgi.FieldStorage() try: print(form['foo']) except: print('not found: foo') # => not found: foo
と、例外が出ます。
そのため、getvalue()
メソッドを使うことで、
form = cgi.FieldStorage() print(form.getvalue('foo')) # => None print(form.getvalue('foo', 'default_value')) # => default_value
と、例外を回避したり、デフォルト値を返すことができます。
同じnameを持つ複数フィールド値の取得
チェックボックスなど、複数のフィールドで同じnameを持つ場合は
form = cgi.FieldStorage() print(form.getfirst('purpose')) # => 贈り物にする print(form.getlist('purpose')) # => ['贈り物にする', '自家用にする']
のように、getfirst()
で最初のものだけ取得したり、getlist()
でリストとして取得します。
なお、nameが一つだけの場合にgetlist()
メソッドを使うと、
form = cgi.FieldStorage(keep_blank_values=True) print(form.getlist('takeout')) # => ['自分で持ち帰る']
と、要素数1のリストとなります。
cgi.FieldStorageを使ったアップロードファイルの受け取り
ファイルアップロードができるフォームを
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>例</title> </head> <body> <h1>ファイルアップロードサンプル(一気に読み込み)</h1> <form action="/cgi-bin/cgi_file_upload.py" method="POST" enctype="multipart/form-data"> <!--input text--> <label for="id_upload_file">対象ファイル</label> <input type="file" id="id_upload_file" name="upload_file"> <!--submit--> <p> <input type="submit"> </p> </form> </body> </html>
と用意した場合も、cgi.FieldStorageでアップロードしたファイルのデータを扱えます。
例えば、
upload_file.txt
あ a
というファイルを扱う場合、
cgi_file_upload.py
#!/usr/bin/python3 import cgi # HTTPレスポンスヘッダ print('Content-Type: text/plain;charset=utf-8\n') print('') form = cgi.FieldStorage() print(form) # => FieldStorage(None, None, [FieldStorage('upload_file', 'upload_file.txt', b'\xe3\x81\x82\na')])
とすることで、FieldStorageのインスタンスとして取得できます。
FieldStorageインスタンスからファイルの中身を一気に読み込む場合、
form = cgi.FieldStorage() content = form.getvalue('upload_file') print(content.__class__) # => <class 'bytes'> print(content.decode('utf-8')) # => # あ # a
と、getvalue()
メソッドでバイト文字列を取得し、それをdecode()にて文字列にします。
また、一気に読み込むのが難しい場合は、一定のバイト数で区切って読み込むこともできます。
cgi_file_upload_with.py
form = cgi.FieldStorage() upload_file = form['upload_file'] print(upload_file) # => FieldStorage('upload_file', 'upload_file.txt', b'\xe3\x81\x82\na') print(dir(upload_file)) # => ['FieldStorageClass', '_FieldStorage__file', '_FieldStorage__write', '__bool__', # '__class__', '__contains__', '__del__', '__delattr__', '__dict__', '__dir__', # '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattr__', # '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', # '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', # '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', # '__subclasshook__', '__weakref__', '_binary_file', 'bufsize', 'bytes_read', # 'disposition', 'disposition_options', 'done', 'encoding', 'errors', 'file', # 'filename', 'fp', 'getfirst', 'getlist', 'getvalue', 'headers', 'innerboundary', # 'keep_blank_values', 'keys', 'length', 'limit', 'list', 'make_file', 'name', # 'outerboundary', 'qs_on_post', 'read_binary', 'read_lines', 'read_lines_to_eof', # 'read_lines_to_outerboundary', 'read_multi', 'read_single', 'read_urlencoded', # 'skip_lines', 'strict_parsing', 'type', 'type_options'] print(upload_file.name) # => upload_file print(upload_file.filename) # => upload_file.txt print(upload_file.file) #=> <_io.BytesIO object at 0x7fe34bba03b8> # with文にも対応しているとのことなので、試してみる # http://docs.python.jp/3/library/cgi.html with upload_file as f: content = b'' while True: # 1バイトずつ読み込む場合 byte_strings = f.file.read(1) print(byte_strings) # => b'\xe3', b'\x81', b'\x82', b'\n', b'a', b'' の順に読み込まれる if not byte_strings: break content += byte_strings print(content.decode('utf-8')) # あ # a
ソースコード
GitHubに上げました。alpine_apache_python36_cgi_module
ディレクトリの中が今回のものです。
thinkAmi-sandbox/Docker_Apache-sample