Wat, missä juna?

Pyry-Samuli Lahti

Heikunkeikun, pimpelipompeli!

Scala-blogiprojekti tuli valmiiksi tuossa päälle kuukausi sitten. Nyt tuli sitten näköjään tutustuttua Node.js:ään, jQuery Mobileen ja Herokuun.

Lapsukaiset… oikein arvasitte! Tämä on epäeeppinen kertomus yhden koodisankarin koettelemuksista kohti käytettävää mobiilipalvelua.

Kivi ja kuokka

Tarina alkaa, kun Jussi, hyvä ystäväni sekä sormikoukkuyhdistyksemme varapuheenjohtaja, oli väsyneenä matkalla kohti kotiaan. Aseenaan VR:n käytettävyyden riemujamboree m.vr.fi Jussi ei koskaan löytänyt kotiin…

Jotta Jussin kohtalo ei koituisi muille kohtaloksi, kohtalo ohjasi minut kohti projektia, jonka kohtaloksi muodostui tarjota mobiilisofta junien aikatauluhakua varten.

Node.js

Node.js:n non-blocking I/O -malli sopii kuin lerssi lehtoon proxy-tyyppiseksi kevyeksi palvelimeksi aikatauluhakuja varten. Käydäänpä lyhyesti läpi, minkälaiseksi node.js-moduulikasa loppujen lopuksi muodostui:

ModuuliKuvaus
ExpressPikaisen Googletuksen jälkeen päädyin rakentamaan palvelun Express-nimisen frameworkin päälle.
handlebarsOletuksena Express käyttää Jade template-engineä. Minua Jaden lähestymistapa ei erityisesti viehättänyt, joten vaihdoin template-engineksi handlebarsin (Express ei suoraan osaa käyttää Handlebarsia, mutta onneksi Internetin Ihmeellisestä Maailmasta löytyy valmis hbs-moduuli tätä varten).
gzippoOlin hieman yllättynyt, kun en löytänyt mitään defacto-ratkaisua tekstipohjaisten HTTP-vastausten gzippaukseen. gzippo kuitenkin toimii Express-kehyksen kanssa erittäin hyvin yhteen, vaikka ei mitenkään kovin viralliselta tahi kypsältä moduulilta muuten vaikuttanutkaan.
bundle-upBundle-up on kirjasto assettien (JS/CSS) pakkaukseen ja organisointiin. Bundle-up toimii juuri siten kuten pitääkin. Kehitysaikana JS/CSS-tiedostoja ei pakata ja ne palvellaan yksitellen. Tuotannossa sen sijaan kaikki JS-tiedostot ja CSS-tiedostot pakataan ja minifioidaan yhdeksi tiiviiksi tiedostoksi.
jsdomJsdom on aivan loistava kirjasto datan parsimiseen jQueryn avulla palvelinpäässä.
restlerRestler on HTTP-client kirjasto, joka tarjoaa sopivan abstraktiotason HTTP-kutsujen tekemiseen. Itse käytän sitä erityisesti HTTP-postien yhteydessä.
Moment.jsMoment.js on kalenteri- ja aikamanipulointiin soveltuva kirjasto.

Helppokäyttöisen API:n kautta asiaan

Asia selvä. Backend-stack on siis kasassa. Ensimmäinen askel oli tutustua VR:n tarjoamaan moderniin haku-”API”:iin. Kulahtaneista standardeista poiketen API ei ole JSON-pohjainen, vaan perustuu vieläkin varma- ja helppotoimisempaan HTML-standardiin. Otetaan esimerkki API:n tarjoamasta datasta:

<div class="resultsep">
  <span class="boldText">19:42 Siuntio </span>
   - 20:23 Pasila, Lähijuna 
  <div class="trainchange">20:36 Pasila  - 
    <span class="boldText">21:02 Korso, </span>Lähijuna 
  </div>
  <div class="trainchange">Vaihtoja 1, kesto 1:20.</div>
</div>

Noniin, eli hommahan olikin jo käytännössä tehty. Karkea esimerkki Node.js-koodista, joka toimii proxynä ja muokkaa HTML-pohjaisesta vastauksesta rakenteisen JSON-vastauksen client-kirjastoa (jQuery Mobile) varten:

app.js
app.post('/schedule', function(request, response) {
  try {
    schedule.find(request, response);
  } catch (err) {
    utils.log('Caught exception: %s\nGiving error response...', err.stack);
    response.writeHead(500, { 'Content-Type': 'application/json' });
    response.end(JSON.stringify({}), "utf8");
  }
});
schedule.js
exports.find = function(request, apiResponse) {
  var params = request.body;
  var searchData = {
    dateDay: params.date.substring(0,2),
    dateMonth: params.date.substring(3,5),
    dateYear: params.date.substring(6),
    from: params.fromCity,
    timeHours: params.time.substring(0,2),
    timeMinutes: params.time.substring(3,5),
    timeType: 1, // VR tarjoaa valinnan lähtö-/tuloajalle, mutta todellisuudessa tämä valinta ei tee yhtään mitään
    to: params.toCity
  };
  utils.log("Searching from %s to %s", searchData.from, searchData.to);
  // Eli tässä käytämme restler-kirjastoa, ja teemme varsinaisen kutsun, jolla saamme ehdotukset junayhteyksistä määritellyillä hakuehdoilla
  rest.post(postUrl, {
    data: searchData
  }).on('complete', function(data, response) {
      // Parsimme helppokäyttöisestä HTML-"API":sta vastauksen ja teemme siitä JSON-vastauksen
      jsdom.env(data, ['http://code.jquery.com/jquery-1.7.2.min.js'], function(errors, window) {
        var $ = window.$;
        var jsonResponse = parseSuggestionsUsingJQuery($);
        apiResponse.writeHead(200, { 'Content-Type': 'application/json' });
        apiResponse.end(jsonResponse, "utf8");
      });
    });
};

jQuery Mobile

Kun Node.js-palvelin oli saatu pystyyn, olikin aika siirtyä client-pään koodiin. Itse asiassa aluksi harkitsin tekeväni Android-sovelluksen aikatauluhausta, mutta hetken aikaa pähkäiltyäni päätinkin tehdä HTML5-pohjaisen ratkaisun, koska se toimisi periaatteessa kaikissa puhelimissa. Varsin lyhyen tutkailun jälkeen valitsin jQuery Mobilen lähinnä mutu-tuntumalla (tutkin pikaisesti myös jQTouchia ja Sensa Touchia). Suurin syy valmiin frameworkin käyttöön oli se, että en jaksanut ruveta säätämään sovellukseni tyylin, ulkoasun tai skaalauksen (näytölle) kanssa.

Kaiken kaikkiaan olen ihan tyytyväinen valintaan. Sain melko pienellä vaivalla tehtyä ulkonäöltään varsin siistin pikku sovelluksen. Olennaisimpina parannuksina VR:n omaan hakuun näkisin seuraavat seikat:

  • Se näyttää siltä, että se on tehty tällä vuosituhannella.
  • Ensimmäinen sivulataus on isompi/hitaampi, mutta tämän jälkeen kaikki toimii huomattavasti nopeammin, koska koko sivua ei haeta uudestaan (ainoastaan hakudata JSON-muodossa).
  • Palvelu muistaa käyttäjän tekemät haut, ja niiden valinta uudestaan on nopeaa.
  • Datepicker helpottaa oikean päivän valintaa.
  • Palvelu tukee haun jatkamista seuraavalle päivälle vuorokauden vaihtuessa.

Ihana Heroku

Jäljellä oli enää palvelun hostaus. Valinta oli helppo: pikainen Googletus, jonka jälkeen palvelu pyörikin jo Herokun pilvessä. Ilmaiseksi. On se helppoa. :)

Palvelun laillisuus?

Yritin metsästää tietoa siitä, kuinka laillinen tällainen proxy-tyyppinen palvelu on. Tekemäni sivustohan ei siis tallenna tietoa mitenkään. Se ainoastaan lukee sitä julkiselta sivustolta, ja muokkaa sitä eri (lue: parempaan) muotoon. Ainoa syy sen olemassaoloon on se, että en koe VR:n oman palvelun erityisesti palvelevan käyttötarpeitani. Arvon VR, jos näette tässä jotain väärää, niin ottakaa toki yhteyttä… junankäyttäjien yhteisellä asiallahan tässä ollaan. :)

Sanavagan, missä se on?!

No härregud, sehän löytyy osoitteesta www.junat.info.