FastCGI
O FastCGI
FastCGI je vylepšená verze CGI. CGI při každém požadavku načte program znovu z disku počítače a spustí. A to trvá dost dlouho. FastCGI přichází s jiným přístupem. Program se spustí jednou a pak ve smyčce obsluhuje jedno připojení za druhým.
Všechno co potřebujete k tomu, aby váš program mohl běžet ve smyčce a přijímat od serveru jedno spojení
za druhým už pro vás bylo naprogramováno. Stačí, když si nainstalujete vývojový balíček FastCGI.
V OpenSuSE se jmenuje FastCGI-devel
,
v Debianu libfcgi-dev
. V jiné distribuci
se zase může jmenovat jinak …
Když už máte FastCGI knihovnu nainstalovanou, můžete se hned pustit do prvního příkladu.
Důležitá věc při psaní FastCGI programu je použití hlavičkového souboru <fcgi_stdio.h>. Tento soubor redefinuje standardní knihovnu <stdio.h>. V souboru fcgi_stdio.h najdete podobné věci tomuto:
#define FILE FCGI_FILE
#undef stdin
#define stdin FCGI_stdin
Redefinuje se v něm nejen FILE, stdin, stdout a stderr, ale i funkce printf()
,
fprintf()
, fread()
, fwrite()
atd.
Až se budete učit C++ tak se dozvíže, že C++ na vstup a výstup používá objekty std::cin a std::cout atp. Tyto C++ vymoženosti ale knihovna fcgi_stdio.h neredefinuje, takže je v FastCGI programu nemůžete používat.
FastCGI program tedy používá jiné vstupní a výstupní proudy, ale díky <fcgi_stdio.h> to vypadá, jako by pracoval se standardním vstupem a výstupem.
Knihovna fcgi_stdio.h musí být includovaná jako poslední ze všech includovaných knihoven. Jakákoliv jiná knihovna totiž může v sobě includovat stdio.h a tím pádem všechny redefinice z fcgi_stdio.h zase redefinovat. Taková chyba se pak projeví zobrazením chyby v prohlížeči a hláškou Premature end of script headers v logu /var/log/apache/error.log.
V příkladech v této kapitole budu používat soubory common.h, common.c, html.h a html.c z kapitoly o CGI. Jediný rozdíl je v tom, že v souborech html.h a common.h includuju místo knihovny <stdio.h> knihovnu <fcgi_stdio.h>.
Příklad FastCGI programu
Takto vypadá ukázka FastCGI programu:
- /*------------------------------------------------*/
- /* 46fcgi/fcgi1.c */
- #include "common.h"
- #include "html.h"
- #include <fcgi_stdio.h>
- {
- /* init */
- /* work */
- i++;
- htmlStart("FastCGI 1");
- printEnvironmentVals();
- printPostData();
- htmlForm("post");
- printAllEnvironmentVals(env);
- htmlEnd();
- }
- }
- /*------------------------------------------------*/
Typický FastCGI program začíná nějakou inicializací. Pak se spustí cyklus, který obsluhuje
jednotlivé požadavky. O provádění cyklu se stará funkce FCGI_Accept()
.
Tato funkce vrací hodnotu >= 0, dokud chce server využívat služby programu. Pokud server
nedostane na FCGI program dlouho žádný požadavek, rozhodne se jej ukončit. To udělá právě
tím, že funkce FCGI_Accept()
vrátí zápornou hodnotu.
Program inicializuje proměnnou i na 0 a pak ji při každém požadavku zvýší o 1. Když tedy v prohlížeči požádáte o spuštění tohoto FCGI programu, pokaždé by vám měl vrátit číslo o jedno větší.
Ovšem pozor, jak už jsem psal, pokud FCGI program dlouho nikdo nespustí, server jej ukončí a při dalším požadavku jej spustí na novo. Řada čísel tak zase začne od nuly. Server se taky může rozhodnout spustit FCGI program v několika vláknech, takže mohou běžet různé instance FCGI programu současně. Na to všechno je potřeba myslet při vývoji FCGI progarmu (o CGI programech nemluvě). Dejte si taky pozor na práci se sdílenými prostředky, jako jsou třeba soubory. Pokud server spustí více instancí FCGI programu a každá z nich se pokusí zapsat něco do stejného souboru, nejspíš si změny navzájem přepíšou. Tomu můžete zabránit použitím zamykání souborů. Nebo použijte databázi, která je na víceuživatelský přístup připravená.
Při překladu musíte překladači říct, v jakém adresáři najde soubor fcgi_stdio.h a připojit
knihovnu fcgi
. Mě se nainstaloval fcgi_stdio.h do adresáře /usr/include/fastcgi/,
takže překlad může vypadat nějak takto:
Přeložený program můžete zkopírovat do ~/public_html/
a podívat se na výsledek
na http://127.0.0.1/~username/fcgi1.cgi
FastCGI 1
Toto je 1-tý požadavek.
QUERY_STRING:
REQUEST_METHOD: GET
HTTP_HOST=localhostHTTP_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
HTTP_ACCEPT_LANGUAGE=cs,en-us;q=0.7,en;q=0.3
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_DNT=1
HTTP_COOKIE=fontSize=large
HTTP_CONNECTION=keep-alive
HTTP_CACHE_CONTROL=max-age=0
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SERVER_SIGNATURE=Apache/2.4.6 (Linux/SUSE) Server at localhost Port 80
SERVER_SOFTWARE=Apache/2.4.6 (Linux/SUSE)
SERVER_NAME=localhost
SERVER_ADDR=::1
SERVER_PORT=80
REMOTE_HOST=localhost
REMOTE_ADDR=::1
DOCUMENT_ROOT=/srv/www/htdocs
REQUEST_SCHEME=http
CONTEXT_PREFIX=/~username
CONTEXT_DOCUMENT_ROOT=/home/username/public_html
SERVER_ADMIN=[no address given]
SCRIPT_FILENAME=/home/username/public_html/fcgi1.cgi
REMOTE_PORT=33514
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.1
REQUEST_METHOD=GET
QUERY_STRING=
REQUEST_URI=/~username/fcgi1.cgi
SCRIPT_NAME=/~username/fcgi1.cgi
Teď asi mačkáte jako blázni tlačítko refresh ve svém webovém prohlížeči
a přemýšlíte, proč se vám pořád zobrazuzje Toto je 1-tý požadavek..
To proto, že se program nespouští jako FCGI, ale jen jako CGI. Funkce
FCGI_Accept()
se v takovém případě zachová vychytrale. Při prvním volání
vrací hodnotu >=0 a při druhém zápornou hodnotu. Takže smyčka proběhne
právě jednou.
Aby se program spustil jako FastCGI, musíte nejdříve nakonfiguraovat Apache.
Konfigurace Apache
Konfigurace bude probíhat podobně jako při konfiguraci Apache pro CGI.
Nejdříve si musíte nainstalovat modul fcgid
.
V OpenSuSE je to balíček apache2-mod_fcgi
,
v Debianu libapache2-mod-fcgid
.
Druhým krokem je přidání řádku AddHandler fcgid-script .fcgi
do souboru mod_userdir.conf, jako v případě konfigurace CGI, nebo do souboru mod_fcgid.conf
(pak tato volba bude fungovat ve všech adresářích, nejen v public_html adresářích uživatelů).
A stejně jako se musel zapnout modul CGI, musí se zapnout i modul FCGI a restartovat Apache. Tj, například v OpenSuSE takto:
$ sudo systemctl restart apache2
/etc/init.d/apache2 restart
.
Teď už stačí přejmenovat program fcgi1.cgi na fcgi1.fcgi a měli byste vidět, jak se hodnota v Toto je 1-tý požadavek. při každém znovunačtení stránky zvyšuje.
Tentokrát si ale můžete všimnou jiné podivnosti. Podívejte se na výpis všech proměnných prostředí.
Kam zmizely? Vypisují se proměnné prostředí předané jako třetí argument funkce main.
Ten ale ve FastCGI
žádné proměnné nedostane (maximálně proměnnou PATH). Správně byste měli načítat
proměnné prostředí z extern **environ;
. Předejte tuto proměnnou funkci
printAllEnvironmentVals()
jako argument a všechny proměnné prostředí se zase vrátí :-).
qDecoder
V kaptiole o CGI jste viděli, jak jsou předávány uživatelem odeslaná data pomocí proměnných prostředí nebo standardního vstupu. A taky že se různými způsoby kódují. Knihovna qDecoder je tu od toho, aby vám s dekódováním těchto dat pomohla.
Knihovna qDecoder pracuje s CGI i FastCGI. Kromě parsování GET a POST dat (dat předaných v query stringu a ve standardním vstupu) umí pracovat i s cookies a vytvářet session. Cookies a session jsou pojmy vztahující se k programování webů, takže je tu nebudu dále rozebírat.
Instalace
qDecoder nejspíš jako balíček ve své linuxové distribuci nenajdete. Stáhněte si zdrojové kódy z githubu.
Rozbalte jej do nějakého adrsáře. Já jsem jej rozbalil do qdecoder12_0_5
(tento adresář
je i součástí zdrojových kódů ke stažení). Přejděte do něj a nejdříve
spusťte konfiguraci. Protože budu ukazovat qDecoder s FastCGI, musíte konfiguračnímu skriptu
předat argument s cestou ke knihovně fcgi_stdio.h:
--enable-fastcgi=/usr/include/fastcgi/
.
Dále spusťte příkazy make
a jako root make install
.
...
qdecoder12_0_5$ make
...
qdecoder12_0_5$ sudo make install
...
Pozor! V Debianu jsem musel před make install
upravit v souboru
mkdir
a dalším.
Jsou tam totiž cesty zapsané jako /usr/bin/mkdir
, ale v Debianu
je mkdir (a další programy) v adresáři /bin/
.
Příkaz make install
přesune knihovnu libqdecoder.so a liqdecoder.a
do adresáře /usr/local/lib64/ a hlavičkový soubor qdecoder.h do adresáře
/usr/local/include/. Aby šlo program s touto knihovnou přeložit, musí se nejdříve
spustit ldconfig
, viz sdílené
knihovny.
V 32 bitové verzi Linuxu to asi nebude adresář /usr/local/lib64/, ale nějaký jiný.
V Debianu byste měli nejdříve přidat adresár /usr/local/lib64
do souboru
/etc/ld.so.conf.d/qdecoder.conf
a pak spustit ldconfig
.
$ ldconfig
Načítání proměnných z POST a GET
Teď už by mělo být všechno připravno k tomu, abyste si mohli qDecoder vyzkoušet.
První je v příkladu volána funkce qcgireq_parse()
.
Funkce alokuje paměť pro strukturu qentry_t
a vrátí na ni ukazatel.
Prvním argumentem může být odkaz na tuto strukturu, pokud voláte qcgireq_parse()
podruhé a chcete tuto paměť „recyklovat“. Při prvním volání je tedy, logicky, vždy NULL.
Druhý argument určuje, odkud se budou parzovat proměnné. 0 znamená, že se budou parsovat nejdříve z COOKIES. Pokud se tam proměnná nenajde, bude se parsovat z POST a když ani tam se nenajde, bude se parsovat z GET.
Q_CGI_ALL nebo 0 | Proměnná se hledá v pořadí COOKIE, POST, GET |
---|---|
Q_CGI_COOKIE | Proměnná se hledá jen v COOKIES |
Q_CGI_POST | Proměnná se hledá jen v POST |
Q_CGI_GET | Proměnná se hledá jen v GET |
Struktura qentry_t
obsahuje spousty ukazatelů na funkce, které můžete používat pro dotazovanání se na dekódované
proměnné. Nejčastěji asi budete používat getstr()
, viz příklad:
- /*------------------------------------------------*/
- /* 46fcgi/qdecoder1.c */
- #include "common.h"
- #include "html.h"
- #include <qdecoder.h>
- #include <fcgi_stdio.h>
- {
- i++;
- htmlStart("qDecoder 1");
- printEnvironmentVals();
- /* qDecoder */
- qentry_t *req = qcgireq_parse(NULL, 0);
- name ? name : "(null)", surname ? surname : "(null)");
- /* konec qDecoder */
- printPostData();
- htmlForm("post");
- printAllEnvironmentVals(environ);
- htmlEnd();
- }
- }
- /*------------------------------------------------*/
Při překladu přidejte fcgi a qdecoder knihovny a cestu k hlavičkovým souborům:
Funkce req->getstr()
má jako první argument strukturu qentry_t
,
jako druhý argument název proměnné, jejíž hodnotu chcete získat (z COOKIE, POST nebo GET –
to záleží na druhém parametru qcgireq_parse()
).
Pokud je třetí argument true
, bude pro výsledek alokována nová paměť, kterou pak musíte
sami uvolnit pomocí free()
. Pokud je třetí parametr false
, stačí na konci
zavolat req->free(req)
pro uvolnění paměti struktury qentry_t
(to by se mělo volat v každém případě).
Ve vícevláknovém prostředí by měl být třetí argument vždy true
.
Zkuste si spustit tento příklad z url: http://localhost/~username/qdecoder1.fcgi?jmeno=Julius
.
Uvidíte, že qDecoder získá jméno z query stringu (neboli GET požadavku). Když pak odešlete
formulář (POSTem), který má input se jménem jmeno
, vezme qDecoder hodnotu proměnné
jmeno
z POSTu.
Načítání souboru z POST
Druhý příklad zobrazí obsah odeslaného (textového) souboru.
Tentokrát je nastaveno, že se data budou číst jenom z POSTu.
Formulář odesílá soubor v inputu se jménem soubor
.
Všiměte si, jaké používa qDecoder názvy proměnných pro získání
informací o souboru (např. soubor.length pro získání
délky souboru).
- /*------------------------------------------------*/
- /* 46fcgi/qdecoder2.c */
- #include "common.h"
- #include "html.h"
- #include <qdecoder.h>
- #include <fcgi_stdio.h>
- {
- i++;
- htmlStart("qDecoder 2");
- /* qDecoder */
- qentry_t *req = qcgireq_parse(NULL, Q_CGI_POST);
- filename, length, contenttype);
- }
- /* konec qDecoder */
- htmlFormWithFile();
- htmlEnd();
- }
- }
- /*------------------------------------------------*/
Jediná nová funkce, kterou jsem v tomto příkladu použil, je req->getint()
pro získání proměnné soubor.length jako čísla.
Příklad vypíše nahraný soubor na obrazovku. To bude fungovat jen v případě textových
souborů, binární soubory samozřejmě nemůžete tisknout pomocí printf()
.
Funkce printf()
zastaví tisk u prvního nulového bajtu, kterých je
v binárních souborech obvykle hodně.
Po odeslání souboru hello-world.txt, jehož obsahem je jediná věta Hello World! může výsledek vypadat takto:
qDecoder 2
Toto je 5-tý požadavek.
Byl nahrán soubor hello-world.txt délky 13 bajtů typu text/plain.
Obsah souboru:
Hello World!
Tímto způsobem nahrává qDecoder celý soubor do paměti (do proměnné soubor),
což v případě velkých souborů není moc praktické. To můžete změnit voláním
funkce qcgireq_setoption()
, kterou přepnete do něčeho, čemu
autor qDecoderu říká file mode
. Soubor pak bude uložen přímo
na disk.
Soubor máte na disku, víte že cesta k němu je savepath, takže si s ním už můžete dělat co chcete.
CURL
V kaptiole o CGI jste mohli vidět, jak je query string kódován pomocí % sekvencí.
Například Žluťoučký kůň se zakóduje jako %C5%BDlu%C5%A5ou%C4%8Dk%C3%BD+k%C5%AF%C5%88
.
Knihovana qDecoder vám tuto sekvenci znaků dekóduje zase jako Žluťoučký kůň.
Co ale když chcete použít query string v odkazu v HTML dokumentu, který generujete?
Jak převedete Žluťoučký kůň na zakódovaný query string? K tomu vám
už qDecoder nepomůže. Ale pomůže vám knihovna CURL.
Knihovna CURL je velmi známá, oblíbená a šikovná knihovna, která se používá především pro posílání dat přes HTTP protokol. Teď mě tak napadá, že kdybych vám o ní řekl už na začátku, mohli jste si ušetřit práci s psaním vlastního HTTP klienta :-P.
CURL toho umí ale mnohem, mnohem víc. Dokáže komunikovat nejen pomocí HTTP, ale i HTTPS (šifrovaná verze), FTP, POP3 a SMTP (čtení a posílání emailů), umí se přihlašovat pomocí jména a hesla k různým službám atd.
CURL není jen knihovna (libcurl), ale i program curl
, který můžete spouštět
z příkazové řádky.
Já vám tu nebudu popisovat ani program curl
, ani knihovnu libcurl
nějak do hloubky, jen vám ukáži jak použít funkci curl_easy_escape()
k zakódování hodnoty v query stringu. Snad to bude pro vás takový odrazový můstek pro
další zkoumání CURL.
Instalace libcurl
Tuto knihovnu budete mít v balíčcích své oblíbené linuxové distribuce.
V OpenSuSE si nainstalujte libcurl-devel
.
V Debianu máte na výběr ze tří možností: libcurl4-openssl-dev
,
libcurl4-nss-dev
a libcurl4-gnutls-dev
.
Tyto balíčky se liší v použité implementaci TLS (šifrování).
Pro použití funkce curl_easy_escape()
je úplně nepodstatné, který balíček s vyberete.
Já dávám přednost libcurl4-openssl-dev
.
Použití libcurl
První, co musíte udělat, je inicializovat CURL. K tomu se používají funkce
curl_global_init()
a curl_easy_init()
.
Parametr flags může být:
flags | Význam |
---|---|
CURL_GLOBAL_ALL | Inicializuje všechno co jde, kromě CURL_GLOBAL_ACK_EINTR |
CURL_GLOBAL_SSL | Inicializuje jen SSL |
CURL_GLOBAL_WIN32 | Inicializuje Win32 sokety (jen we Windows) |
CURL_GLOBAL_NOTHING | Neinicalizuje nic extra. |
CURL_GLOBAL_DEFAULT | Doporučené „rozumné“ nastavení. Inicializuje SSL i Win32. V tuto chvíli je to ekvivalentní flagu CURL_GLOBAL_ALL |
CURL_GLOBAL_ACK_EINTR | Bez tohoto natavení CURL ignoruje signál EINTR a čeká, dokud nevyprchá timeout (při čekání na nějaká data z internetu atp.) |
Protože chci v příkladu použít jen curl_easy_escape()
, vystačím si s flagem CURL_GLOBAL_NOTHING
.
Tato funkce vrátí odkaz na strukturu CURL
, kterou většina curl funkcí vyžaduje jako argument.
Struktura musí být po použití uvolněna funkcí curl_easy_cleanup()
, aby nedocházelo k
únikům paměti.
No a pro escapování hodnoty do query stringu se použije curl_easy_escape()
:
Parametr length určuje délku parametru url (což je řetězec, který
se bude escapovat). Pokud necháte length 0, funkce curl_easy_escape()
použije pro zjištění délky url funkci strlen()
.
Vrácené pole znaků se musí uvolnit funkcí curl_free()
:
Příklad:
- /*------------------------------------------------*/
- /* 46fcgi/curl1.c */
- #include "common.h"
- #include "html.h"
- #include <qdecoder.h>
- #include <curl/curl.h>
- #include <fcgi_stdio.h>
- {
- i++;
- htmlStart("CURL 1");
- printEnvironmentVals();
- /* qecoder */
- qentry_t *req = qcgireq_parse(NULL, Q_CGI_POST);
- name ? name : "(null)", surname ? surname : "(null)");
- /* curl */
- curl_global_init(CURL_GLOBAL_NOTHING);
- CURL *curl = curl_easy_init();
- escName ? escName : "",
- escSurname ? escSurname : "");
- curl_free(escName);
- curl_free(escSurname);
- curl_easy_cleanup(curl);
- }
- /* konec curl */
- printPostData();
- htmlForm("post");
- htmlEnd();
- }
- }
- /*------------------------------------------------*/
Při překladu programu můžete využít skript curl-config
pro získání
parametrů s knihovnou a adresářem k hlavičkovým souborům CURL:
Výstup, po odeslání formuláře se jménem Žluťoučký kůň bude vypadat takto:
CURL 1
Toto je 2-tý požadavek.
QUERY_STRING:
REQUEST_METHOD: POST
qdecoder: name = 'Žluťoučký kůň', surname = ''
curl = 'jmeno=%C5%BDlu%C5%A5ou%C4%8Dk%C3%BD%20k%C5%AF%C5%88&prijmeni='
CURL pochopitelně můžete používat v jakémkoliv programu, nejen CGI. Pokud se chcete o knihovně CURL dozvědět více, začněte tutoriálem.
PHP
Pokud vás zajímá PHP, můžete se podívat na to, jak nastavit PHP jako FastCGI. Pokud vás nezajímá, tak na to nekoukejte :-).