thesis/tex/chapters/3-projekti.tex

86 lines
10 KiB
TeX
Raw Normal View History

2020-04-17 18:07:19 +03:00
\chapter{Projekti}
Tein opinnäytetyön yhteydessä fullstack-projektin, missä sekä palvelin- että asiakaspuolen ohjelmointi tehtiin Rustilla. Tarkoituksena ei ollut saada aikaiseksi mitään todella monimutkaista ohjelmaa, vaan puhtaasti arvioida Rustin soveltuvuutta web-ohjelmointiin.
\section{Palvelinpuoli}
2020-05-01 21:27:17 +03:00
\subsection{Kehys}
Palvelinpuolen kehykseksi valikoitui Actix web. Se on käytännössä vastine JavaScript-maailman Express.js:lle, eli se hoitaa HTTP-palvelimen työtä ja reitittää GET ja POST pyynnöt ohjelman oikeille funktioille.
2020-04-17 18:07:19 +03:00
2020-05-02 21:11:26 +03:00
Actix web on puolestaan rakennettu hyödyntämällä Actix -sovelluskehystä, mikä on rakennettu löyhästi actor-mallin pohjalta. Actor-malli\cite{wiki:actor} on Carl Hewittin vuonna 1973 luoma matemaattinen ja tietotekninen malli rinnakkaisajosta. Tämän ansiosta Actix web on hyvin suorituskykyinen ja helposti skaalautuva ratkaisu rajapintoja rakennettaessa.
Actix web on myös hyvin integroitu Rustin vahvaan tyypitykseen. Erityisen vaikuttavaa oli se, että esimerkiksi tietyn rajapinnan päätepisteen POST-pyyntöön pystyy määrittämään parametriksi tietyn rakenteen (eng. structure).
\begin{code}
\inputminted{Rust}{code/registeruser.rs}
\captionof{listing}{Rakenne POST-pyynnön parametrinä}
\label{code:rust:registeruser}
\end{code}
Tällöin varmistutaan automaattisesti siitä, että kun asiakkaan POST-pyyntö saapuu tähän funktioon, kaikki data on oikeanlaista tyyppiä ja kaikki tarvittava data on pyynnössä mukana. Verrattuna JavaScriptiin, jossa ei ole tyyppejä, kaikki vastaanotettava data pitäisi tarkastaa käsin.
\begin{code}
\inputminted{JSON}{code/registeruser.json}
\captionof{listing}{Rakenteeseen sovitettava JavaScript-objekti}
\label{code:json:registeruser}
\end{code}
Kun koodiesimerkin 7 funktioon ''register'' saapuu koodiesimerkin 8 mukainen JSON objekti, Actix huomaa väärän tyypin ja vastaa statuskoodilla ''400 Bad Request'', koska objektin parametri \mintinline{JSON}{"admin"} ei ole tyyppiä \mintinline{Rust}{bool}.
2020-04-17 18:07:19 +03:00
2020-05-01 21:27:17 +03:00
\subsection{Todentaminen}
Actix webin modulaarisuus mahdollisti myös helposti tarvittavien väliohjelmistojen sisällyttämisen ohjelman toimintaan. Actix identity -paketista löytyi tarvittavat palikat joilla sain lisättyä itse kirjoittaman käyttäjän todentamistoiminnallisuuden suojaamaan haluttuja reittejä.
2020-05-02 21:11:26 +03:00
Todentamiseen päätin käyttää JSON Web Tokeneita. JSON Web Tokenit ovat standardoitu (RFC 7519\cite{jwt:home}) tunnistautumistapa verkossa. Tokenit ovat merkkijonoja, jotka sisältävät JavaScript-objektin tekstimuodossa (JSON).
\begin{code}
\inputminted{Rust}{code/jwt.json}
\captionof{listing}{Yhden JWT-tokenin sisältö tästä projektista}
\label{code:json:jwt}
\end{code}
2020-05-01 21:27:17 +03:00
Tämä tokenin sisältö on kaikkien sen hallussapitäjien nähtävissä. Turvallisuus tulee siitä, että token on allekirjoitettu palvelinpuolella vain palvelimen tiedossa olevalla salasanalla niin, että jos tokenin sisältö muuttuu yhtään, palvelin näkee että se ei ole enää validi.
2020-05-02 21:11:26 +03:00
Eli esimerkiksi jos joku tämän sovelluksen käyttäjä, joka ei ole ylläpitäjä, koittaa tehdä itsestään ylläpitäjän muuttamalla tokenista parametrin \mintinline{JSON}{"admin"}: \mintinline{JSON}{false} arvoksi \mintinline{JSON}{true}, palvelimella suoritettava JWT validointifunktio, joka tietää oikean salasanan, näkee että tämä tunniste ei ole validi.
2020-05-01 21:27:17 +03:00
2020-05-02 21:11:26 +03:00
Tokenin sisällön voi päättää kokonaan itse, vaikkakin joitakin standardeja kenttiä on määritetty, esimerkiksi iss (issuer), sub (subject) ja exp (expiration time). Päätin sisällyttää tiedon siitä, että onko käyttäjä ylläpitäjä, koska tätä tietoa voi sitten käyttää asiakaspuolella esimerkiksi käyttöliittymän muokkaamiseen käyttäjän roolin perusteella. Usein myös käyttäjän nimi sisällytetään tokeniin. Tokenin sisältöä suunnitellessa kannattaa pitää mielessä että sen sisältö on nähtävissä kaikille, joten se ei ole oikea paikka tallettaa salaista tietoa, kuten vaikka käyttäjän salasana.
2020-05-01 21:27:17 +03:00
2020-05-02 21:11:26 +03:00
Päätin tallettaa edellä mainitun JSON Web Tokenin keksiin (eng. cookie), joka on standardi tapa tallettaa juuri tällaisia todentamiseen käytettäviä tietoja selaimiin. Keksien käyttämisen etu on se, että selain huolehtii sen tallettamisesta automaattisesti ilman lisätoimia kehittäjältä. Lisäksi selain sisällyttää sen seuraaviin kutsuihin automaattisesti.
2020-05-01 21:27:17 +03:00
\subsection{Tietokanta}
2020-04-17 18:07:19 +03:00
Tietokannaksi valikoitui itselleni tuttu MySQL. Relaatiotietokannan sai helposti yhdistettyä Rust-koodiini Diesel ORM:llä. Diesel on tähän mennessä käyttämistäni ORM-kirjastoista selkeästi mukavin käyttää.
2020-05-02 21:11:26 +03:00
Käytännöllisimmät ominaisuudet kehittäjän näkökulmasta olivat Dieselin mukana tuleva komentorivikäyttöliittymä ja migraatiot. Jokaiselle taululle luodaan uusi migraatio, esimerkiksi \mintinline{shell}{diesel migration generate users}, jonka jälkeen Dieselin luomaan hakemistoon kirjoitetaan up.sql- ja down.sql-tiedostot, eli ohjeet siitä, miten tämä taulu luodaan ja poistetaan. Taulu viedään tietokantaan komennolla \mintinline{shell}{diesel migration run} ja taulun voi poistaa ja luoda uudelleen komennolla \mintinline{shell}{diesel migration redo}. Tämä mahdollistaa myös samalla sen, että versiohallintaan voi tallentaa useita versioita samasta taulusta ja palata helposti takaisin vanhempaan versioon, jos uudemman kanssa ilmenee ongelmia.
2020-04-17 18:07:19 +03:00
2020-05-01 21:27:17 +03:00
Edellä mainitut työkalut helpottivat tietokannan kehitystä huomattavasti. Usein varsinkin projektin alkuvaiheilla tietokanta muuttuu jatkuvasti ja usein tulee tarve poistaa ja luoda tietokanta uudelleen. Monet kehittäjät pitävät juurikin tällaista Dieselin up.sql kaltaista tiedostoa versiohallinnassa ja tarpeen mukaan poistavat tietokannan käsin ja liittävät komennon tiedostosta tietokannan komentorivikäyttöliittymään. Dieseliä käytettäessä tämä tulee tehtyä automaattisesti ja se tuntuu todella luontevalta.
2020-04-17 18:07:19 +03:00
\section{Asiakaspuoli}
2020-05-02 21:11:26 +03:00
\subsection{Asennus}
Projektin asiakaspuolen aloittaminen vaati aika paljon tutkimustyötä. Rust-koodi pitää kääntää WebAssemblyksi, johon on olemassa useita eri työkaluja. Lisäksi selaimien rajapintojen käyttämiseen tarvitsee jonkinlaisen kirjaston, joita Rust-maailmassa on tällä hetkellä kaksi: stdweb\cite{webassembly:stdweb} ja web\char`_sys\cite{webassembly:websys}. Näistä stdweb on vanhempi, mutta myös tuetumpi. web\char`_sys on uudempi tulokas ja näyttää siltä että se tulee korvaamaan stdweb:n tulevaisuudessa.
Valitsemani sovelluskehys tukee molempia kirjastoja, mutta päädyin kuitenkin valitsemaan stdweb:n. Suurimpana syynä oli projektin aloittamisen helppous, joka tehdään Cargon liitännäisellä cargo-web. Toisena syynä oli että WebAssembly-ohjelman paketointi web\char`_sys:n kanssa on tätä kirjoittaessani on riippuvainen NodeJS:stä, kun taas stdweb ei vaadi NodeJS:n asennusta kehittäjän koneelle ollenkaan.
WebAssembly-koodin suorittamiseksi selain tarvitsee pienen pätkän JavaScriptiä, joka tekee tarvittavat toimet WebAssembly-ohjelman käynnistämiseksi. Tämän koodin generoi puolestani cargo-web. Lisäksi JavaScriptin suorittamiseen tarvitaan yksi HTML-tiedosto, minne voi myös sisällyttää metadataa, kuten sivuston otsikon, joka näkyy selaimen välilehdessä. Tähän tiedostoon linkitetään myös kaikki muu staattinen data, kuten tyylimääritykset. Nämä tiedostot laitoin käytännön mukaisesti static-nimiseen hakemistoon asiakaspuolen projektin juureen. Tämän hakemiston linkitin symbolisella linkillä palvelinpuolen projektiin, jonka HTTP-serveri voi sitten lähettää HTML-dokumentin, JavaScript-tiedoston ja WebAssembly-binäärin käyttäjän selaimelle.
\subsection{Kehys}
Asiakaspuolen sovelluskehykseksi valitsin Yew:n\cite{yew:home}. Yew muistuttaa hyvin paljon JavaScript-maailmassa suosittua Reactia, eli sen on komponenttipohjainen. Tämä tarkoittaa sitä että kaikkien ohjelman osien, joilla halutaan näyttää jotakin käyttöliittyän osaa, täytyy implementoida Yew:n Component-rajapintaa, jossa on funktiot create, change, update ja view. Näiden funktioiden avulla Yew pystyy orkestroimaan kullakin hetkellä näytettävien komponenttien tilaa.
\subsection{Ulkonäkö}
Olen JavaScript-maailmassa tottunut siihen, että käyttöliittymäkehyksiin löytyy usein liitännäinen, joka tarjoaa valmiit tyylimääritykset, mutta en löytänyt vastaavaista liitännäistä Yew:lle. Kaikki käyttöliittymän elementit olivat siis selaimen oletustyylisiä. En halunnut ryhtyä tässä projektissa kirjoittamaan CSS-määrityksiä alusta alkaen, joten päädyin käyttämään UIkittiä\cite{uikit}. UIkitin dokumentaatio on laaja ja siellä on paljon esimerkkejä, joten alkuun pääsi todella nopeasti.
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{illustration/login.png}
\caption{Login-komponentin ulkonäkö}
\label{fig:login}
\end{figure}
\section{Ongelmat}
Projektin loppuvaiheilla ilmeni muutamia ongelmia, mitä en ehtinyt enää ajan puutteen takia korjaamaan.
Palvelinpuolella keksin kokoava kirjasto, Actixin liitännäinen ''actix-identity'', kirjoittaa oletuksena keksiin parametrin \mintinline{JSON}{"HttpOnly": true}, mikä tarkoittaa sitä, että selain ei anna sen sisällä suoritettaville ohjelmille pääsyä tähän keksiin. Tämä on todella hyvä turvallisuusominaisuus, mikä estää haitallisia ohjelmia varastamaan käyttäjän kirjautumistietoja.
2020-05-01 21:27:17 +03:00
2020-05-02 21:11:26 +03:00
Suunnittelmana oli käyttää keksin tietoja asiakaspuolella reititykseen. Esimerkiksi jos keksiä ei ole olemassa, käyttäjä tulee reitittää sisäänkirjautumiskomponenttiin, missä keksi voidaan noutaa palvelimelta. Koska keksissä on tämä HttpOnly-parametri, tähän ei ole mahdollisuutta. Tämän parametrin voisi tietysti laittaa pois päältä, mutta ''actix-identity'' ei tarjoa tähän mitään mahdollisuutta.
2020-04-17 18:07:19 +03:00
2020-05-02 21:11:26 +03:00
Yhtenä ratkaisuna tähän ongelmaan olisi käyttää JSON Web Tokenin tallettamiseen keksien sijasta selaimen LocalStoragea. Tämä vaatisi lisää työtä sekä asiakaspuolella että palvelinpuolella ja valitettavasti tähän ei jäänyt enää aikaa.