kolmapäev, 29. september 2010

Progressist JavaScript põhise e-posti serveri kirjutamisel

Mõnda aega tagasi kirjutasin kuidas sain valmis POP3 protokolli implementatsiooni Javascriptis. Sain sellest katsetusest pisiku külge ja otsustasin proovida luua samas stiilis täiemahulist e-posti serveri tarkvara. Väga kaugele pole jõudnud, alustalade seadmine on palju aega ja mõtlemist nõudnud, aga tundub, et kõik on ületatav. Hetke progressi saab näha siit.

Kuna eri serverid (SMTP, POP3, IMAP) on üles ehitatud käsklus-vastus stiilis tekstipõhiste serveritena, lõin sarnaste situatsioonide jaoks serveri baasobjekti, mis sisaldab kõike toimimiseks vajalikku ning mida tuleb lihtsalt konkreetse serveri vajadustest lähtuvalt uute käsklustega täiendada. Näiteks POP3 server saab defineerida käskluse "STLS", aga SMTP hoopis "STARTTLS". Nimetasin selle serveri baasobjekti Request-Answer-Interface'ks ja selle source on nähtaval siin (faili lõpus on kommentaarina ka minimaalne implementatsioon). Täpsemat näiterakendust saab uurida failidest pop3.js ja smtp.js.

Hetke staatuseks on, et SMTP server suudab kirju vastu võtta ning töödelda täielikult läbi kirja päised. Järgmise sammuna on kavas siis kirja sisu töötlemine. Kuna kirja sisu võib olla väga suur (sõltuvalt manuste olemasolust ja suurusest) tuleb seda teha jupikaupa, mitte korraga ja see lisab ühe keerukusastme juurde. Kõige suurem probleem, Base64 dekodeerimine jupi kaupa on praeguseks lahendatud.

Süsteemist siis niipalju, et põhiosa on kirjutatud JavaScriptis (Node.JS), vähemalt üks moodul C's (node-iconv), kasutajate autoriseerimise andmed ja sessioonide andmed asuvad Redis andmebaasis, kirjade sisu ja meta-info läheb MongoDB andmebaasi ning manused MongoDB GridFS failihoidlasse. GridFS annab mugava võimaluse jagada faile suure hulga serverite vahel - selleks tuleb luua MongoDB klaster ning selles saab failide jupitamise ja kokkukogumisega GridFS juba kergelt hakkama. Samuti saab faili juurde salvestada suure hulga suvalist meta-infot. See on midagi, mida tavaline failisüsteem ei võimalda.

Kogu idee asjal on selles, et reeglina hoitakse e-posti originaalkujul (mime formaat) tekstifailides. Kas siis kogu postkast on ühes suures failis või siis on eri kirjad eraldi failides samas kataloogis. Selge on see, et taoline struktuur ei skaleeru ja on üldse ebaefektiivne (näiteks manused salvestatakse kettal Base64 kodeeringus, mis on kolmandiku võrra suurem, kui originaalmaht jne). Minu lahendus hoiaks süsteemselt siis kõiki andmeid neile sobivaimas hoidlas (Redis, MongoDB, GridFS) ning nende andmete põhjal pandaks alles vajadusel (klient üritab kirja alla laadida või tuleb see serverist välja saata) kokku standardne mime formaadis e-kirja dokument.

Selline struktuur annab ka meeletu eelise näiteks veebipõhise e-posti kliendi loomiseks - pole vaja keskenduda millelegi muule kui kasutajaliidesele, kogu info on programmi jaoks loetaval kujul andmebaasis juba olemas, midagi teisendada pole vaja.

laupäev, 11. september 2010

Base64 striimi parsimine

Kuna POP3 protokolli implementeerimine node.js platvormil õnnestus ootamatult hästi, proovisin katsetada ka maailmast kirjade vastuvõtmist SMTP protokolli abil. Ka see õnnestus üsnagi valutult (vähemalt see osa, mis protokolli puutub) - lisasin nimeserverisse MX kirje; kirjutasin lihtsa skripti mis kuulab porti 25 liiklust; skript tunnistas lihtsamaid vajaminevaid käske nagu HELO, DATA jne. Panin seejärel skripti tööle ja üritasin sellele Gmaili'ist kirja saata. Suureks üllatuseks töötas kõik väga kenasti.

Seega tuligi mõte teha valmis juba täismahuline e-maili server. Koos e-posti kontodega, kirjade saatmise ja vastuvõtmisega ning POP3 ja IMAP liidesega kliendi jaoks. Eks paista, kas sellest projektist ka midagi reaalselt välja tuleb, aga hetkel tundub et kõik vajalik on säärase lihtsama projekti jaoks olemas (st. et ei arvesta väga standardiväliste quirkide'ga jne).

Üheks esimeseks probleemiks tekkis kirja vastuvõtul manuste töötlemine. Manused on MIME formaadis kirjades lihtsustatult esitatud nii:

-----suvaline-unikaalne-ääre-tähis-ascii-tähdega+\r\n
Content-Type: text/plain; name="manus.zip"
Content-Disposition: attachment; filename="manus.zip"
Content-Transfer-Encoding: base64

pOb3RlczoNCg0KTmV2ZXIgaW1wbGVtZW50IGEgSlVNUCB0YWcgd2l0aG91dCBhIGNvcnJlc3Bv
bmRpbmcgQUQgdGFnLCBhcyB0aGlzIHdpbGwgcmVzdWx0IGluIG5vIGltcHJlc3Npb25zIG9yIGNs
aWNrcyBiZWluZyBjb3VudGVkIGZvciB0aGUgYXNzb2NpYXRlZCB0YW
[....]
-----suvaline-unikaalne-ääre-tähis-ascii-tähdega+\r\n

Kuna reeglina on manuseks mitte tekstifailid, vaid binaarsed failid, on kõikide baitide probleemivabaks transpordiks kasutusel base64 kodeering. Base64 kasutab vaid 64 võimalikku sümbolit, mis kõik on "prinditavad", mis tähendab et ühe "päris" baidi asemel (kaheksa bitti) saab ühe sümboliga edasi anda vaid 6 bitti (64 = 26).

Probleem tekib suurte manustega - juhul kui manus pole näiteks mitte 2 kB või 100 MB siis kogu selle faili korraga mällu lugemine ja seejärel base64->baitideks teisendamine kõlab üsna kahtlase väärtusega ideena. Seega tuleks teisendus teha juba faili sisselugemisel. Node.JS näiteks ei annagi kogu infot korraga edasi, vaid lõikab sissetuleva info umbkaudu 10 kB suurusteks juppideks.
socket.on("data", function(data){
    console.log(data); // data suurus ei ole suurem kui 10kB
});

Sellise umbmäärase pikkusega lõikude korral aga ei ole base64 dekodeerimine alati võimalik. Oluline on õigest sümbolist alustada ja õigest lõpetada, vastasel korral läheb bitijada nihkesse ja seetõttu tekivad ka baidid väga valed.

Kuidas seda olukorda siis lahendada, nii et saaks dekodeerida base64 andmestriimi?

Väga lihtsalt, kui algus on teada, saab sellest andmeid teisendada 4 baidiste lõikudena ning ülejääk jätta järgmise korrani.
var current = "";
socket.on("data", function(data){
    // NB! tegelikult on andmetüüp mitte string vaid Buffer!
    current += data;
    base64_decode(current.substr(0, current.length-current.length % 4));
    current = current.substr(-current + data.length % 4); // jääk
});

Juhul kui tegu oleks tavalise striimiga, siis piisaks ka määrangust, et striimi kodeeringuks on base64. MIME tüüpi kirja puhul aga on tegu ühe pika stringiga, kus kodeeringud vahelduvad vastavalt kontekstile. Seega tuleb ise jälgida mis andmetega parasjagu tegemist on.

teisipäev, 7. september 2010

POP3 server Node.JS jaoks

Implementeerisin POP3 protokolli toe Node.JS rakenduste jaoks - http://github.com/andris9/n3

Tegu ei ole küll POP3 serveriga selle traditsioonilises mõttes, st. et rakendus ei pea ilmitingimata serveerima klientidele (Outlook, Thunderbird, Mail App vms.) kindla kasutaja postkasti sisu, vaid mida iganes. Näiteks Twitteri sõnumeid või RSS uudiste kokkuvõtet viimasest kontrollist.

Testitud kliendid: Thunderbird 3, Mail App (Max OSX 10.5, 10.6), Outlook Express, Entourage, iPhone Mail. Tekkinud anomaaliad: Outlook Express näitas viimast HTML tag'i vigasena. Paragraafi lõpetamise asemel oli kirja lõpus </

reede, 3. september 2010

Ilmus mu artikkel ajakirjas A&A

Sain täna TTÜ kirjastusest kätte ajakirja A&A uue numbri (2/2010), kus on sees minu artikkel Google App Engine kasutamisest (kahjuks pole seda online kuskilt viidata). Kiirelt otsides leidsin terve rivi bakalaureusetöid ja vähemalt ühe magistritöö, mis allikana viitavad mõnele A&A artiklile, seega on tegu täiesti respektaabli avaldamiskohaga :)

Ainus prolbeem antud ajakirja juures on, et seda on praktiliselt võimatu kuskilt osta. Mina näiteks pole seda kuskil müügil näinud. Ausalt öeldes, ma ei teadnudki sellest ajakirjast suurt midagi enne kui mulle pakuti võimalust sinna oma artikkel kirjutada. Kuulnud olin, aga näinud mitte.

neljapäev, 2. september 2010

node-markdown

Vormistasin väga hea Showdown skripti Node.JS mooduliks nimega node-markdown. Tegu on siis Markdown süntaksi konverteerijaga, mis teeb Markdown tekstist HTML koodi. Installida saab mooduli npm kaudu käsuga npm install node-markdown.

Kasutamine on lihtne - tuleb laadida konverteerimisfunktsioon ja seda saabki seejärel otse kasutada.

var md = require("node-markdown").Markdown,
    html = md(markdown_text);


Täpsemalt võib lugeda mooduli lehelt.

Miks üldse Markdowni kasutada?

Tegu on "naturaalse" süntaksiga, mis on eeskuju võtnud tekstilise e-posti vormistamisest. Idee seisneb selles, et ka töötlemata tekst peaks olema silmale arusaadav ning ei paistaks otseselt kodeeringuna välja.

Näiteks

See on pealkiri
-------------------

Siin tuleb mummudega nimekiri:

  * Nimekirja element nr 1
  * Nimekirja element nr 2
  * Nimekirja element nr 3


Näeb ju arusaadav välja. Kodeerijast läbi lastes muutuks "See on pealkiri"
<H2> elemendiks, "Siia tuleb mummudega nimekiri" <P> paragrafiks ja tärnidega read <UL> nimekirjaks.