dshimizu/blog/alpha

とりとめのないITブログ

Apache を使って CGI を動かし、リクエストを受けてからプロセスの起動と終了するまでの動きを確認した時のメモ書き

はじめに

ふと 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プログラムは実行可能なバイナリファイル以外に、実行権限が与えられたPerlbashなどのスクリプトファイルのほか、CGIの仕様に従っていれば共有ライブラリや、Webサーバー内のサブルーチンもCGIプログラムに含まれるようです。 CGIプログラムはCGIの仕様に従って、CGIリクエストを受け取りCGIレスポンスを返さなければなりません。ただ、大抵はライブラリ等がこの役割を担ってくれると思うのでそんなに意識する必要はないかもしれません。

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 を新規に作成して設定を行います。 ApacheCGI を動かすためには mod_cgi が必要なため、これをロードします。 コンテナ上の /usr/local/apache2/vhosts/localhost/cgi-binCGI スクリプトを配置するため、.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

まとめ

ApacheCGI プログラムを実行して、リクエストを受けてからプロセスの起動と終了するまでの動きを見てみました。 簡単ですが、改めて動きを確認し理解が深まりました。

参考