11. Appendix:開発環境・デプロイTips集
このAppendixでは、本書のサンプルCGIプログラムを動かすための環境構築と、各章で詰まりやすい実行時の注意点をまとめます。
本文ではCGIプログラムそのものの理解を優先します。Apache、MariaDB、unixODBC、権限設定、文字コード、デプロイ時の調整は、この章を必要に応じて参照してください。
A.0 前提環境
本書では、次の構成を前提にします。
- Ubuntu Linux
- Apache
- GCC / make
- MariaDB
- unixODBC
- MariaDB用ODBCドライバ
Ubuntu 24.04 LTSのようなLTS版を使うと、ローカル環境からVPS環境へ移しやすく、学習用としても扱いやすいです。
必要なパッケージをまとめて入れる場合は、次のようにします。
sudo apt update
sudo apt install apache2 gcc g++ make mariadb-server unixodbc unixodbc-dev libmariadb-dev
Apacheの再起動や設定変更には管理者権限が必要です。学習用のローカル環境で試す場合も、実行するコマンドの意味を確認しながら進めてください。
A.1 ApacheでCGIを有効にする
ApacheでCGIを実行するには、mod_cgi または mod_cgid が有効になっている必要があります。Debian / Ubuntu系では、次のコマンドで有効化できます。
sudo a2enmod cgi
sudo systemctl restart apache2
標準的なCGIディレクトリは /usr/lib/cgi-bin/ です。環境によってはサイト設定ファイルに次のような設定が必要です。
<Directory "/usr/lib/cgi-bin">
Options +ExecCGI
AddHandler cgi-script .cgi
Require all granted
</Directory>
設定を変更したらApacheの構文を確認し、問題がなければ再起動します。
sudo apache2ctl configtest
sudo systemctl restart apache2
A.2 CGIファイルの配置と権限
CGIプログラムは、Webサーバが実行できる場所に置き、実行権限を付ける必要があります。
gcc hello.c -o hello.cgi
chmod 755 hello.cgi
sudo cp hello.cgi /usr/lib/cgi-bin/
配置後は、ファイルの権限を確認します。
ls -l /usr/lib/cgi-bin/hello.cgi
少なくともWebサーバの実行ユーザーが読み取りと実行をできる必要があります。権限が不足していると、ブラウザでは 500 Internal Server Error になることがあります。
Apacheのエラーログは、CGI実行時の問題を切り分けるうえで重要です。
sudo tail -f /var/log/apache2/error.log
A.3 UserDir構成を使う場合
ユーザーごとの ~/public_html 以下でCGIを動かしたい場合は、mod_userdir を有効にします。
sudo a2enmod userdir
sudo systemctl restart apache2
CGIを許可するには、設定ファイルに ExecCGI と AddHandler を追加します。
<Directory /home/*/public_html/cgi-bin>
Options +ExecCGI
AddHandler cgi-script .cgi
Require all granted
</Directory>
アクセス例は次のようになります。
http://localhost/~username/cgi-bin/hello.cgi
UserDir構成はユーザーごとに実験環境を分けやすい一方、ホームディレクトリや public_html の権限設定で詰まりやすくなります。最初の学習では、標準の /usr/lib/cgi-bin/ を使う方が切り分けやすいです。
A.4 GET / POSTのデバッグ
GETパラメータはURLに含まれ、CGIプログラムからは主に QUERY_STRING 環境変数で参照します。
curl -i "http://localhost/cgi-bin/form.cgi?name=Alice&age=20"
POSTデータは標準入力から読み取ります。CONTENT_LENGTH の値をもとに読み取るため、長さの扱いを間違えると、入力が途中で切れたり、読み取り待ちになったりします。
curl -i -X POST -d "name=Alice&age=20" http://localhost/cgi-bin/form.cgi
送信データの中身を確認したい場合は、ファイルに保存して hexdump で見ると、改行コードや余計な空白を確認できます。
printf 'name=Alice&age=20' > postdata.txt
hexdump -C postdata.txt
A.5 文字コードと改行コード
HTMLテンプレートやCソースはUTF-8で保存することを前提にします。文字コードが混在していると、日本語表示やURLエンコード処理の確認が難しくなります。
file template.html
Windows環境で編集したファイルをLinuxで実行する場合は、改行コードにも注意します。
dos2unix hello.cgi
dos2unix template.html
C言語のCGIでは、HTTPヘッダの文字コードも明示しておくと確認しやすくなります。
printf("Content-Type: text/html; charset=UTF-8\r\n\r\n");
JSONを返す場合は次のようにします。
printf("Content-Type: application/json; charset=UTF-8\r\n\r\n");
A.6 メモリ管理の確認
フォーム値の解析やテンプレート処理では、malloc()、strdup()、free() を使う場面があります。CGIはリクエストごとにプロセスが終了することが多いとはいえ、学習段階から解放漏れや範囲外アクセスを確認する習慣をつけておくとよいです。
valgrind ./form_handler.cgi
標準入力を使うCGIを単体実行する場合は、環境変数を与えてテストします。
CONTENT_LENGTH=17 REQUEST_METHOD=POST ./form_handler.cgi < postdata.txt
CGIとして動かす前にコマンドラインで実行できるようにしておくと、問題の切り分けが楽になります。
A.7 MariaDBの初期構築
MariaDBをインストールしたら、初期設定を行います。
sudo mysql_secure_installation
学習用のデータベースとユーザーを作成する例です。
CREATE DATABASE sample;
CREATE USER 'user'@'localhost' IDENTIFIED BY 'pass';
GRANT ALL PRIVILEGES ON sample.* TO 'user'@'localhost';
FLUSH PRIVILEGES;
第9章の簡易CMSでは、次のようなテーブルを例として使います。
CREATE TABLE diary (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
title VARCHAR(255),
body TEXT,
is_public TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
初期データの投入例です。
INSERT INTO diary (user_id, title, body, is_public)
VALUES
(1, '初めての日記', 'これはテスト投稿です。', 1),
(1, '下書きメモ', 'これは非公開の投稿です。', 0);
A.8 unixODBCの設定
ODBC接続では、ドライバ定義とDSN定義を使います。
設定ファイルの場所は次のコマンドで確認できます。
odbcinst -j
ドライバ定義の例です。
[MariaDB]
Description = ODBC for MariaDB
Driver = /usr/lib/x86_64-linux-gnu/odbc/libmaodbc.so
DSN定義の例です。
[mydb]
Driver = MariaDB
Server = localhost
Database = sample
User = user
Password = pass
接続確認は isql で行います。
isql -v mydb user pass
本文中で接続文字列を直接使う場合は、次のような形になります。
SQLCHAR conn_str[] = "DRIVER={MariaDB};SERVER=localhost;DATABASE=sample;UID=user;PWD=pass;";
DSNあり構成とDSNなし構成のどちらを使うかは、学習目的に応じて選びます。設定ファイルの仕組みを理解したい場合はDSNあり、サンプルコード単体で追いやすくしたい場合はDSNなしが扱いやすいです。
A.9 セッションディレクトリの運用
第8章のようにファイルベースでセッションを保存する場合は、保存先ディレクトリの権限に注意します。
sudo mkdir -p /var/lib/c-cgi-book/sessions
sudo chown www-data:www-data /var/lib/c-cgi-book/sessions
sudo chmod 700 /var/lib/c-cgi-book/sessions
学習用に /tmp 以下を使うこともできますが、環境によっては自動削除されます。ログイン状態の検証を安定させたい場合は、専用ディレクトリを用意する方が安全です。
セッションIDをCookieで渡す場合は、少なくとも HttpOnly と SameSite を付けます。
printf("Set-Cookie: SID=%s; HttpOnly; SameSite=Lax\r\n", sid);
HTTPSで運用する場合は Secure も付けます。
printf("Set-Cookie: SID=%s; HttpOnly; Secure; SameSite=Lax\r\n", sid);
セッションIDは推測しにくい値にする必要があります。学習用でも、固定文字列や連番は避けてください。
A.10 JSON APIの確認
第10章でJSONレスポンスを返すCGIを作る場合は、Content-Type を明示します。
printf("Content-Type: application/json; charset=UTF-8\r\n\r\n");
curl でレスポンスヘッダを含めて確認します。
curl -i http://localhost/cgi-bin/api.cgi
JSONの整形には jq が便利です。
curl -s http://localhost/cgi-bin/api.cgi | jq
.cgi 以外の拡張子でAPIを分けたい場合は、Apache側でハンドラを追加します。
AddHandler cgi-script .cgi .api
ただし、拡張子を増やすよりも、まずは .cgi のままContent-Typeを変えてJSONを返す方が、CGIの仕組みを理解しやすいです。
A.11 デプロイ時の注意
学習用に動いたCGIを外部公開する場合は、少なくとも次の点を確認してください。
- 入力値を検証しているか
- HTML出力時にエスケープしているか
- SQLにユーザー入力を直接連結していないか
- セッションIDが推測困難な値になっているか
- Cookie属性が適切か
- エラーメッセージに内部情報を出していないか
- CGIファイルやテンプレートの権限が過剰でないか
- DBユーザーに必要以上の権限を与えていないか
- HTTPSを使っているか
CGIは仕組みが単純なぶん、入力と出力の責任がプログラム側に強く残ります。公開環境に置く場合は、教材コードをそのまま使うのではなく、エラー処理、ログ、権限、セキュリティを見直してください。
A.12 トラブルシュート早見表
| 症状 | 確認すること |
|---|---|
500 Internal Server Error |
実行権限、Apacheログ、CGIの異常終了、ヘッダ出力 |
| Cコードは動くがブラウザで動かない | ApacheのCGI設定、配置先、ファイル権限 |
| POST値が読めない | CONTENT_LENGTH、標準入力の読み取り、送信Content-Type |
| 日本語が文字化けする | HTMLのcharset、ファイルの文字コード、DBの文字コード |
| DBに接続できない | MariaDBユーザー、DSN設定、ODBCドライバ、接続文字列 |
| Cookieが保存されない | Set-Cookie の出力位置、属性、ブラウザの開発者ツール |
| JSONが壊れる | Content-Type、文字列エスケープ、余計なHTML出力 |
このAppendixは、各章のサンプルを動かすための補助資料です。本文で詰まったときは、該当する章の内容とあわせて参照してください。