FastCGI with mod_fastcgi

mod_fcgidとmod_fastcgiは別物という事実にショックを受けつつ、気を取り直してmod_fastcgiをインストールしてみる。

環境

インストール

既にyumからapacheをinstallしているので、DSOでインストールする。
ちなみにDSOはDynamic Shared Objectの略で、実行時にLoadModuleディレクティブで読み込めるApacheモジュールのこと。そしてapxsはAPache eXtenSion toolのことである。

wget http://www.fastcgi.com/dist/mod_fastcgi-2.4.6.tar.gz
tar zxfv mod_fastcgi-2.4.6.tar.gz
cd mod_fastcgi-2.4.6


apache 1.3なら以下でOKらしいが、apache2だとうまくいかない...

# for apache 1.3...
apxs -o mod_fastcgi.so -c *.c
apxs -i -a -n fastcgi mod_fastcgi.so



INSTALL.AP2を見ると、以下のように書かれているので、これに従う。

    $ cd <mod_fastcgi_dir>
    $ cp Makefile.AP2 Makefile
    $ make
    $ make install

ただし、top_dirを自分のinstallディレクトリに合わせる必要あるので、下記の通り書き直した。

#top_dir      = /usr/local/apache2
top_dir      = /usr/lib/httpd


/etc/httpd/conf/httpd.confを確認する。無事インストールされている模様。

LoadModule fastcgi_module     /usr/lib/httpd/modules/mod_fastcgi.so

設定

まずは単純なfastcgiスクリプトとして動かそう、ということで/etc/httpd/conf.d/fastcgi.confに以下を記述してapache再起動する。なお、kotaro:kotaro(user:group)でhttpdが動作してる前提。

ScriptAlias /fcgi/ /home/kotaro/fcgi/

fcgiスクリプト

以下をScriptAlias設定した、ディレクトリ/home/kotaro/fcgiにhello.fcgiというファイル名で置く。実行権限はちゃんと付けておく。

#!/usr/bin/perl
use CGI::Fast;

while (my $q = CGI::Fast->new) {
    print("Content-Type: text/plain\n\n");
    foreach $var (sort(keys(%ENV))) {
        $val = $ENV{$var};
        $val =~ s|\n|\\n|g;
        $val =~ s|"|\\"|g;
        print "${var}=\"${val}\"\n";
    }
}

http://localhost/fcgi/hello.fcgi
で動いた!!!

ただし、これだと単なるCGIスクリプトと何も変わらない。プロセス常駐してこそFastCGIの意味があるのだが、方法はstatic, dynamic, externalの3つがある。まずはstaticからトライ。

static

/etc/httpd/conf.d/fastcgi.confを以下に設定

Alias  /fcgi/  /home/kotaro/fcgi/
FastCgiServer /home/kotaro/fcgi/hello.fcgi     -processes 5

これだと動かないので、/var/log/httpdのpermission変更する。

chmod o+x /var/log/httpd


確認すると...

[root@mdev1 ~]# ps aux | grep fcgi
kotaro    1758  0.0  0.1   9976  1896 ?        S    19:33   0:00 /usr/sbin/fcgi-
kotaro    1759  0.0  0.3   5004  3148 ?        S    19:33   0:00 /usr/bin/perl /home/kotaro/fcgi/hello.fcgi
kotaro    1769  0.0  0.2   4812  2852 ?        S    19:33   0:00 /usr/bin/perl /home/kotaro/fcgi/hello.fcgi
kotaro    1770  0.0  0.2   4812  2820 ?        S    19:33   0:00 /usr/bin/perl /home/kotaro/fcgi/hello.fcgi
kotaro    1771  0.0  0.2   4812  2812 ?        S    19:33   0:00 /usr/bin/perl /home/kotaro/fcgi/hello.fcgi
kotaro    1772  0.0  0.2   4812  2816 ?        S    19:33   0:00 /usr/bin/perl /home/kotaro/fcgi/hello.fcgi

ちゃんとfcgiのプロセスが動いてますね〜。ふぅ。

まとめ

  • 既にApacheがインストールされていても、後からmod_fastcgiを組み込むことが可能
  • ScriptAliasを設定すれば、指定したディレクトリ下のファイルをCGIとして動かせる
  • FastCGIには3通り(static,dynamic,external)の動かし方があるが、staticが一番簡単(そう)
  • fastcgiのrunディレクトリ(今回は/var/log/httpd/fastcgi)のパーミッションは注意
  • 最初のコンパイルが面倒なだけで、理解してれば作業量はそれほど多くない

ということでFastCGI怖くない!

PSGI/Plackに挑戦

CGI/FastCGIの仕組みがわかり、ようやくPlackにチャレンジ。

インストール

これでPlack関連のモジュールをまとめてinstallできる。

cpanm Task::Plack

Hello PSGI!

まずはPSGIの基本形をやろう、ということで以下のコードリファレンスをhello.psgiとして保存。

use strict;
my $app = sub {
    my $env = shift;
    return [
        200,
        [ 'Content-Type' => 'text/plain' ],
        [ "Hello PSGI!" ],
        ];
};

これをplackupすると、PSGIアプリケーションが起動する。HTTPサーバは指定してないのでHTTP::Server::PSGIが自動的に選ばれた。

kotaro@mdev1:~> plackup psgi/hello.psgi 
HTTP::Server::PSGI: Accepting connections at http://0:5000/

http://localhost:5050/にアクセスすると、「Hello PSGI!」と表示される

Hello PSGI with FastCGI

今度はFastCGIをサーバにして、PSGIアプリを動かしてみる。
FastCGIはExternalにし、Unixドメインソケットを通して、PSGIアプリとやり取りする。

FastCgiExternalServer /var/www/html/hello.fcgi -socket hello.sock
  • plackup
kotaro@mdev1:~> plackup -s FCGI --listen /var/log/httpd/fastcgi/hello.sock psgi/hello.psgi


Unixドメインソケットがlistenになってるか調べると、確かにlistenステータス。

kotaro@mdev1:~> netstat --unix -l | grep hello.sock
unix  2      [ ACC ]     STREAM     LISTENING     111707 /var/log/httpd/fastcgi/hello.sock

この状態で、http://localhost/hello.fcgi/にアクセスすれば、「Hello PSGI!」と表示された。
一度理解すれば大したことじゃないけど、辿り着くにはだいぶ時間かかりました。。

サーバプロセス数を増やす

Plack::Handler::FCGIのSYNOPSISみてたら、--nprocオプションが使えるみたい

kotaro@mdev1:~> plackup -s FCGI --listen /var/log/httpd/fastcgi/hello.sock psgi/hello.psgi --nproc 5
FastCGI: manager (pid 13481): initialized
FastCGI: server (pid 13482): initialized
FastCGI: manager (pid 13481): server (pid 13482) started
FastCGI: server (pid 13483): initialized
FastCGI: manager (pid 13481): server (pid 13483) started
FastCGI: server (pid 13484): initialized
FastCGI: manager (pid 13481): server (pid 13484) started
FastCGI: manager (pid 13481): server (pid 13485) started
FastCGI: server (pid 13485): initialized
FastCGI: server (pid 13486): initialized
FastCGI: manager (pid 13481): server (pid 13486) started

Starmanにサーバを切替える

あっけないほど簡単。PSGIという共通仕様のメリットを実感。

starman --port 8080 psgi/hello.psgi
# or this
plackup -s Starman --port 8080 psgi/hello.psgi

Apache + CGI再入門

いつもフレームワークの上でしか書いてないので、きちんと理解したい。

CGI Specification

http://httpd.apache.org/docs/2.0/en/howto/cgi.html

The CGI (Common Gateway Interface) defines a way for a web server to interact with external content-generating programs, which are often referred to as CGI programs or CGI scripts. It is the simplest, and most common, way to put dynamic content on your web site. This document will be an introduction to setting up CGI on your Apache web server, and getting started writing CGI programs.

つまり、CGIというのは外部プログラムとの協調方法を定義するもの(=仕様)


http://www.ietf.org/rfc/rfc3875.txt

The CGI defines the abstract parameters, known as meta-variables,
which describe a client's request. Together with a concrete
programmer interface this specifies a platform-independent interface
between the script and the HTTP server.

The server is responsible for managing connection, data transfer,
transport and network issues related to the client request, whereas
the CGI script handles the application issues, such as data access
and document processing.


'meta-variable'
A named parameter which carries information from the server to the
script. It is not necessarily a variable in the operating
system's environment, although that is the most common
implementation.

'script'
The software that is invoked by the server according to this
interface. It need not be a standalone program, but could be a
dynamically-loaded or shared library, or even a subroutine in the
server. It might be a set of statements interpreted at run-time,
as the term 'script' is frequently understood, but that is not a
requirement and within the context of this specification the term
has the broader definition stated.

'server'
The application program that invokes the script in order to
service requests from the client.

単純化すれば、CGIはリクエストをmeta-variablesで表現し、HTTPサーバはリクエストを処理し、CGI scriptをinvokeし、データをクライアントに戻す責務を持つ、と。


で、Apacheのドキュメントを再度みてみる。
http://httpd.apache.org/docs/2.0/ja/howto/cgi.html

サーバとクライアント間のもう一つの通信は、標準入力 (STDIN)と標準出力 (STDOUT) を通じて行なわれます。通常の文脈において、STDIN はキーボードやプログラムが動作するために与えられるファイルを意味し、 STDOUT は通常コンソールまたはスクリーンを意味します。

ウェブフォームから CGI プログラムへPOST したとき、フォームのデータは特別なフォーマットで束ねられ、 STDIN を通して、CGI プログラムに引き渡されます。 プログラムはデータがキーボード もしくはファイルから来ていたかのように処理することができます。

なるほど。完全に理解。

exec CGI

CentOS + ApacheperlpythonのCGI scriptを試してみる。
まず、/etc/httpd/conf/httpd.confにScriptAliasを設定する

ScriptAlias /cgi-bin/ /home/kotaro/cgi-bin/


次いで、/home/kotaro/cgi-binにperl.cgiとpython.cgiを作成。もちろんpermissionは適切に設定する。

#!/usr/bin/perl

print "Content-type: text/html\n\n";
for my $key (keys %ENV) {
    printf "%s => %s<br>", $key, $ENV{$key};
}
#!/usr/bin/python
import os

print "Content-type: text/html\n\n"
for k, v in os.environ.iteritems():
    print "%s: %s<br>\n" % (k, v)

これでhttp://localhost/cgi-bin/perl.cgi, http://localhost/cgi-bin/python.cgiにアクセスすれば、環境変数が表示される。

まとめ

  • CGIはHTTPリクエストに対して、CGI scriptをinvokeし、環境変数と標準入力を渡す
  • 環境変数にはREQUEST_URI, QUERY_STRINGなどが入り、標準入力にはPOSTデータが入る
  • CGI scriptはContent-TypeとHTTP Bodyを標準出力に出す

FastCGI with mod_fcgid

mod_fcgidというApacheモジュールがあるので、これでFastCGIを動かせるものかと思ってやってみる。
手順はhttp://www.movabletype.jp/documentation/developer/server/fastcgi.htmlにほぼ沿ってる。

インストール

# 必要なCPANモジュール
cpanm FCGI
cpanm CGI

# ライブラリ インストール
sudo su -
yum install httpd-devel

wget http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz
tar zxfv fcgi-2.4.0.tar.gz  -C /usr/local/src/
cd /usr/local/src/fcgi-2.4.0
./configure
make
make install

# mod_fcgid インストール
wget http://ftp.riken.jp/net/apache/httpd/mod_fcgid/mod_fcgid-2.3.5.tar.gz
tar zxvf mod_fcgid-2.3.5.tar.gz -C /usr/local/src/
cd /usr/local/src/mod_fcgid-2.3.5/
./configure.apxs
make
make install

httpd.conf

  • /etc/httpd/conf.d/fcgid.conf
ScriptAlias /fcgi/ /home/kotaro/fcgi/
<IfModule mod_fcgid.c>
    AddHandler fcgid-script .fcgi
    SocketPath /tmp/fcgid_sock/
    IPCConnectTimeout 20
    MaxProcessCount 8
    DefaultMaxClassProcessCount 2
    TerminationScore 10
    SpawnScore 80
    IdleTimeout 300
</IfModule>

fcgi script

mkdir /home/kotaro/fcgi
vi fcgi/hello.fcgi


http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.htmlから拝借したスクリプトを置く

#!/usr/bin/perl
use lib '/home/kotaro/perl5/lib';
use lib '/home/kotaro/perl5/lib/perl5/i386-linux-thread-multi';

use CGI::Fast;
while (my $q = CGI::Fast->new) {
    print("Content-Type: text/plain\n\n");
    foreach $var (sort(keys(%ENV))) {
        $val = $ENV{$var};
        $val =~ s|\n|\\n|g;
        $val =~ s|"|\\"|g;
        print "${var}=\"${val}\"\n";
    }

あとはpermissionを設定し、http://localhost/fcgi/hello.fcgiすれば動く!
が、ここまで来て、


http://www.mail-archive.com/mod-fcgid-users@lists.sourceforge.net/msg00222.html

> Is "FastCgiExternalServer" supported by mod_fcgid?

mod_fastcgi and mod_fcgid are totally different modules and don't even
share a common codebase. They have only the FastCGI protocol in common.
Thus, Apache Directives are totally different. Besides, mod_fcgid has an
adaptive-spawning design and does not support "Static" servers or
External servers.

I guess the short answer is : no, and not planned.


という文章を見つけて凹む。。。

まとめ

  • mod_fastcgiとmod_fcgidは違うものらしい...
  • External Serverとして動かしたいなら、mod_fastcgiを使う