Dockerで、Alpine3.5 + Apache2.4 + Python3.6の環境を作って、フォームのデータをCGIで受け取ってみた

以前、DockerでCGIを動かしてみました。
Dockerで、Alpine3.4 + Apache2.4.25 + Python3.6.0の環境を作って、CGIを動かしてみた - メモ的な思考的な

 
今回は、Dockerで、Alpine3.5 + Apache2.4 + Python3.6の環境を作って、フォームのデータをPythonCGIで受け取ってみます。

 
目次

 

環境

  • Mac OS X 10.11.6
  • Docker for Mac 17.03.1-ce-mac5
  • Alpine3.5
    • httpd:2.4.25-alpineをベースに、Python3をAlpineのパッケージapkでインストールしたもの

 

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ファイルのディレクトリとCGIPythonディレクトリの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にアクセスし、以下のようにフォームへ入力します。

f:id:thinkAmi:20170510210628p:plain:w300

 
送信ボタンを押したあとの結果は以下の通りです。

環境変数のみ値が設定されています。

--------------------
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

 
以下の方法を試してみましたが、うまくいきませんでした。

 
ちなみに、Alpine3.4では useraddusermodが無いとのことです。
Alpine Linuxでユーザやグループを追加・修正・削除する - 水底

 

ソースコード

GitHubに上げました。alpine_apache_python36_cgi_formディレクトリの中が今回のものです。
thinkAmi-sandbox/Docker_Apache-sample