はじめに
ふと CGI を動かしたことが無かったように思ったので、Apacheでどうやって動かすのかを調べながら、リクエストを受けてからプロセスの起動と終了するまでの動きを Docker で動かして確認してみました。
環境
以下の環境で動かしてみました。
% sw_vers ProductName: macOS ProductVersion: 13.6.1 BuildVersion: 22G313
% docker version Client: Cloud integration: v1.0.29 Version: 20.10.21 API version: 1.41 Go version: go1.18.7 Git commit: baeda1f Built: Tue Oct 25 18:01:18 2022 OS/Arch: darwin/amd64 Context: default Experimental: true Server: Docker Desktop 4.15.0 (93002) Engine: Version: 20.10.21 API version: 1.41 (minimum version 1.12) Go version: go1.18.7 Git commit: 3056208 Built: Tue Oct 25 18:00:19 2022 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.6.10 GitCommit: 770bd0108c32f3fb5c73ae1264f7e503fe7b2661 runc: Version: 1.1.4 GitCommit: v1.1.4-0-g5fd4c4d docker-init: Version: 0.19.0 GitCommit: de40ad0
Common Gateway Interface (CGI)
Common Gateway Interface (CGI)は、ざっくりいうと、静的な情報しか返せないWebサーバー(HTTPサーバー)でも、外部のプログラム(主にはスクリプト言語で書かれたプログラム)を呼び出すことで動的なコンテンツを生成して返すための仕様。
RFC 3875で定義されています。
CGIの仕様に従って作られたプログラムはCGIプログラムと呼ばれます。 RFC 3875によれば、CGIプログラムは実行可能なバイナリファイル以外に、実行権限が与えられたPerlやbashなどのスクリプトファイルのほか、CGIの仕様に従っていれば共有ライブラリや、Webサーバー内のサブルーチンもCGIプログラムに含まれるようです。 CGIプログラムはCGIの仕様に従って、CGIリクエストを受け取りCGIレスポンスを返さなければなりません。ただ、大抵はライブラリ等がこの役割を担ってくれると思うのでそんなに意識する必要はないかもしれません。
- スクリプトの呼び出し方 https://datatracker.ietf.org/doc/html/rfc3875#section-3
- CGIリクエスト https://datatracker.ietf.org/doc/html/rfc3875#section-4
- CGIレスポンス https://datatracker.ietf.org/doc/html/rfc3875#section-6
- セキュリティ https://datatracker.ietf.org/doc/html/rfc3875#section-9
CGI 処理の流れの概要は次のような形かと思います。 リクエストが発生する度にアプリケーションのプロセスが起動して結果を返したらプロセスが終了する形で、リクエストが多くなる程その処理が毎回繰り返されるので、今はほぼ使われなくなっていて、代わりにプロセスが常時起動している FastCGI とかになっていると思います。
----------- ----------- ----------- | Web | -> 要求 -> | Web | -> 起動 -> | プログラム | | ブラウザ | <- 結果 <- | サーバ- | <- 結果 <- | | ----------- ----------- -----------
動かす
Apache を使って、Perl のプログラムを CGI で動かしてみます。
Docker Compose の作成
以下のようなDockerComposeを作成します。
version: '3' services: httpd: image: httpd:2.4-alpine volumes: - type: bind source: apache2/conf/httpd.conf target: /usr/local/apache2/conf/httpd.conf - type: bind source: ./apache2/conf/extra/httpd-vhosts.conf target: /usr/local/apache2/conf/extra/httpd-vhosts.conf - type: bind source: ./apache2/vhosts/localhost target: /usr/local/apache2/vhosts/localhost ports: - "20080:80"
Apache の設定
Apacheの設定ファイルを準備します。 単なる動作確認用なのであまり深いことは考えず、ローカルでは docker-compose.yaml の定義に合わせてファイルを配置し、ボリュームマウントさせています。
httpd.conf
を以下のようなもので作成します。元の httpd.conf
から Include conf/extra/httpd-vhosts.conf
のコメントアウトを外して有効化した形です。ここではファイルは httpd-vhosts.conf
にしてますが新規に作成する何かでも何でも良いと思います。
ServerRoot "/usr/local/apache2" Listen 80 LoadModule mpm_event_module modules/mod_mpm_event.so LoadModule authn_file_module modules/mod_authn_file.so LoadModule authn_core_module modules/mod_authn_core.so LoadModule authz_host_module modules/mod_authz_host.so LoadModule authz_groupfile_module modules/mod_authz_groupfile.so LoadModule authz_user_module modules/mod_authz_user.so LoadModule authz_core_module modules/mod_authz_core.so LoadModule access_compat_module modules/mod_access_compat.so LoadModule auth_basic_module modules/mod_auth_basic.so LoadModule reqtimeout_module modules/mod_reqtimeout.so LoadModule filter_module modules/mod_filter.so LoadModule mime_module modules/mod_mime.so LoadModule log_config_module modules/mod_log_config.so LoadModule env_module modules/mod_env.so LoadModule headers_module modules/mod_headers.so LoadModule setenvif_module modules/mod_setenvif.so LoadModule version_module modules/mod_version.so LoadModule unixd_module modules/mod_unixd.so LoadModule status_module modules/mod_status.so LoadModule autoindex_module modules/mod_autoindex.so <IfModule !mpm_prefork_module> </IfModule> <IfModule mpm_prefork_module> </IfModule> LoadModule dir_module modules/mod_dir.so LoadModule alias_module modules/mod_alias.so <IfModule unixd_module> User www-data Group www-data </IfModule> ServerAdmin you@example.com <Directory /> AllowOverride none Require all denied </Directory> DocumentRoot "/usr/local/apache2/htdocs" <Directory "/usr/local/apache2/htdocs"> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> <IfModule dir_module> DirectoryIndex index.html </IfModule> <Files ".ht*"> Require all denied </Files> ErrorLog /proc/self/fd/2 LogLevel warn <IfModule log_config_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%h %l %u %t \"%r\" %>s %b" common <IfModule logio_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio </IfModule> CustomLog /proc/self/fd/1 common </IfModule> <IfModule alias_module> ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/" </IfModule> <IfModule cgid_module> </IfModule> <Directory "/usr/local/apache2/cgi-bin"> AllowOverride None Options None Require all granted </Directory> <IfModule headers_module> RequestHeader unset Proxy early </IfModule> <IfModule mime_module> TypesConfig conf/mime.types AddType application/x-compress .Z AddType application/x-gzip .gz .tgz </IfModule> Include conf/extra/httpd-vhosts.conf <IfModule proxy_html_module> Include conf/extra/proxy-html.conf </IfModule> <IfModule ssl_module> SSLRandomSeed startup builtin SSLRandomSeed connect builtin </IfModule>
ローカル確認用に localhost:80 のバーチャルホストを設定します。
conf/extra/httpd-vhosts.conf
を新規に作成して設定を行います。
Apache で CGI を動かすためには mod_cgi
が必要なため、これをロードします。
コンテナ上の /usr/local/apache2/vhosts/localhost/cgi-bin
に CGI スクリプトを配置するため、.cgi
や .pl` をCGIスクリプトとして認識するように設定します。
LoadModule cgi_module modules/mod_cgi.so <VirtualHost *:80> ServerName localhost DocumentRoot "/usr/local/apache2/vhosts/localhost/htdocs" ScriptAlias /cgi-bin/ /usr/local/apache2/vhosts/localhost/cgi-bin/ <Directory "/usr/local/apache2/vhosts/localhost/htdocs"> Options None AllowOverride None Require all granted DirectoryIndex index.html </Directory> <Directory "/usr/local/apache2/vhosts/localhost/cgi-bin"> AllowOverride None Options +ExecCGI Require all granted AddHandler cgi-script .cgi .pl </Directory> </VirtualHost>
CGI スクリプトとなる vhosts/localhost/cgi-bin/cgi-test.pl
を作成します。
#!/usr/bin/perl print "Content-type: text/html\n\n"; print "Hello, World!!\n\n"; # cgi としてプロセスが起動していることを確認するために 10 秒スリープ sleep(10)
起動
コンテナを起動します。
% docker compose up -d [+] Running 2/2 ⠿ Network apache-cgi-exec-sample_default Created 0.5s ⠿ Container apache-cgi-exec-sample-httpd-1 Started
コンテナ起動後に http://localhost:20080/
でアクセス可能になるので、http://localhost:20080/cgi-bin/cgi-test.pl
にアクセスしてみるとPerlのプログラムの実行結果が表示されています。
コンテナにログインしてプロセスを見ると、cgi-test.pl
がプロセスとして起動し、sleep 処理の10秒間は実行中の状態で、処理が終わるとプロセスが消える、というCGIの一連の動作が確認できました。
% docker exec -ti apache-cgi-exec-sample-httpd-1 /bin/sh /usr/local/apache2 # ps auxf PID USER TIME COMMAND 1 root 0:00 httpd -DFOREGROUND 7 www-data 0:00 httpd -DFOREGROUND 8 www-data 0:00 httpd -DFOREGROUND 10 www-data 0:00 httpd -DFOREGROUND 95 root 0:00 /bin/sh 102 www-data 0:00 {first.pl} /usr/bin/perl /usr/local/apache2/vhosts/localhost/cgi-bin/cgi-test.pl 103 www-data 0:00 httpd -DFOREGROUND 131 root 0:00 ps auxf
まとめ
Apache で CGI プログラムを実行して、リクエストを受けてからプロセスの起動と終了するまでの動きを見てみました。 簡単ですが、改めて動きを確認し理解が深まりました。