Last additions

This commit is contained in:
Marko Korhonen 2020-05-09 21:12:26 +03:00
parent 015c5db775
commit 09c2cf0074
No known key found for this signature in database
GPG key ID: 911B85FBC6003FE5
12 changed files with 104 additions and 34 deletions

View file

@ -1,5 +1,17 @@
\chapter{Projekti}
Tein insinöörityön yhteydessä full-stack projektin, jossa sekä palvelin- että asiakaspuolen ohjelmointi tehtiin Rustilla. Tarkoituksena ei ollut saada aikaiseksi mitään todella monimutkaista ohjelmaa, vaan puhtaasti arvioida Rustin soveltuvuutta web-ohjelmointiin.
Tein insinöörityön yhteydessä full-stack projektin, jossa sekä palvelin- että asiakaspuolen ohjelmointi tehtiin Rustilla.
\section{Tavoitteet}
Tavoitteena ei ollut saada aikaiseksi mitään todella monimutkaista ohjelmaa, vaan puhtaasti arvioida Rustin soveltuvuutta web-ohjelmointiin yksinkertaisella esimerkkiprojektilla.
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{illustration/Architecture.pdf}
\caption{Projektin arkkitehtuurimalli}
\label{fig:architecture}
\end{figure}
Kuvassa \ref{fig:architecture} näkee sovelluksen arkkitehtuurin. Sovelluksen asiakaspuoli koostuu mallin keltaisista laatikoista ja palvelinpuoli sininisistä. Nuolet merkitsevät datan liikkumista, mikä on kaikkialla kaksisuuntaista.
\section{Kehitysympäristön asennus}
Rust-projektin aloittamiseksi kehittäjä tarvitsee koneelleen Rustin paketinhallintatyökalun, Cargon (katso luku \ref{sect:paketinhallinta}). Olen itse Linux-käyttäjä, joten sain asennettua Cargon Linux-jakeluni ohjelmavarastosta. Windows-käyttäjiä suosittelen käyttämään Rustup-asennusohjelmaa. Lisää tietoa Rustin asentamisesta saa Rustin kotisivuilta \cite{rust:install}.
@ -8,6 +20,8 @@ Projektin saa initialisoitua komennolla \mintinline{shell}{cargo init projektinn
Lähdekoodi sijaitsee hakemistossa ''src''. Cargo kirjoittaa hakemistoon valmiiksi ''main.rs'' -tiedoston, jossa on ''Hello world!'' esimerkkikoodi. ''main.rs'' on aina Rust-ohjelman ensimmäiseksi suoritettava tiedosto, eli niin kutsuttu entrypoint. ''main.rs''-tiedoston sisällä pitää olla ''main()''-funktio, josta ohjelman suoritus alkaa. Projektin voi suorittaa komennolla \mintinline{shell}{cargo run}.
\clearpage
Suosittelen asentamaan myös muutaman Cargon liitännäisen helpottamaan kehitystä. cargo-watchin avulla voi ajaa komennon aina, kun lähdekoodi muuttuu. Esimerkiksi komennolla \mintinline{shell}{cargo watch -x run} voi kääntää ja käynnistää projektin aina uudelleen, kun lähdekoodi muuttuu. cargo-add -liitännäisellä voi helposti lisätä uusia riippuvuuksia projektiinsa. Esimerkiksi Actix webin saa lisättyä komennolla \mintinline{shell}{cargo add actix-web}.
Näillä ohjeilla pääsee alkuun palvelinpuolen kehityksessä. Asiakaspuolen kehityksen käynnistäminen vaati vielä muutaman lisävaiheen, siitä lisää luvussa \ref{sect:asiakaspuoli:asennus}.
@ -20,7 +34,7 @@ Actix web on puolestaan rakennettu hyödyntämällä Actix-sovelluskehystä, jok
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) [koodiesimerkki \ref{code:rust:registeruser}].
\bigskip
\clearpage
\begin{code}
\inputminted{Rust}{code/registeruser.rs}
@ -42,14 +56,21 @@ Palvelinpuolen initialisointi ja käynnistäminen löytyvät liitteestä \ref{ap
\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 kirjoittamani käyttäjän todentamistoiminnallisuuden suojaamaan haluttuja reittejä.
Actix webin modulaarisuus mahdollisti myös tarvittavien väliohjelmistojen (eng. middleware) sisällyttämisen ohjelman toimintaan. Actix identity -paketista löytyi tarvittavat palikat, joilla sain lisättyä itse kirjoittamani käyttäjän todentamistoiminnallisuuden suojaamaan haluttuja reittejä.
Todentamiseen päätin käyttää JSON Web Tokeneita. Ne ovat standardoitu (RFC 7519\cite{jwt:home}) tunnistautumistapa verkossa. Tokenit ovat merkkijonoja, jotka sisältävät JavaScript-objekteja tekstimuodossa (JSON). Token koostuu kolmesta osasta \cite{jwt:home}: ylätunnisteesta (eng. header), hyötykuormasta (eng. payload) ja allekirjoituksesta (eng. signature).
Todentamiseen päätin käyttää JSON Web Tokeneita. Ne ovat standardoitu (RFC 7519\cite{jwt:home}) tunnistautumistapa verkossa. Tokenit ovat merkkijonoja, jotka sisältävät JavaScript-objekteja tekstimuodossa (JSON). Token koostuu kolmesta osasta \cite{jwt:home}: ylätunnisteesta (eng. header), hyötykuormasta (eng. payload) ja allekirjoituksesta (eng. signature). JSON Web Tokenin kaikki osat on havainnollistettu kuvassa \ref{fig:jwt}.
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{illustration/jwt.png}
\caption{JWT:n osat havainnollistettuna käyttämällä jwt.io -sivustoa \cite{jwt:home}}
\label{fig:jwt}
\end{figure}
\bigskip
Ylätunniste koostuu JSON-objektista [koodiesimerkki \ref{code:json:jwt-header}], missä on määritelty tokenin tyyppi (tässä tapauksessa "JWT") ja allekirjoituksen algoritmi, mikä voi olla joko HS256 tai RSA.
\clearpage
\begin{code}
\inputminted{Rust}{code/jwt-header.json}
\captionof{listing}{JSON Web Tokenin ylätunniste}
@ -73,50 +94,58 @@ Tokenin toisessa osassa, hyötykuormassa, on itse tokenin sisältö [koodiesimer
Tokenin kolmesta osasta viimeinen on allekirjoitus. Se on koostettu ylätunnisteessa [koodiesimerkki \ref{code:json:jwt-header}] määritellyllä algoritmilla käyttämällä parametreina tokenin hyötykuormaa [koodiesimerkki \ref{code:json:jwt}] ja vain palvelimen tiedossa olevaa salasanaa. Koko tokenin turvallisuus perustuu juuri tähän allekirjoitukseen. Sen avulla palvelin voi varmistua siitä, että tokenia ei ole muokannut kukaan, kenellä ei ole tätä salasanaa.
Tokenia varmennettaessa hyötykuorma allekirjoitetaan uudelleen ja tätä uutta allekirjoitusta verrataan tokenin mukana tulleeseen allekirjoitukseen. Jos ne ovat samat, palvelin voi olla varma siitä että tokenin sisältöön voi luottaa. Tämän edellytyksenä tietysti on, että salasanaa on säilytetty turvallisella tavalla. JSON Web Tokenin kaikki osat on havainnollistettu kuvassa \ref{fig:jwt}.
\clearpage
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{illustration/jwt.png}
\caption{Kaikki JWT:n osat havainnollistettuna käyttämällä jwt.io -sivustoa \cite{jwt:home}}
\label{fig:jwt}
\end{figure}
\bigskip
Tokenia varmennettaessa hyötykuorma allekirjoitetaan uudelleen ja tätä uutta allekirjoitusta verrataan tokenin mukana tulleeseen allekirjoitukseen. Jos ne ovat samat, palvelin voi olla varma siitä että tokenin sisältöön voi luottaa. Tämän edellytyksenä tietysti on, että salasanaa on säilytetty turvallisella tavalla.
Otetaan esimerkkinä tämän sovelluksen käyttäjä Pasi. Pasi ei ole ylläpitäjä, mutta hän koittaa tehdä itsestään ylläpitäjän muuttamalla omasta tokenistaan parametrin \mintinline{JavaScript}{"admin": false} arvoksi \mintinline{JavaScript}{true}. Kun Pasi lähettää tämän muokatun tokenin palvelimelle, se viedään JWT-validointifunktioon. Palvelin huomaa, että token ei ole enää validi, koska sen sisältöä ei ole allekirjoitettu palvelimen salasanalla.
Päätin tallettaa edellä mainitun JSON Web Tokenin evästeeseen (eng. cookie), joka on standardi tapa tallettaa juuri tällaisia todentamiseen käytettäviä tietoja selaimissa. Evästeiden 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.
Projektin koko JWT-toteutus on liitteessä \ref{appx:jwt}.
Projektin JWT-toteutus on liitteessä \ref{appx:jwt}.
\subsection{CORS}
Lisäsin palvelimelle myös Cross-Origin Resource Sharing (CORS) \cite{wiki:cors} -toiminnallisuuden. Oletuksena selaimen lataama ohjelma saa ladata resursseja vain samasta osoitteesta, kuin mistä itse ohjelma on ladattu. Tämä on turvallisuuskäytäntö, joka kulkee nimellä Same-origin policy (SOP) \cite{wiki:sop}. Näihin resursseihin sisältyvät muunmuassa CSS-tyylimääritykset, kuvat ja JavaScript-ohjelmat.
Lähes kaikki nykyiset web-ohjelmistot kuitenkin vaativat näiden ulkoisien resurssien käyttöä. CORS on tekniikka, jossa palvelin kertoo selaimelle sallitut osoitteet, mistä resursseja saa noutaa. CORS:n lisääminen tähän projektiin hoitui Actixin liitännäisellä actix\char`_cors.
\bigskip
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{illustration/CORS.pdf}
\caption{Esimerkki Cross Origin Resource Sharing -tekniikasta}
\caption{Esimerkki Single origin policy -turvallisuuskäytännöstä}
\label{fig:cors}
\end{figure}
Kuvassa \ref{fig:cors} käyttäjä navigoi osoitteeseen "palvelin1.fi", josta selain lataa HTML-dokumentin. Tässä dokumentissa on kaksi kuvaa, "kuva1.jpg" ja "kuva2.png". Näistä kuva 1 tulee samalta palvelimelta kuin mistä dokumenttikin, se on sallitty Same-origin policy -käytännön takia. Kuva 2 tulee toiselta palvelimelta. Selain estää sen lataamisen, koska palvelin 1 ei ole kertonut asiakkaan selaimelle, että osoitteesta "palvelin2.fi" voi ladata resursseja. Jos kuvan 2 lataaminen haluttaisiin toimivaksi,
Kuvassa \ref{fig:cors} käyttäjä navigoi osoitteeseen ''palvelin1.fi'', josta selain lataa HTML-dokumentin. Tässä dokumentissa on kaksi kuvaa, ''kuva1.jpg'' ja ''kuva2.png''. Kuva 1 tulee samalta palvelimelta kuin mistä dokumenttikin, joten selain sallii sen lataamisen SOP-käytännön mukaisesti. Kuva 2 taas tulee toiselta palvelimelta, joten selain estää sen lataamisen.
CORS:n asetusten määritys löytyy liitteestä \ref{appx:actix}.
Lähes kaikki nykyiset web-ohjelmistot kuitenkin vaativat näiden ulkoisten resurssien käyttöä. CORS on tekniikka, jossa palvelin kertoo selaimelle sallitut osoitteet, mistä resursseja saa noutaa.
Jotta kuvan 2 lataaminen saataisiin toimimaan, palvelimen tulee implementoida CORS-tekniikka. Palvelimen tulee lähettää asiakkaalle HTML-dokumentin yhteydessä ylätunniste [koodiesimerkki \ref{code:cors:header}], joka sisältää palvelimen 2 osoitteen. Näin käyttäjän selain tietää, että palvelin 1 sallii resurssien noutamisen palvelimelta 2.
\bigskip
\begin{code}
\inputminted{shell}{code/cors-header}
\captionof{listing}{Esimerkki CORS-ylätunnisteesta}
\label{code:cors:header}
\end{code}
CORS:n lisääminen tähän projektiin hoitui Actixin liitännäisellä actix\char`_cors. CORS:n asetusten määritys löytyy liitteestä \ref{appx:actix}.
\subsection{Tietokanta}
\label{project:database}
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ää.
\begin{figure}[h]
\centering
\includegraphics[width=12cm]{illustration/er-model.pdf}
\caption{Tietokannan ER-malli}
\label{fig:er-model}
\end{figure}
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.
Tietokannaksi valikoitui itselleni tuttu MySQL. Koska projekti on vain yksinkertainen todennusdemo, tietokantaan tuli vain yksi taulu. Users-taulussa on käyttäjän tiedot: käyttäjänimi, salattu salasana, tieto käyttäjän ylläpitäjyydestä sekä käyttäjän luomisajankohta. Tietokannan ER-mallin näkee kuvasta \ref{fig:er-model}.
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ää.
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 [liite \ref{appx:diesel}], 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.
\clearpage
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.
@ -134,6 +163,8 @@ WebAssembly-koodin suorittamiseksi selain tarvitsee pienen pätkän JavaScripti
\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öliittymän osaa, täytyy implementoida Yew:n Component-rajapintaa. Tässä rajapinnassa on funktiot create, change, update ja view. Näiden funktioiden avulla Yew pystyy orkestroimaan kullakin hetkellä näytettävien komponenttien tilaa.
Tässä projektissa komponentteja oli kaksi: ohjelman juuri [liite \ref{appx:frontend-main}] ja kirjautumiskomponentti [liite \ref{appx:login}].
\subsection{Ulkonäkö}
\label{sect:ulkonäkö}
@ -160,7 +191,7 @@ Myös yew\char`_routerin kanssa oli omat haasteensa. Alkuun en saanut sitä toim
Asiakaspuolen reitityksen toteutuksen voi nähdä liitteessä \ref{appx:frontend-main}.
\section{Ongelmat}
Projektin loppuvaiheilla ilmeni muutamia ongelmia, jotka vaativat vielä jatkokehitystä.
Projektin loppuvaiheilla ilmeni muutamia ongelmia, joiden takia projekti vaatii vielä jatkokehitystä.
\subsection{Evästeet}
\label{sect:problems:cookies}
@ -175,7 +206,7 @@ Yhtenä ratkaisuna tähän ongelmaan olisi käyttää JSON Web Tokenin tallettam
Palvelimen ja tietokannan välisessä yhteydessä oli koko projektin ajan ongelma, että jokainen kysely tietokantaan kesti noin viisi sekuntia. Luulen että tämä ongelma liittyy jotenkin käyttämääni yhteyksien yhdistämiseen (luku \ref{project:database}), koska palvelimen loki näyttää satunnaisesti virheviestin \mintinline{shell}{Failed to create DB pool: Error(Some("Too many connections")}. Asian selvittäminen jää jatkokehitykseen.
\section{Tulokset}
Tuloksena syntyi yksinkertainen web-ohjelma, jossa on toimiva sisäänkirjautumistoiminallisuus, jonka avulla palvelimelta asiakas voi noutaa suojattua dataa. Projektissa sekä palvelin- että asiakaspuoli on toteutettu Rustilla. Kuvassa \ref{fig:login-process} on määritelty kaikki kirjautumisprosessin vaiheet.
Tuloksena syntyi yksinkertainen web-ohjelma, jossa on toimiva sisäänkirjautumistoiminallisuus. Sen avulla asiakas voi noutaa palvelimelta suojattua dataa. Kuvassa \ref{fig:login-process} on määritelty kaikki kirjautumisprosessin vaiheet.
\begin{figure}[h]
\centering