(minipop のプロモーションを含みます。)

本記事では、長年に渡り WEB サーバで使われている CGI について紹介してみたい。

CGI とは

CGI は、アクセス事に応答が変わる(動的と言われる) WEB ページを実現するために考えられた仕組みである。 WEB サーバが外部プログラムを呼び出し、外部プログラムの応答をブラウザに返す方法を定めている。

CGI の歴史

CGI は古くからある仕組みだ。1990 年代の最初の頃、WEB サーバと、WEB ブラウザを目にするようになった。 この頃、既に CGI の仕組みがあった。1993年 NCSA httpd で初めて導入されたそうだ。 当時、CGI のサンプルとして提供されていたのは、うろ覚えだが、Perl のスクリプトだったように思う。C 言語のプログラムだったかもしれない。

CGI のルール

サーバの cgi-bin の下にプログラムを置き、ブラウザで「http://〜/cgi-bin/置いたプログラム名」にアクセスすると、そのプログラムが実行され、結果がブラウザに表示される。 環境変数からパラメータ等を取得し、結果を標準出力に出力するものであれば、CGI プログラムとして動作する。

実行可能なファイルで、環境変数からパラメータを取得して、結果を標準出力に出力するプログラムであれば何でも良いので、例えば、次のようなシェルスクリプトでも CGI プログラムとして動作する。

#!/bin/sh

echo "Content-Type: text/plain"
echo ""
echo "hell world!!!!"
echo "environment variables:"
env

このファイルを hell-world として保存し、レンタルサーバの cgi-bin の下に転送し、実行可に変更する。例えば minipop であれば public_html/cgi-bin の下で、他のレンタルサーバでもほぼ同じであろう。

そして、ブラウザで、http[s]://あなたのドメイン名/cgi-bin/hell-world にアクセスする。

そうすれば、この例であれば、サーバで env コマンドが実行され、その結果が表示される。

記述はどの言語で記述しても良いのだが、決まりがある。

最初の空行までは、http レスポンスのヘッダに反映される。空行の後は http レスポンスのボディに反映される。

CGI からの応答を UTF-8 として返す

だから、もし、応答を UTF-8 で返したければ 、以下のようになる。

#!/bin/sh

echo "Content-Type: text/plain; charset=UTF-8"
echo ""
echo "hell world!!!!"
echo "環境変数は以下のようになっています。"
env

どのようなプログラムがインストールされているか知りたければ、env を

find /

とすれば良いだろう。

CGI から画像を返す

画像はどうだろうか?もし、convert(imagemagick) がインストールされていれば、以下のようにすれば文字列を画像にして返すことができる。

#!/bin/sh

echo "Content-Type: image/png"
echo ""
tmp=$(mktemp)
png=${tmp}.png
convert "caption:hell wolrd!!!" $png
cat $png

任意の文字列を与えたければ、query string に変換したい文字列を、プログラムでは環境変数 QUERY_STRING を解析して変換したい文字列を取得し、convert に渡せば良いだろう。ただし、セキュリティの考慮が必要である。

CGI から JSON で返す

もしかすると、JSON も返せるのだろうか?

JSON を返せるのであれば、今時の JavaScript フロントエンドと組み合わせると、ゲームや、様々な WEB アプリを簡単に作れそうな樹がする。

どうやら可能なようだ。

hell-world-json

#!/bin/sh

echo "Content-Type: application/json; charset=UTF-8"
echo ""
echo '{"hell": "world"}'

hell.html

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="hell">
</div>
<script>
fetch('/cgi-bin/hell-world-json')
    .then(response => response.json())
    .then(json => {
	document.querySelector('#hell').append(json['hell'])
    })
</script>
</body>
</html>

hell-world-json を public_html/cgi-bin に置き、hell.html を public_html に置く。

これで、https://あなたのドメイン名/hell.html にアクセスすると、world が表示されるだろう。

ここまではシェルスクリプトで確認してみたが、別に Python や Perl のスクリプトでも良い。

GO 言語でクロスビルドした実行ファイルを CGI として実行できるのか?

スクリプトでも良いのだが、ちょっと長めのスクリプトを書いてみると、デバッグがとてもめんどうだ。

例えば minipop で CGI を作ろうとすると、サーバのエラーログがいまひとつ貧弱だし、レンタルサーバにインストールされているライブラリや、コマンド類のバージョンによっては、ローカル環境では動作するが、レンタルサーバ環境では動作しない、といったことが起こりえる。

GO 言語を使うことで、Windows 上で Linux の実行ファイルを作ることができる。 レンタルサーバが Linux であれば、Windows 上で動作を確認した後、Linux の実行ファイルを作ってアップロードすれば、動作させることができるのではないだろうか。

minipop で試してみると、可能なようだ。おそらく、他の Linux ベースのレンタルサーバであれば同様だろう。

go.mod (go mod init go-hell で作成)

module go-hell

go 1.21.1

main.go

package main

import "fmt"

func main() {
    fmt.Println("Content-Type: text/plain")
    fmt.Println("")
    Fmt.Println("hell world")
}

このまま、Windows 上で go build を実行すると、go-hell.exe というファイルができ、実行すると、

Content-Type: text/plain

hell world

のように表示される。

set GOOS=linux
set GOARCH=amd64

を実行して、go build を実行すると、Linux で実行可能な go-hell というプログラムができる。

cgi-bin に置き、実行可能属性を ON にして、ブラウザで https://あなたのドメイン名/cgi-bin/go-hell にアクセスすれば、hell world が表示される。

CGI とセキュリティ

何か書こうかと思ったが、安全なウェブサイトの作り方 に網羅されているので、一読することをおすすめする。

上記サイトには、以下のような項目が記載されている。

1.SQLインジェクション 1.OSコマンド・インジェクション 1.パス名パラメータの未チェック/ディレクトリ・トラバーサル 1.セッション管理の不備 1.クロスサイト・スクリプティング 1.CSRF(クロスサイト・リクエスト・フォージェリ) 1.HTTPヘッダ・インジェクション 1.メールヘッダ・インジェクション 1.クリックジャッキング 1.バッファオーバーフロー 1.アクセス制御や認可制御の欠落

これらを考慮して CGI を作る必要がある。

セキュリティについて考慮すべきことが多いので、本格的な WEB 開発においては、単純な CGI ではなく、セキュリティ対策が考えられた「WEB アプリケーションフレームワーク」を使う。

参考に、WEB アプケーションフレームワークには、以下のようなものがある。

  • Spring Framework / Spring Boot (Java)
  • Laravel (PHP)
  • Django (Python)
  • Express (Node.js)
  • Echo (GO)

これら、WEB アプリケーションフレームワークを使う場合、CGI としてではなく、単体のサーバとして起動して WEB サーバから呼び出す、または、WEB サーバのモジュールから呼び出す形になるだろう。

PHP と PHP-CGI

レンタルサーバによっては、PHP が CGI として動作するものがある。

PHP を書く時、Content-Type を出力しないが、CGI だったかな?と思った。

PHP をビルドすると、php コマンドと php-cgi コマンドができる。

試してみたところ、この二つは、動作が違っていた。

例えば、以下の PHP スクリプト。

hell.php

<?php
echo "hell world\n";

php hell.php の結果は次のようになる。

hell world

php-cgi hell.php の結果は次のようになる。

X-Powered-By: PHP/5.4.16
Content-type: text/html

hell world

php-cgi は、Content-type と空行を追加するようだ。

WEB サーバで、php ファイルが、 php-cgi で処理するように設定すれば、PHP も CGI として動作する。

通常は、レンタルサーバで、適切に設定されているはずだ。

WEB サーバで php-cgi を使う設定

自分でサーバを構築するのでなければ、考えることは無いかもしれない。

WEB サーバは Apache とする。

PHP を yum や apt などのパッケージマネージャからインストールすると、通常、PHP がモジュールモードで動作するようになっているはずだ。 そのため、PHP ファイルを置けば、PHP スクリプトが実行されて、結果が表示される。

しかし、わざわざ CGI で実行する方法。suEXEC を使って、WEB サーバと CGI の実行ユーザを別にし、セキュリティを高めたい、などといった場合が考えられる。

  1. まず、PHP 関連の設定を無効にする。通常は、conf.d や、conf.modules.d に php 関連の設定ファイルがあるので、*.conf でない名前に変更するか削除する。

  2. PHP を CGI として実行したい Directory セクションに以下を追加。

    Options +ExecCGI
    AddHandler cgi-handler .php
    Action cgi-handler /cgi-bin/php-cgi
    
  3. php-cgi を cgi-bin に置く。

    php-cgi が /bin にあり、/var/www/cgi-bin が CGI 用のディレクトリの場合、以下を実行する。

    ln /bin/php-cgi /var/www/cgi-bin/
    

    ln でなく ln -s でシンボリックリンクにする場合は、cgi-bin ディレクトリの設定を

    Options FollowSymLinks
    

    とする必要がある。

CGI に関するまとめ

  • 実行可能ファイルであれば何でも良い
  • 環境変数でパラメータが渡される。標準出力に出力したものが応答として返される。
  • 出力の最初の空行までは http のレスポンスヘッダに、それ以降は http のボディに返される。
  • セキュリティ対策を考えて実装すること。

参考


[PR]