BETA
My accountGet started
Oct 152016
Peter Širka

Časť 2: Ako funguje Total.js na pozadí

V dnešnom blogu pre predstavu opíšem ako funguje klasický routing a uploadovanie súborov.

Časť 2: Ako funguje Total.js na pozadí

Ak Vás zaujala časť 1, tak určite Vás zaujme aj táto časť 2. V tejto časti sa pozrieme na spracovanie dynamického obsahu a na uploadovanie súborov.

Routing

Trasovanie (routing) považujem asi za najdôležitejšiu časť frameworku, pretože registrovaním (vytvorením) trasy vzniká webová aplikácia. Total.js pozná 3x rôzne typy trás:

  • dynamický obsah: F.route(), príklad: /products/
  • súbory: F.file(), príklad: /img/logo.png
  • websocket: F.websocket() príklad: /chat/

Framework vyhodnocuje trasovanie pre každý typ requestu trošku inak. Pri prijatí requestu vyhodnotí framework, či sa jedná o súbor (kontroluje či má .extension - koncovku) a ak platí, že sa jedná o súbor, tak request ďalej spracúva tzv. FileHandler - (spracovanie statických súborov), v opačnom prípade sa request začne analyzovať ako dynamický obsah:

  • Total.js sa snaží vyparsovať z requestu tzv. flags napr. xhr, mobile, robot, post, put, atď.
  • následne sa overí podľa Content-Type, či request obsahuje binárne data

Pokiaľ obsahuje request.method: GET, HEAD alebo OPTIONS

Tento typ requestu je spracovaný najrýchlejšie, pretože neobsahuje žiadne data v tele requestu, ktoré je potrebné dodatočne parsovať. Takže framework vyhodnotí:

  • vyhodnotí sa CORS
  • globálny middleware (ak je)
  • vyhodnotí sa autorizácia (ak je)
  • vyhodnotí sa trasovanie
  • vyhodnotí sa route middleware (ak je)
  • vytvorí sa inštancia controllera
  • spustí sa akcia s scope controllera

Request s content-type: application/x-www-form-urlencoded alebo iným

Pri zistení, že sa jedná o tento typ requestu, tak framework vyhľadá trasu route a podľa jej nastavenia začne načúvať udalosť request.on('data'). Ak trasa neexistuje, framework vracia 403 a žiadne data nepríjma.

Ak trasa existuje tak do dočasnej premennej req.buffer_data začne framework postupne zapisovať prijaté data po chunk (rozumej pole bytes), veľkosť prijatých dát (default: 5 kB) závisí od povolenia v metóde F.route() (túto veľkosť je možné zmeniť celoplošne aj v configu). Ako náhle veľkosť prekročí stanovený limit:

  • framework ruší príjmanie dát a maže obsah req.buffer_data pre vyčistenie pamäti
  • následne je clientovi vrátená chyba 431: Request Header Fields Too Large

V prípade, že data sú OK, tak sa vyhodnocuje:

  • data sa deserializujú (JSON to Object, UrlEncoded to Object, Xml to Object alebo ostávajú raw)
  • ak trasa obsahuje mapovanie na schému, tak data sa predpripravia a validujú podľa schémy
  • ďalej sa vykoná authorizácia (ak je)
  • vyhodnotí route middleware (ak je)
  • vytvorí sa inštancia controllera
  • spustí sa akcia v rámci scope controllera

Request s content-type: multipart/form-data

Uploadovanie súborov je v Total.js dosť špecifické kvôli 2 veciam (o tom neskôr). Na spracovanie uploadu používam algoritmus node-formidable, ktorý používa ak sa nemýlim aj framework Express.js. V každom prípade, nejdem písať o tom ako funguje tento algoritmus, ale ako Total.js rieši problémy s uploadovaním súborov a čo všetko k tomu ešte navyše ponúka.

Upload súborov funguje tak, že všetky súbory v requeste sa automaticky ukladajú do temporary adresára /myapp/tmp/. Framework pri zisťovaní obsahu requestu a zistení, že sa jedná o súbor otvorí Writable Stream pre každý súbor a prijaté data po chunk (rozumej pole bytes) zapisuje do súboru. Celý tento proces je streamovaný a neblokuje vlákno. V prípade, že sa jedná o obrázok, tak framework sa snaží na základe content-type súboru zistiť jeho rozmery v prvom chunk. Pri gif, png a svg je rýchlo čitateľné rozlíšenie obrázku, no a pri jpg je ten proces trošku náročnejší. Takže prvou špecifickou vecou v Total.js je to, že Total.js Vám pri uploade obrázkov ihneď zistí ich rozlíšenie (toto správanie sa dá vypnúť cez tzv. Total.js behaviour). Ostatné data (nie binárne) sú spracované klasicky ako pri content-type application/x-www-form-urlencoded.

Druhou špecifickou vecou pri uploade súborov je to, že Total.js počíta veľkosť aktuálne zapísaných dát, takže sa nemôže stať, že Vám niekto začne uploadovať 1 GB data a tým zaťažovať server. By default je veľkosť nastavená na 5 kB a pri každej F.route() si viete túto veľkosť zmeniť:

Po dokončení príjmania dát sa:

  • data sa deserializujú (UrlEncoded to Object)
  • ak trasa obsahuje mapovanie na schému, tak data sa predpripravia a validujú podľa schémy
  • ďalej sa vykoná authorizácia (ak je)
  • vyhodnotí route middleware (ak je)
  • vytvorí sa inštancia controllera
  • spustí sa akcia v rámci scope controllera

Framework vymaže všetky nahraté súbory automaticky, keď sa ukončí response. Toto správanie sa dá tiež vypnúť cez controller.noClear(true), len potom je potrebné vymazať data manuálne cez controller.clear() alebo req.clear().

WebSocket

O WebSocket budem písať v inom blogu, ktorý sa mu bude samostatne venovať. Len pre informovanosť uvediem, že spracovanie WebSocketu funguje trošku inak.

Zaujímavosti

Query argumenty:

req.query / controller.query vlastnosť je parsovaná len vtedy, keď sa volá. Výsledky parsera sú následne uložené do skrytej property a pri opätovnom volaní sa už hodnoty znova neparsujú.

Vyhodnotenie trás je cacheované:

Vyhodnotenie trás a ich flagov sa cachuje. To znamená, že framework sa snaží zapamätať zapamätateľné trasy, tak - aby pri nasledujúcom requeste nemusel prehľadávať všetky trasy znova.

CORS:

CORS mechanizmus sa vyhodnocuje ihneď v prípade OPTIONS metódy a ďalej framework vo vyhodnocovaní nepokračuje. Napísal som ho tak, aby bol čo najefektívnejší. Framework obsahuje F.cors() metódu na povolenie CORS, viac v dokumentácii.

Flagy robot a referer:

Sú analyzované len vtedy, ak framework obsahuje reálne routes s týmito flagmi. ... aj takto som sa snažil šetriť performance.

Timeouty:

Zaujímavou funkciou v Total.js sú timeouty. Pokiaľ controller neodpovie v požadovanom čase, napr. stane sa neočakávaná chyba, framework jednoducho request ukončí. Toto správanie sa dá vypnúť alebo upraviť jeho čas priamo pri vytváraní trasy F.route(). Viac info v dokumentácii.

Dynamicky definované argumenty:

Dynamicky definované argumenty v trasovaní sa nevyhodnocujú nijako špeciálne. Framework vidí dynamické hodnoty takto /products/{0}/{1}/{2}/ a my ich deklarujeme napr. takto /products/{category}/{subcategory}/{detail}/ takže v konečnom dôsledku ich v akcii môžeme nazvať ako chceme, viď nižšie uvedený príklad: