From 09c2cf0074f2d063e2f8da6cdb5b1c117c2462d0 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Sat, 9 May 2020 21:12:26 +0300 Subject: [PATCH] Last additions --- tex/appendix/diesel.tex | 12 ++++ tex/chapters/0-abbr.tex | 6 ++ tex/chapters/3-kielet.tex | 12 +++- tex/chapters/4-projekti.tex | 91 ++++++++++++++++++--------- tex/chapters/5-soveltuvuus.tex | 2 +- tex/code/cors-header | 1 + tex/code/down.sql | 1 + tex/code/up.sql | 8 +++ tex/illustration/Architecture.drawio | 1 + tex/illustration/Architecture.pdf | Bin 0 -> 22282 bytes tex/illustration/er-model.pdf | Bin 0 -> 11497 bytes tex/main.tex | 4 ++ 12 files changed, 104 insertions(+), 34 deletions(-) create mode 100644 tex/appendix/diesel.tex create mode 100644 tex/code/cors-header create mode 100644 tex/code/down.sql create mode 100644 tex/code/up.sql create mode 100644 tex/illustration/Architecture.drawio create mode 100644 tex/illustration/Architecture.pdf create mode 100644 tex/illustration/er-model.pdf diff --git a/tex/appendix/diesel.tex b/tex/appendix/diesel.tex new file mode 100644 index 0000000..98de0ef --- /dev/null +++ b/tex/appendix/diesel.tex @@ -0,0 +1,12 @@ +\chapter{Tietokannan migraatiotiedostot}\label{appx:diesel} +\section{up.sql} +\begin{code} + \inputminted{SQL}{code/up.sql} +\end{code} + +\section{down.sql} +\begin{code} + \inputminted{SQL}{code/down.sql} +\end{code} + +\clearpage diff --git a/tex/chapters/0-abbr.tex b/tex/chapters/0-abbr.tex index f2fc6e0..370e852 100644 --- a/tex/chapters/0-abbr.tex +++ b/tex/chapters/0-abbr.tex @@ -52,4 +52,10 @@ description={Ohjelman palvelimella suoritettava osa, johon asiakaspuolen ohjelmisto yhdistyy} } +\newglossaryentry{Full-stack}{ + name={Full-stack}, + plural={full-stack}, + description={Sovellus, missä on toteutettu sekä asiakas- että palvelinpuolen ohjelmisto} +} + \glsaddall diff --git a/tex/chapters/3-kielet.tex b/tex/chapters/3-kielet.tex index 747bf4d..d14dc14 100644 --- a/tex/chapters/3-kielet.tex +++ b/tex/chapters/3-kielet.tex @@ -19,7 +19,7 @@ Rustin yhtenä pääominaisuutena on mainostettu sen uudenlaista näkökulmaa mu Koodiesimerkin \ref{code:rust:ownership} rivillä 9 tapahtuva ''name''-arvon tulostus ei toimi, koska arvon omistajuus on siirretty funktiolle ''say\char`_hello''. Funktion suorittamisen jälkeen se poistuu näkyvyysalueelta ja kaikki sen omistamat arvot vapautetaan muistista. Näin ollen arvoa ''name'' ei ole enää olemassa, kun sitä yritetään käyttää rivillä 9. Koodi saadaan toimimaan pienellä muutoksella. -\bigskip +\clearpage \begin{code} \inputminted{Rust}{code/borrow.rs} @@ -116,6 +116,8 @@ Rustia kehitettäessä on aina ollut tavoitteena luotettavuus. Tämä tarkoittaa Myös Rustin kääntäjään on panostettu paljon, ja virheiden sattuessa se on todella hyvä työkalu ohjelmoijalle. Se alleviivaa ongelmakohdat ja selittää lyhyesti, mistä ongelma johtuu. Jossain tapauksissa kääntäjä jopa antaa pieniä koodin pätkiä, mistä voi olla apua ongelman ratkaisemisessa [koodiesimerkki \ref{code:rust:help}]. +\clearpage + \begin{code} \inputminted[linenos=false]{shell}{code/compiler-help} \captionof{listing}{Rustin kääntäjä auttaa unohtuneen importin lisäämisessä} @@ -163,25 +165,29 @@ Metaohjelmointi avaa aivan uudenlaisia mahdollisuuksia sille, mitä ohjelmointik \subsection{Dokumentaatio ja yhteisö} Rust on tunnettu todella laajasta dokumentaatiostaan ja vahvasta yhteisöstään. Molemmista on paljon apua varsinkin aloittelijoille. -Aloitin itsekin opiskelemaan Rustia vain hieman ennen tämän insinöörityön alkua ja yhteisöstä oli monesti apua projektin aikana vastaan tulleissa ongelmissa. +Aloitin itsekin opiskelemaan Rustia vain hieman ennen tämän insinöörityön alkua. Yhteisöstä oli monesti apua projektin aikana vastaan tulleissa ongelmissa. \subsection{Paketinhallinta} \label{sect:paketinhallinta} Rustin paketinhallinta on toteutettu Cargo-nimisellä ohjelmalla. Sitä voi käyttää koko ohjelmiston elinkaaren ajan aina projektin luomisesta sen julkaisemiseen. Cargon käsittelemiä paketteja kutsutaan laatikoiksi (eng. crate), jotka julkaistaan crates.io \cite{rust:cratesio} pakettirekisterissä. Laatikot voivat myös olla riippuvaisia toisista laatikoista. Laatikon tiedot ja riippuvuudet määritetään Cargo.toml-tiedostossa [koodiesimerkki \ref{code:rust:cargo-toml}]. +\clearpage + \begin{code} \inputminted{TOML}{code/Cargo.toml} \captionof{listing}{Projektin palvelinpuolen Cargo.toml} \label{code:rust:cargo-toml} \end{code} -Cargoon on saatavilla myös useita liitännäisiä, esimerkiksi cargo-watch, joka suorittaa halutun toiminnon aina, kun projektin sisällä tapahtuu muutoksia sekä tässäkin projektissa käytetty cargo-web, joka helpottaa WebAssembly-ohjelmien kehittämistä. +Cargoon on saatavilla myös useita liitännäisiä, esimerkiksi cargo-watch, joka suorittaa halutun toiminnon aina, kun projektin sisällä tapahtuu muutoksia sekä tässäkin insinöörityössä käytetty cargo-web, joka helpottaa WebAssembly-ohjelmien kehittämistä. \section{WebAssembly} WebAssembly \cite{webassembly:home} on kehitteillä oleva asiakaspuolen ohjelmointikieli. Sitä on suunniteltu JavaScriptin seuraajaksi ja sen suurimpana etuna JavaScriptiin verrattuna on huomattavasti matalamman tason esitysmuoto, minkä ansiosta se on suorituskykyisempi. Kehittäjän ei ole tarkoitus kirjoittaa WebAssemblya itse, vaan käyttää työkaluja, joilla olemassa olevia ohjelmointikieliä voi kääntää WebAssemblyksi. Rust on tästä hyvä esimerkki, sillä WebAssembly on yksi sen kääntäjän natiiveista ''targeteista'', samalla tavalla kuin vaikka x86-prosessorit. +\clearpage + WebAssembly on siis ensisijaisesti binääriformaatti, mutta sen voi muuntaa myös tekstiformaatiksi, jonka nimi on WebAssembly text \cite{webassembly:text}. WebAssembly text käyttää syntaksissaan S-lausekkeita \cite{s-expression}. Se on notaatio puurakenteiselle datalle, joka on kehitetty Lisp-ohjelmointikieltä varten, joten WebAssembly text muistuttaa syntaksiltaan hyvin paljon Lispiä. WebAssembly textiä käytetään tilanteissa, joissa ihmisen täytyy ymmärtää, mitä koodissa tapahtuu. Tätä hyödynnetään esimerkiksi WebAssemblyn sisäisessä kehityksessä ja web-ohjelmistojen debuggereissa. \bigskip diff --git a/tex/chapters/4-projekti.tex b/tex/chapters/4-projekti.tex index 96a98c6..ec8687b 100644 --- a/tex/chapters/4-projekti.tex +++ b/tex/chapters/4-projekti.tex @@ -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 diff --git a/tex/chapters/5-soveltuvuus.tex b/tex/chapters/5-soveltuvuus.tex index ee5f318..f0eff94 100644 --- a/tex/chapters/5-soveltuvuus.tex +++ b/tex/chapters/5-soveltuvuus.tex @@ -14,6 +14,6 @@ Vahvan tyypityksen hyötyjä ei voi kieltää, mutta kuten luvussa \ref{sect:sov Kirjastojen saatavuus on ongelma asiakaspuolella. Kaikkien kirjastojen, jotka vuorovaikuttavat selaimen kanssa, pitää nimenomaan tukea WebAssemblya. WebAssembly-ekosysteemi on vielä melko alkutekijöissään, ja näitä kirjastoja on vielä hyvin vähän. Tämän projektin aikana tätä ei ilmennyt muuta kuin tyylimäärityksissä (luku \ref{sect:ulkonäkö}), mutta tämän projektin käyttöliittymä oli vielä todella yksinkertainen, eikä montaa kirjastoa tarvittukaan. -Jos lähdetään siitä oletuksesta, että suurin osa web-kehittäjistä osaa jo JavaScriptiä, en näiden syiden takia voi suositella kokonaan uuden kielen opettelemista asiakaspuolta varten. Mutta jos on esimerkiksi kehittäjä, jolla ei ole mitään taustaa web-kehityksessä, mutta osaa jo valmiiksi Rustia, tai muita matalemman tason kieliä, Rust voi olla mielekäs vaihtoehto. Tässäkin tapauksessa edellytyksenä on, että kaikki kehittäjän tarvitsemat kirjastot ovat saatavilla, tai kehittäjällä on aikaa ja halua toteuttaa puuttuvien kirjastojen toiminallisuus itse. +Jos lähdetään siitä oletuksesta, että suurin osa web-kehittäjistä osaa jo JavaScriptiä, en näiden syiden takia voi suositella kokonaan uuden kielen opettelemista asiakaspuolta varten. Mutta jos on esimerkiksi kehittäjä, jolla ei ole mitään taustaa web-kehityksessä, mutta osaa jo valmiiksi Rustia, tai muita matalan tason kieliä, Rust voi olla mielekäs vaihtoehto. Tässäkin tapauksessa edellytyksenä on, että kaikki kehittäjän tarvitsemat kirjastot ovat saatavilla, tai kehittäjällä on aikaa ja halua toteuttaa puuttuvien kirjastojen toiminallisuus itse. Rust-koodista käännettyjä WebAssembly-tiedostoja voi myös sisällyttää JavaScript-ohjelmiin \cite{javascript-wa}. Jos asiakaspuolen ohjelmassa on osia, jotka vaativat suoritustehoa ja/tai rinnakkaisajo-ominaisuuksia, voi olla hyvä vaihtoehto kirjoittaa vain nämä osat Rustilla ja sisällyttää ne JavaScript-koodiin. diff --git a/tex/code/cors-header b/tex/code/cors-header new file mode 100644 index 0000000..55b0892 --- /dev/null +++ b/tex/code/cors-header @@ -0,0 +1 @@ +Access-Control-Allow-Origin: http://palvelin2.fi diff --git a/tex/code/down.sql b/tex/code/down.sql new file mode 100644 index 0000000..cc1f647 --- /dev/null +++ b/tex/code/down.sql @@ -0,0 +1 @@ +DROP TABLE users; diff --git a/tex/code/up.sql b/tex/code/up.sql new file mode 100644 index 0000000..9f8a82d --- /dev/null +++ b/tex/code/up.sql @@ -0,0 +1,8 @@ +CREATE TABLE users ( + `id` int NOT NULL AUTO_INCREMENT, + `username` varchar(100) UNIQUE NOT NULL, + `password` varchar(128) NOT NULL, + `admin` boolean NOT NULL, + `created_at` timestamp NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/tex/illustration/Architecture.drawio b/tex/illustration/Architecture.drawio new file mode 100644 index 0000000..8226130 --- /dev/null +++ b/tex/illustration/Architecture.drawio @@ -0,0 +1 @@ +7VjbcpswEP0aPyaDQGD7sXHS27TTdNKZNk8dGRZbYxk5sohxv77CiIuQXV9K4vTywmgPq0V79mgR9LzRPHsjyGL6kUfAeq4TZT3vuue6w4G65Pa6sJGLCmAiaKShGrijP0CDjkZTGsHScJScM0kXJhjyJIFQGhgRgq9Mt5gz86kLMgELuAsJs9GvNJLTAh34To2/BTqZlk9Gjr4zJ6WzBpZTEvFVA/Juet5IcC6L0TwbAcupK3kp5r3ecbdamIBEHjJhfbUiM/yeUkzTizTLHj5/f7jQUR4JS3XC7xIJIgGpFy3XJRMqmiJdGVcqk0UOhoynKvjVakol3C1ImIMrpQKFTeWcKQupYUwZG3HGxSaOF/kwiHAeRgo+g8adgTv2gkDd0YsCISHbmS2qOFTSAz4HKdbKRU/wStor3Wl7VVcReX6BTRsVHGo/ooUzqULX3KqBpvcIql2L6i8UJJ+RRBKL7IriNaNJBGI/y2OeKsfow7gCSDibiBz9lEoVBXaUg8AgDreVIwgHMI67KUeAzXIM7WqUULMYVdU6rwayy3EPK6sOomBVU3ec0OM4dsOtzEbBOPA7EjoamsxWnbMpdLSF2+DJqPV2NpWLJTBCk7+A5v7ZWcYWy69CSbNOuX2e5oBfHLe+xe0tYY/AOpbueeh9AR2ib/FrHzeS6FV+ftucM8hySQueiJA23GAYMiq/qbFz6WvrPrf0+DprGuvSSFRSjUm5eV/Gy4162sYq5xVrhsg6QbYqo/LiqQjhAMWp/CYg9257u9aNWvpbSlliQvVfSR/NBW+rr37CLacqlUpKPsaXvikmpyWSIlU9r3kctUKZgRBuBSqosAJtBFcl/hsaHPxTGjyXYlxbMehExdihkPfMmhk+uWZQUzGVfvZrxhCNczn09+hmY92CoIqT/POi84bmHtrQzqlO7JivRtw/UZvYbwVqf7jsUKbSBFk33Ba5w3L3gr0hNjcTNj761aCI2Knsy1yetFViQ/jo18JXRlu79WYwd8K+jdCl5v+Id7jXbzVRxz9R864ZyG/F6Uryfdxa73NI3v4x1pnkdwi1v69jn/CG6FLch55Q3f8N/Qh1B63/lK2Gvt8fHeeP9G46cfcos/5jXbjXf/29m58= \ No newline at end of file diff --git a/tex/illustration/Architecture.pdf b/tex/illustration/Architecture.pdf new file mode 100644 index 0000000000000000000000000000000000000000..933b1345d5cc9cbb31d9d6dbf428f58ad5dbc881 GIT binary patch literal 22282 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 literal 0 HcmV?d00001 diff --git a/tex/illustration/er-model.pdf b/tex/illustration/er-model.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5cff0407c7c32d7391fc90d0c2a311bc7cb94cf2 GIT binary patch literal 11497 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