以前、DockerでCGIを動かしてみました。
Dockerで、Alpine3.4 + Apache2.4.25 + Python3.6.0の環境を作って、CGIを動かしてみた - メモ的な思考的な
今回は、Dockerで、Alpine3.5 + Apache2.4 + Python3.6の環境を作って、フォームのデータをPythonのCGIで受け取ってみます。
目次
- 環境
- Dockerfile作成
- HTMLフォームの作成
- CGI用のPythonスクリプトを作成
- CGI用のPythonスクリプトのパーミッションを変更
- Dockerの起動
- フォームでGET
- フォームでPOST
- その他悩んだこと
- ソースコード
環境
Dockerfile作成
以前は「Python3.6のAlpine版のイメージ + ApacheをセットアップするDockerfile」という構成でした。
ただ、Alpine3.4縛りのせいとはいえ、ApacheをセットアップするDockerfileがほぼコピペだったので、あまり良くないと感じていました。
他の方法を探してみたところ、Alpine3.5にapkのPython3をインストールしているDockerfileがありました。
frol/docker-alpine-python3: The smallest Docker image with Python 3.5 (~61MB)
そこで、httpd:2.4.25-alpineをベースに、apkのPython3をインストールするDockerfileを作成しました。
なお、Apacheのconfファイルは前回のものを流用します。
Dockerfile
FROM httpd:2.4.25-alpine RUN apk --update --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/main/ add python3 && \ python3 -m ensurepip && \ rm -r /usr/lib/python*/ensurepip && \ pip3 install --upgrade pip setuptools && \ rm -r /root/.cache # ローカルのhttpd.confをコピー COPY httpd.conf /usr/local/apache2/conf/
HTMLフォームの作成
ひと通りのフォーム要素を持つHTMLを用意します。
また、GETだけではなくPOSTも試したかったので、formのmethodだけを変えたHTMLも用意します。
htdocs/form_get_stdin.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>例</title> </head> <body> <h1>フォームサンプル(GET)</h1> <form action="/cgi-bin/stdin_environ.py" method="GET"> <!--input text--> <label for="id_subject">件名</label> <input type="text" id="id_subject" name="subject"> <!--radio button--> <p> <label for="id_apple">リンゴ</label> <input id="id_apple" type="radio" name="fruit" value="りんご"> <label for="id_mandarin">ミカン</label> <input id="id_mandarin" type="radio" name="fruit" value="みかん"> <label for="id_grape">ブドウ</label> <input id="id_grape" type="radio" name="fruit" value="ぶどう"> </p> <p> <label for="id_big">大</label> <input id="id_big" type="radio" name="fruit_size" value="大きいもの"> <label for="id_small">小</label> <input id="id_small" type="radio" name="fruit_size" value="小さいもの"> </p> <!--select--> <p> <label for="id_quantity">個数</label> <select id="id_quqntity" name="quantity"> <option id="id_selected1_1" name="select1_1" value="1個">1</option> <option id="id_selected1_2" name="select1_2" value="2個">2</option> <option id="id_selected1_3" name="select1_3" value="3個">3</option> </select> </p> <!--select multiple--> <p> <label for="id_accessories">付属品</label> <select id="id_accessories" name="accessories" multiple> <option id="id_selected2_1" name="select2_1" value="紙袋">紙袋</option> <option id="id_selected2_2" name="select2_2" value="容器">容器</option> <option id="id_selected2_3" name="select2_3" value="紐">紐</option> </select> </p> <!--checkbox--> <p> <label for="id_takeout">持ち帰る</label> <input type="checkbox" id="id_takeout" name="takeout" value="自分で持ち帰る"> </p> <p> <label for="id_gift">贈り物</label> <input type="checkbox" id="id_gift" name="gift" value="贈り物にする"> </p> <!--hidden--> <input type="hidden" id="id_hidden_value" name="hidden_valude" value="隠しデータ"> <!--textare--> <label for="id_memo">メモ</label> <textarea id="id_memo" name="memo"></textarea> <!--submit--> <p> <input type="submit"> </p> </form> </body> </html>
POST用のHTML(htdocs/form_get_stdin.html)は省略します。
CGI用のPythonスクリプトを作成
Apacheのドキュメントによると、フォームのデータは環境変数と標準入力(STDIN
)に設定されます。
裏で何が起こっているのか? | Apache Tutorial: CGI による動的コンテンツ - Apache HTTP サーバ バージョン 2.4
そこで今回は、環境変数と標準入力の値をブラウザへと返すようにしてみます。
なお、Pythonでは、
- 環境変数の値:
os.environ
辞書 - 標準入力の値:
sys.stdin.read()
でそれぞれ取得できます。
cgi/stdin_environ.py
#!/usr/bin/python3 # shebangに指定するPython3を以下で確認 # bash-4.3# which python3 # /usr/bin/python3 import os import sys # HTTPレスポンスヘッダ print('Content-Type: text/plain;charset=utf-8\n') print("\n") # HTTPレスポンスボディ # 標準入力 print('-'*20) print('stdin:\n{}'.format(sys.stdin.read())) # 環境変数 print('-'*20) print('os.environ:\n') for k, v in os.environ.items(): print('{}: {}'.format(k, v))
CGI用のPythonスクリプトのパーミッションを変更
今回、HTMLやPythonスクリプトは Dockerの-v
オプションを使用して、ホストとコンテナで共有します。
ただ、Dockerfileでは共有した時のパーミッションをうまく設定できませんでした(後述)。
そこで今回は、ホストのPythonスクリプトのパーミッションを変更します。これにより、DockerコンテナとPythonスクリプトを共有しても、同じパーミッションになります。
# 変更前のMac上のパーミッション $ ls -al -rw-r--r-- 1 you staff 513 5 10 05:46 stdin_environ.py # 実行可能へと変更 $ chmod 755 stdin_environ.py # 変更後のMac上のパーミッション $ ls -al -rwxr-xr-x 1 you staff 513 5 10 05:46 stdin_environ.py
Dockerの起動
ローカルとDockerでファイルを共有するため、-v
オプションを使用します。
今回はhtmlファイルのディレクトリとCGI用Pythonディレクトリの2つを共有するため、-v
オプションを2つ使ってそれぞれ指定します。
Mounting multiple volumes on a docker container? - Stack Overflow
ホスト | コンテナ |
---|---|
htdocs/ | /usr/local/apache2/htdocs |
cgi/ | /usr/local/apache2/cgi-bin/ |
実際に入力する内容は以下の通りです。
# ビルド $ docker build -t alpine:python3_httpd24_cgi_form . # 起動 $ docker run -p 8081:80 --name cgi_form -v `pwd`/htdocs/:/usr/local/apache2/htdocs -v `pwd`/cgi/:/usr/local/apache2/cgi-bin/ alpine:python3_httpd24_cgi_form
コンテナのパーミッションも確認します。
# コンテナに入る $ docker exec -it `docker ps | grep cgi_form | awk '{print $1}'` /bin/bash # カレントディレクトリの確認 bash-4.3# pwd /usr/local/apache2 # カレントディレクトリのパーミッション bash-4.3# ls -al total 40 drwxr-xr-x 1 www-data www-data 4096 Mar 3 21:57 . drwxr-xr-x 1 root root 4096 Mar 3 21:57 .. drwxr-xr-x 2 root root 4096 Mar 3 21:57 bin drwxr-xr-x 2 root root 4096 Mar 3 21:57 build drwxr-xr-x 4 root root 136 May 9 20:47 cgi-bin drwxr-xr-x 1 root root 4096 May 9 20:53 conf drwxr-xr-x 3 root root 4096 Mar 3 21:57 error drwxr-xr-x 5 root root 170 May 9 20:46 htdocs drwxr-xr-x 3 root root 4096 Mar 3 21:57 icons drwxr-xr-x 2 root root 4096 Mar 3 21:57 include drwxr-xr-x 1 root root 4096 May 9 20:54 logs drwxr-xr-x 2 root root 4096 Mar 3 21:57 modules # cgi-binディレクトリの中にある「stdin_environ.py」のパーミッションを確認 bash-4.3# cd cgi-bin/ bash-4.3# ls -al -rwxr-xr-x 1 root root 513 May 9 20:46 stdin_environ.py
ホストと同じパーミッションが設定されていました。
フォームでGET
http://localhost:8081/form_get_stdin.html
にアクセスし、以下のようにフォームへ入力します。
送信ボタンを押したあとの結果は以下の通りです。
環境変数のみ値が設定されています。
-------------------- stdin: -------------------- os.environ: HTTP_HOST: localhost:8081 HTTP_CONNECTION: keep-alive HTTP_UPGRADE_INSECURE_REQUESTS: 1 HTTP_USER_AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 HTTP_ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 HTTP_REFERER: http://localhost:8081/form_get_stdin.html HTTP_ACCEPT_ENCODING: gzip, deflate, sdch, br HTTP_ACCEPT_LANGUAGE: ja,en-US;q=0.8,en;q=0.6 PATH: /usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin SERVER_SIGNATURE: SERVER_SOFTWARE: Apache/2.4.25 (Unix) SERVER_NAME: localhost SERVER_ADDR: 172.17.0.2 SERVER_PORT: 8081 REMOTE_ADDR: 172.17.0.1 DOCUMENT_ROOT: /usr/local/apache2/htdocs REQUEST_SCHEME: http CONTEXT_PREFIX: /cgi-bin/ CONTEXT_DOCUMENT_ROOT: /usr/local/apache2/cgi-bin/ SERVER_ADMIN: you@example.com SCRIPT_FILENAME: /usr/local/apache2/cgi-bin/stdin_environ.py REMOTE_PORT: 59532 GATEWAY_INTERFACE: CGI/1.1 SERVER_PROTOCOL: HTTP/1.1 REQUEST_METHOD: GET QUERY_STRING: subject=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB&fruit=%E3%82%8A%E3%82%93%E3%81%94&fruit_size=%E5%B0%8F%E3%81%95%E3%81%84%E3%82%82%E3%81%AE&quantity=2%E5%80%8B&accessories=%E5%AE%B9%E5%99%A8&takeout=%E8%87%AA%E5%88%86%E3%81%A7%E6%8C%81%E3%81%A1%E5%B8%B0%E3%82%8B&gift=%E8%B4%88%E3%82%8A%E7%89%A9%E3%81%AB%E3%81%99%E3%82%8B&hidden_valude=%E9%9A%A0%E3%81%97%E3%83%87%E3%83%BC%E3%82%BF&memo=%E4%B8%80%E8%A1%8C%E7%9B%AE%0D%0A%E4%BA%8C%E8%A1%8C%E7%9B%AE REQUEST_URI: /cgi-bin/stdin_environ.py?subject=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB&fruit=%E3%82%8A%E3%82%93%E3%81%94&fruit_size=%E5%B0%8F%E3%81%95%E3%81%84%E3%82%82%E3%81%AE&quantity=2%E5%80%8B&accessories=%E5%AE%B9%E5%99%A8&takeout=%E8%87%AA%E5%88%86%E3%81%A7%E6%8C%81%E3%81%A1%E5%B8%B0%E3%82%8B&gift=%E8%B4%88%E3%82%8A%E7%89%A9%E3%81%AB%E3%81%99%E3%82%8B&hidden_valude=%E9%9A%A0%E3%81%97%E3%83%87%E3%83%BC%E3%82%BF&memo=%E4%B8%80%E8%A1%8C%E7%9B%AE%0D%0A%E4%BA%8C%E8%A1%8C%E7%9B%AE SCRIPT_NAME: /cgi-bin/stdin_environ.py
フォームでPOST
http://localhost:8081/form_post_stdin.html
にアクセスし、同じようにフォームに入力し、送信ボタンを押します。
POSTでは環境変数と標準入力に値が設定されています。
-------------------- stdin: subject=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB&fruit=%E3%82%8A%E3%82%93%E3%81%94&fruit_size=%E5%B0%8F%E3%81%95%E3%81%84%E3%82%82%E3%81%AE&quantity=2%E5%80%8B&accessories=%E5%AE%B9%E5%99%A8&takeout=%E8%87%AA%E5%88%86%E3%81%A7%E6%8C%81%E3%81%A1%E5%B8%B0%E3%82%8B&gift=%E8%B4%88%E3%82%8A%E7%89%A9%E3%81%AB%E3%81%99%E3%82%8B&hidden_valude=%E9%9A%A0%E3%81%97%E3%83%87%E3%83%BC%E3%82%BF&memo=%E4%B8%80%E8%A1%8C%E7%9B%AE%0D%0A%E4%BA%8C%E8%A1%8C%E7%9B%AE -------------------- os.environ: HTTP_HOST: localhost:8081 HTTP_CONNECTION: keep-alive CONTENT_LENGTH: 444 HTTP_CACHE_CONTROL: max-age=0 HTTP_ORIGIN: http://localhost:8081 HTTP_UPGRADE_INSECURE_REQUESTS: 1 HTTP_USER_AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 CONTENT_TYPE: application/x-www-form-urlencoded HTTP_ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 HTTP_REFERER: http://localhost:8081/form_post_stdin.html HTTP_ACCEPT_ENCODING: gzip, deflate, br HTTP_ACCEPT_LANGUAGE: ja,en-US;q=0.8,en;q=0.6 PATH: /usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin SERVER_SIGNATURE: SERVER_SOFTWARE: Apache/2.4.25 (Unix) SERVER_NAME: localhost SERVER_ADDR: 172.17.0.2 SERVER_PORT: 8081 REMOTE_ADDR: 172.17.0.1 DOCUMENT_ROOT: /usr/local/apache2/htdocs REQUEST_SCHEME: http CONTEXT_PREFIX: /cgi-bin/ CONTEXT_DOCUMENT_ROOT: /usr/local/apache2/cgi-bin/ SERVER_ADMIN: you@example.com SCRIPT_FILENAME: /usr/local/apache2/cgi-bin/stdin_environ.py REMOTE_PORT: 59536 GATEWAY_INTERFACE: CGI/1.1 SERVER_PROTOCOL: HTTP/1.1 REQUEST_METHOD: POST QUERY_STRING: REQUEST_URI: /cgi-bin/stdin_environ.py SCRIPT_NAME: /cgi-bin/stdin_environ.py
その他悩んだこと
Dockerfileの中で、共有ディレクトリのパーミッションを設定する方法
前述のとおり、今回はホストとコンテナの共有ディレクトリのパーミッションは、ホストのパーミッションを変更することで対応しました。
なお、ホストのパーミッションを変更しなかった場合、Dockerのログに以下が出力されるとともに、ブラウザに「Internal Server Error」が表示されました。
[pid 97:tid 140512665647944] (13)Permission denied: AH01241: exec of '/usr/local/apache2/cgi-bin/stdin_environ.py' failed [pid 12:tid 140512664455856] [client 172.17.0.1:59462] End of script output before headers: stdin_environ.py, referer: http://localhost:8081/form_get_stdin.html
以下の方法を試してみましたが、うまくいきませんでした。
- What is the (best) way to manage permissions for docker shared volumes - Stack Overflow
- Make uid & gid configurable for shared volumes · Issue #7198 · moby/moby
ちなみに、Alpine3.4では useradd
やusermod
が無いとのことです。
Alpine Linuxでユーザやグループを追加・修正・削除する - 水底
ソースコード
GitHubに上げました。alpine_apache_python36_cgi_form
ディレクトリの中が今回のものです。
thinkAmi-sandbox/Docker_Apache-sample