Předběžná verze dokumentace ke knihovně filtrů
Předpokládám, že současný model bude změněn
z |->
na ->|
(viz TODO),
proto považujte tento dokument za zcela nezávazný.
Následující popis je sice zaměřen na
filtrování výstupu z modulů Apache, ale popsaný
mechanizmus by měl platit pro manipulaci s filtry obecně.
Současná koncepce filtrů umožňuje:
- aby si filtry posílaly různé
signály,
- aby nemusely veškerá data
neustále kopírovat z buferu do buferu, tj. aby několik filtrů
mohlo sdílet tentýž bufer,
- aby se neaktivní filtry mohly samy odstranit z
řetězce filtrů, tj. není nutné dávat pozor, aby byly v
řetězci jen ty filtry, které budou skutečně při
filtrování aktivní.
Výhody filtrů oproti
stávajícímu řešení:
- Filtry umožní věci, které bez nich
nejsou možné nebo by se dělaly velice obtížně (mod_czech,
"on fly" compression, ...).
- Elegantně nahradí některé části
Apache, které jsou teď ošklivě složité a
"sdrátované" (chunked encoding, EBCDIC).
- Filtry mají modulární strukturu,
tudíž se snáze píší, mění a
opravují.
Jak to funguje:
Filtry jsou seřazeny do řetězce filtrů. Řetězec filtrů
bere při zavolání ze svého vstupu data, prožene je
jednotlivými filtry a uloží je na svůj výstup. Činnost
filtrů se řídí signály, které jim řetězec
posílá, pořadí volání filtrů
závisí na hodnotách, které filtry vracejí. Na
konci vrátí řetězec filtrů hodnotu, z které je poznat
důvod, proč řetězec svoji činnost přerušil nebo ukončil.
- Řetězec filtrů může vrátit: F_IBE, F_OBF, F_EOF
nebo F_ERR.
- Každý jednotlivý filtr může
vrátit: F_IBE, F_OBF, F_INIT, 0, některý signál (vč.
F_EOF a F_ERR), nebo některý signál
rozšířený o F_IBE.
- Každý filtr dostává postupně tyto
signály:
- Nejprve obdrží signál F_INIT.
- Potom je filtr opakovaně volán bez
signálu, tj. provádí svoji normální
funkci, ale volání jsou občas proložena různými
signály, např.
- F_RECONF následovaným
- F_INIT | F_RECONF.
- Na konec dostane filtr signál F_EOF.
- Pokud v jiném filtru vznikne chyba, může
filtr dostat signál F_ERR.
Významy signálů jsou
následující:
- F_INIT dostane filtr jen jednou, a to jako
úplně první signál.
Filtr by se
měl při tomto signálu inicializovat, k čemuž může použít i
informace uložené v f->fch->*.
Při
inicializaci může filtr např. sám sebe trvale odstranit (funkcí
remove_filter() - jako např. filtr f_out_charset, pokud se dokument typu
f->fch->r->content_type nebude kódovat); nebo svůj bufer může
spojit s buferem následujícího filtru (funkcí
attach_filter_buffer() - pokud filtr s většinou dat v buferu
nepotřebuje manipulovat, např. f_out_chunk).
Pozor: Při F_INIT už f->next nemusí
mít svůj bufer prázdný!
- Je-li filtr zavolán bez signálu, měl by
provádět svoji normální funkci, tj. konvertovat data,
která bere ze svého vlastního buferu a
ukládá v buferu fitru f->next.
Pozor: Filtr by měl vyprázdnit svůj bufer
dříve, než vrátí F_IBE! To, že se filtr dostal ke slovu,
znamená, že předchozí filtr už neměl kam dávat data - a
dokud filtr svůj bufer neuvolní, pro předchozí filtr se nic
nezmění. Nerespektování tohoto doporučení může
vyvolat zacyklení řetězce filtrů!
- F_RECONF znamená, že se změnily některé
parametry, především f->r, a že by se filtr měl připravit na
reinicializaci.
Filtr může signál
odmítnout a vrátit F_IBE, ale pokud jej akceptuje, musí
svůj bufer i všechny své vnitřní bufery
vyprázdnit, a místo F_IBE vrátit F_IBE
rozšířené o tentýž signál (totiž F_IBE |
F_RECONF) jako potvrzení, že je připraven.
V
případě akceptování signálu a
vrácení F_IBE | F_RECONF dostane filtr při
příštím zavolání signál F_INIT |
F_RECONF.
Poznámky:
- Kromě F_RECONF mohou existovat i jiné
signály, které jsou však určeny
konkrétním filtrům (např. F_CHARSET). Filtr by se k těmto
signálům měl chovat vesměs stejně jako k F_RECONF.
- Filtr by měl signál F_RECONF akceptovat,
pokud k odmítnutí nemá nějaký
zvláštní důvod (např. pokud by vypráznění
vnitřních buferů znamenalo ukončení činnosti filtru, jako
(asi) u f_compress). Umožní tím reinicializaci i filtrům za
ním následujícím, ke kterým by se při
odmítnutí signál F_RECONF nedostal.
- Vrácením samotného F_RECONF
filtr tento signál vyvolá; potvrzení signálu
vyžaduje vrátit F_IBE | F_RECONF!
- F_INIT | F_RECONF znamená totéž co
samotný F_INIT, jen s informací navíc, že není
nutné provádět kompletní inicializaci. Filtr může v tuto
chvíli např. znovu obnovit připojení svého buferu
funkcí attach_filter_buffer(), nastavit si různé parametry
apod.
Pozor: Z
f->fch->r by si měl filtr údaje zkopírovat, protože po
návratu z subrequestu může být tento pointer neplatný a
filtr by tak mohl způsobit segmentation violation. Totéž se
týká i ostatních údajů - mohou se změnit
ještě před F_RECONF, kdy filtr stále potřebuje znát
staré hodnoty.
- F_EOF je protipólem F_INIT. Filtr musí
svůj bufer a všechny své vnitřní bufery
vyprázdnit, a ve chvíli, kdy vrátí F_EOF, jeho
činnost končí (už nebude volán).
- Na F_ERR by měl filtr reagovat stejně jako na F_EOF,
jen místo F_EOF musí vrátit F_ERR.
Podrobněji o signálech:
- Signály se filtrům předávají v
proměnné f->flags. Jejich účelem je umožnit filtrům
překonfigurovat se při změně parametrů. Protože mohou mít filtry v
okamžiku překonfigurování neprázdné bufery,
musí stará data přefiltrovat dříve, než se nastaví
na nové hodnoty.
Překonfigurování
je prováděno dvoufázově: napřed filtry jeden po druhém
dostanou příslušný signál; ten se jim po
chvíli vrátí ve formě pokynu k reinicializaci (F_INIT).
(Zdá se mi, že toto řešení má oproti
jednofázové reinicializaci několik výhod,
především že je snazší napsat korektně se
chovající filtr.)
Signál F_INIT se
šíří od předních filtrů zpět, ostatní
signály se šíří opačným směrem, tj.
dopředu. Následující povídání se na
F_INIT nevztahuje .
- Jakmile filtr obdrží signál,
musí, jestliže jej akceptuje, provést signálem
požadovanou akci. Tato akce přinejmenším zahrnuje
vyprázdnění všech buferů a přípravu na
inicializaci. Úspěšné vykonání
signálem požadované akce filtr oznámí
vrácením signálu téhož jména
"obohaceným" o F_IBE (tj. že má prázdný
bufer).
Některé signály odmítnout
nelze: F_INIT, F_EOF a F_ERR.
- V průběhu vykonávání akce může
dojít k naplnění buferu, do nehož filtr data
ukládá, může dojít k chybě atd. Filtr proto smí
vrátit F_OBF, nebo libovolný signál kromě
samotného F_IBE. Místo F_IBE by měl filtr vrátit F_IBE |
"příslušný signál". (Jestliže chce filtr
signál sám vyvolat, vrátí tento signál bez F_IBE.)
- Po té, co filtr potvrdí svou
připravenost na inicializaci vrácením F_IBE |
"příslušný signál", bude signál
předán následujícímu filtru. Až signál
dojde k poslednímu filtru, nebo bude-li některým filtrem
odmítnut, začne se vracet ve formě F_INIT | "přísl.
signál", tj. rozšířený o informaci,
čím bylo init vyvoláno.
Pomocná makra:
- K práci se signály jsou autorům k
dispozici makra F_DONE_SIGNAL(), F_IBE_SIGNAL() a F_OBF_SIGNAL(). Makra
rozpoznají, zda mají vrátit F_IBE, F_OBF nebo
některý ze signálů.
Obyčejnému
filtru stačí, aby při návratu vracel F_DONE_SIGNAL(f),
další dvě makra jsou určena pro použití ve
výstupních filtrech (f_*_output); F_IBE_SIGNAL() se
používá i ve vstupních filtrech (zdrojích dat,
f_*_input) namísto F_EOF_SIGNAL(f) (umožňuje to spojovat data z
více vstupních filtrů).
- K odmítnutí signálů může filtr
použít makro REJECT_SPECIAL_F_SIGNALS().
- Filtry, které ke
kopírování dat nepoužívají funkci
copy_filter_buffer(), mohou při inicializaci použít makro
CANNOT_BE_LAST_FILTER().
- Filtry, které neumožňují
nastavení vstupního buferu funkcí set_filter_buffer(),
mohou při inicializaci použít makro CANNOT_BE FIRST_FILTER().
Nepoužívejte pro f_*_input filtry.
- Makro F_DONE_SIGNAL() mj. zajistí, že před
finálním F_EOF či F_ERR bude filtr zavolán ještě
jednou, s prázdným vstupním buferem. Makro filter_eof(f)
bude v tuto chvíli vracet true. To dává filtru
příležitost svou činnost korektně ukončit (např.
f_file_output).
Poznámky:
- Signály nemusí vyvolávat jen
systém, ale i samy filtry tím, že vrátí
příslušný signál. Pokud nebude signál
obsahovat F_IBE, uchrání tím samy sebe před
inicializací.
- Filtr si nesmí
uchovávat informace o buferu následujícího filtru,
protože ten se může kdykoliv bez upozornění přemístit (viz např.
f_pcopy_output). Každý filtr si totiž se svým buferem může dělat
(témeř) co chce. Ze stejného důvodu se filtr nemůže
spoléhat, že bufer, který si při inicializaci připojil
funkcí attach_filter_buffer(), má stále připojený.
Buď by měl kontrolovat f->attached, nebo to může nechat na funkci
copy_filter_buffer(), která to udělá za něj.
- Všechny signály, kromě F_INIT (F_IBE a
F_OBF za signály nepovažuji), jsou pokryty maskou F_SIGNAL.
- *src, se, *dst a de jsou pouhé zkratky za
(postupně) f->buf_bottom, f->buf_top, f->next->buf_top a
f->next->buf_end.
- Na mnoha místech jsem si ulehčil práci a netestoval jsem situace, které
normální člověk nevyvolá. (Např. nikdo nebude volat remove_filter() na
f_*_input filtr.) Kdybych měl ošetřovat úplně všechny možnosti, asi bych se
z toho zbláznil, a navíc by to asi moc dobře nefungovalo.
Hodně jsem promýšlel, co vlastně může reálně nastat, takže ve srovnání
s prvními verzemi knihovny je její současná podoba relativně jednoduchá.
O to více je však vše provázané se vším.
Poznámky k filtrování výstupu
z Apache:
- F_INIT se vyvolá až po odeslání
HTTP hlaviček, takže např. f->r je filtru k dispozici už správně
nastavený.
- Předpokládám, že
chunkování se provádí pouze v tomto řetězci
filtrů, tj. při odesílání výsledků requestu
klientovi, takže jsem z buff.c všechny příslušné
rutiny vyházel a chunkuje se jen filtrem f_out_chunk.