Hello FastCGI!

FastCGI jest bardzo często kojarzone wyłącznie z Perl'em lub PHP, w przypadku serwera Lighttpd, użycie FastCGI jest jedynym sposobem aby zmusić go do pracy z PHP, ale musimy wiedzieć, że jest niezależne od języka programowania i serwera WWW, jest to raczej coś w rodzaju sposobu komunikacji pomiędzy serwerem a aplikacją (pomostem). Zasadniczą różnicą pomiędzy CGI a FastCGI jest wydajność, która została znacznie podniesiona poprzez wyeliminowanie konieczności uruchamiania skryptu/aplikacji przy każdym odwołaniu. W przypadku CGI serwer WWW otrzymując żądanie od przeglądarki uruchamiał "na boku" aplikację i zwracał wynik, natomiast dzięki FastCGI istnieje możliwość uruchomienia konkretnej liczby instancji aplikacji, które w trybie ciągłym oczekują na wywołanie bez konieczności każdorazowego uruchamiania procesu.

Przewagą FastCGI nad CGI może być również to, że uruchomiona aplikacja jest w stanie przechowywać różne wartości w pamięci do których możemy się odwołać w dowolnym momencie, w przypadku CGI nie było to możliwe, ponieważ po obsłużeniu każdego żądania proces umierał i zawartość buforów była tracona.

Procesy FastCGI (aplikacje) są całkowicie niezależne od serwera, po prostu działają obok, a to zdecydowanie utrudnia wywołanie awarii w której padnie serwer WWW.

Jeśli chodzi o skalowanie, to sprawa wygląda tak, że serwer WWW otrzymuje request od użytkownika i przekazuje go w pełni do aplikacji fcgi, w serwerze WWW możemy określić w jaki sposób ma nastąpić połączenie (np. socket, lub tcp), dzięki temu możemy mieć odseparowane aplikacje od warstwy HTTP.

Spróbujmy napisać i uruchomić prostą aplikację FastCGI w C (Lighttpd + Ubuntu):

Instalacja niezbędnej biblioteki libfcgi do obsługi FastCGI w ANSI C
# apt-get install libfcgi-dev

Konfiguracja serwera Lighttpd:

# cat /etc/lighttpd/conf-enabled/10-fastcgi.conf
server.modules   += ( "mod_fastcgi" )

fastcgi.server    = ( ".fcgi" =>
((
"bin-path" => "/tmp/app-cgi",
"socket" => "/tmp/cgi.socket",
"max-procs" => 4,
"check-local" => "disable",
"bin-copy-environment" => (
"PATH", "SHELL", "USER"
),
))
)

  • server.modules - taki zapis pozwala na dodanie do listy modułów, modułu mod_fastcgi
  • fastcgi.server - ".fcgi" - określa dla których rozszerzeń żądanie ma być przekazane do aplikacji fcgi
  • bin-path - ścieżka do naszej aplikacji
  • socket - ściezka do socketu po których będzie odbywała się komunikacja (lighttpd -> fcgi), istnieje również możliwość komunikacji po tcp, wtedy nasza aplikacja fcgi może być uruchomiona na zdalnym serwerze (klastrze)
  • max-procs - maksymalna liczba procesow naszej aplikacji fcgi
  • idle-timeout - czas bezczynności
  • check-local - jeśli jest enable, to serwer lighttpd będzie najpierw szukał pliku ihha.fcgi w document.root serwera, jeśli nie znajdzie to zwróci 404, natomiast jeśli jest ustawiona na disable to przekaże od razu request do aplikacji fcgi
  • bin-copy-environment - pozwala na kopiowanie wartości zmiennych środowiskowych (PATH, SHELL, USER)

Kod naszej aplikacji:

  • #include "fcgi_stdio.h" - plik nagłówkowy dla fcgi
  • int count - deklaracja zmiennej count - licznika obsłużonych żądań (dla każdego procesu app-cgi jest unikalny)
  • while (FCGI_Accept() >= 0) - pętla główna naszej aplikacji (specyficzne dla fastcgi, ponieważ aplikacja nie kończy działania)
  • printf("Content-type: text/html") - wysyłamy nagłówek HTTP mówiący o tym, że kontent będzie danymi w postaci text/html (lighttpd przekazuje całe żądanie do aplikacji fastcgi, tak więc aplikacja musi odpowiedzieć wraz z nagłówkami, ten jest niezbędny aby wyświetlić tekst w przeglądarce)
  • ++count, getpid() - inkrementacja licznika count oraz pobranie pidu procesu (argumenty dla printf(%d %d))

Kompilacja odbywa się w następujący sposób:

# gcc /tmp/cgi-app.c -lfcgi -o /tmp/app-cgi

  • -lfcgi - mówi kompilatorowi by zlinkował nasz program app-cgi z biblioteką fcgi

# ldd /tmp/app-cgi
linux-gate.so.1 => (0x0070c000)
libfcgi.so.0 => /usr/lib/libfcgi.so.0 (0x00ef8000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00ce9000)
libnsl.so.1 => /lib/tls/i686/cmov/libnsl.so.1 (0x005fc000)
libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0x00234000)
/lib/ld-linux.so.2 (0x00665000)

Mając skompilowaną aplikację i skonfigurowany serwer WWW możemy spróbować uruchomić całość:

# /etc/init.d/lighttpd restart

Dodatkowo możemy sprawdzić czy nasza aplikacja FCGI została poprawnie zespawnowana:

# pstree | grep lighttpd
|-lighttpd---4*[app-cgi]

Widzimy 1 proces lighttpd i dodatkowe 4 procesy app-cgi ;-) czyli wszystko gra. Pobieramy stronę:

$ lwp-request http://192.168.1.194/app.fcgi
<h1>FastCGI App!</h1>Request: 1 <br />PID: 11282

Wszystko działa poprawnie, serwer nam zwrócił numer requestu: 1 dla konkretnego procesu app-cgi (PID). W przypadku dużego ruchu, Lighttpd rozrzuci ruch pomiędzy 4 procesy app-cgi, ale należy pamiętać, że zmienne aplikacji są tylko w obrębie jednego procesu - count dla każdego procesu app-cgi będzie inny.

Czy jest sens używania FastCGI? Oczywiście, że tak. W wielu zastosowaniach taka aplikacja będzie dużo bardziej wydajna niż skrypt napisany w PHP, czasami nie ma potrzeby zaprzęgania całego interpretera PHP czy Perl, do wykonania jakiejś prostej czynności, wszyscy przecież wiemy, że Linuks lubi C. ;-)

A Wy korzystacie z FastCGI? albo widzicie jakieś ciekawe zastosowanie?