Continue writing report

This commit is contained in:
Marko Korhonen 2020-05-02 21:11:26 +03:00
parent bc3fe77c93
commit f36c9fef60
No known key found for this signature in database
GPG key ID: 911B85FBC6003FE5
22 changed files with 266 additions and 165 deletions

View file

@ -56,7 +56,7 @@
@misc{rust:cratesio,
title = {The Rust community's crate registry},
url = {https://crates.io},
publisher = {}
publisher = {},
author = {Rust Team},
year = 2020,
lastchecked = {1. Toukokuuta 2020},
@ -89,11 +89,38 @@
lastchecked = {1. Toukokuuta 2020},
}
@misc{webassembly:web_sys,
title = {web_sys},
url = "https://docs.rs/web-sys/0.3.35/i686-unknown-linux-gnu/web_sys/",
@misc{webassembly:websys,
title = {web\char`_sys},
url = {https://docs.rs/web-sys/0.3.35/i686-unknown-linux-gnu/web_sys},
publisher = {},
author = {Rust team},
year = 2020,
lastchecked = {1. Toukokuuta 2020},
}
@misc{uikit,
title = {UIkit homepage},
url = {https://getuikit.com},
publisher = {},
author = {},
year = 2020,
lastchecked = {2. Toukokuuta 2020},
}
@misc{webassembly:home,
title = {WebAssembly homepage},
url = {https://webassembly.org},
publisher = {},
author = {},
year = 2020,
lastchecked = {2. Toukokuuta 2020},
}
@misc{webassembly:text,
title ={Understanding WebAssembly text format},
url = {https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format},
publisher = {},
author = {Mozilla},
year = 2019,
lastchecked = {2. Toukokuuta 2020},
}

View file

@ -10,13 +10,14 @@
\newacronym{jwt}{JWT}{JSON Web Token}
\newacronym{rest}{REST}{Representational state transfer}
\newacronym{api}{API}{Application Programming Interface}
\newacronym{css}{CSS}{Cascading Style Sheets}
% Glossary entries
\newglossaryentry{Asiakaspuoli}{
name={Asiakaspuoli},
plural={asiakaspuolen},
description={Loppukäyttäjän käyttöliittymä ohjelmaan. Sanaa käytetään usein web-kehityksessä jossa sillä viitataan nettiselaimeen}
description={Loppukäyttäjän käyttöliittymä ohjelmaan. Sanaa käytetään usein web-kehityksessä, jossa sillä viitataan nettiselaimeen}
}
\newglossaryentry{Palvelinpuoli}{
@ -25,7 +26,7 @@
description={Ohjelman palvelimella suoritettava osa, johon asiakaspuolen ohjelmisto yhdistyy}
}
\newglossaryentry{ORM}{
\newglossaryentry{Object-relational mapping}{
name={ORM},
description={Ohjelman objektien sovittamista relaatiotietokannan tauluihin ja tietokannasta takaisin ohjelmaan}
}

View file

@ -25,12 +25,12 @@ Simo Silander
\def\pvm{\specialdate\today}
%English section, for title/abstract
\title{Rust as a web development language}
\title{Rust as a Web Development Language}
\def\metropoliadegree {Bachelor of Engineering} % change to your needs, e.g. "master", etc.
\def\metropoliadegreeprogramme {Information Technology}
\def\metropoliadegreeprogramme {Information and Communications Technology}
\def\metropoliaspecialisation {Software Engineering}
\def\metropoliainstructors {
Simo Silander, Principal Lecturer
Simo Silander, Senior Lecturer
}
\def\metropoliakeywords {Keywords}
\date{\longmonth\today}

View file

@ -5,9 +5,9 @@
Tämän takia myös web-ohjelmoinnissa käytettävissä teknologioissa on tapahtunut paljon muutoksia. Jotta nettisivuista saatiin pelkkien HTML-dokumenttien sijasta ohjelmia, 90-luvun puolivälissä julkaistiin PHP (palvelinpuolen kieli) ja JavaScript (asiakaspuolen kieli). Tänä päivänä näistä kahdesta JavaScript on ehdottomasti suositumpi, minkä takia keskityn tässä opinnäytetyössä sen vertailuun Rustin kanssa.
Vaikka JavaScript onkin suosittu, siinä on paljon ongelmia\cite{medium:javascript}. Suurimmat ongelmat liittyvät kielen heikkoon tyypitykseen, mikä saattaa aiheuttaa ongelmia jotka ilmenevät vasta ajon aikana ja vain tietyissä rajatapauksissa. Tämän takia koodin testaamiseen ja laadun ylläpitämiseen pitää panostaa enemmän. Toinen iso ongelma on se, että JavaScript ei ole tarkka koodin tyylistä. Jo pelkästään funktion kirjoittamiseen on useita eri tyylejä. Tämä hankaloittaa projekteissa kommunikointia ja työtoverien koodin lukemista.
Vaikka JavaScript onkin suosittu, siinä on paljon ongelmia\cite{medium:javascript}. Suurimmat ongelmat liittyvät kielen heikkoon tyypitykseen, mikä saattaa aiheuttaa ongelmia, jotka ilmenevät vasta ajon aikana ja vain tietyissä rajatapauksissa. Tämän takia koodin testaamiseen ja laadun ylläpitämiseen pitää panostaa enemmän. Toinen iso ongelma on se, että JavaScript ei ole tarkka koodin tyylistä. Jo pelkästään funktion kirjoittamiseen on useita eri tyylejä. Tämä hankaloittaa projekteissa kommunikointia ja työtoverien koodin lukemista.
Koska JavaScript on tulkittava kieli, se tulee aina häviämään suorituskyvyssä koottaville kielille. Tätä eroa on osittain kavennettu ajonaikaisen kääntämisen optimoinneilla.
Koska JavaScript on tulkattava kieli, se tulee aina häviämään suorituskyvyssä käännettäville kielille. Tätä eroa on osittain kavennettu ajonaikaisen kääntämisen optimoinneilla.
Edellä mainittujen syiden takia lähivuosina on ilmaantunut useita vaihtoehtoja sekä palvelin- että asiakaspuolen ohjelmointikieliksi. Näistä yksi itseäni eniten kiinnostava on Rust. Tämän opinnäytetyön yhteydessä toteutettu projekti käyttää Rustia sekä palvelimella että asiakaspuolella.

View file

@ -1,82 +1,70 @@
\chapter{Kielet}
\section{Rust}
Rust\cite{rust:lang} on Mozillan 2010 julkaisema ohjelmointikieli. Se on hyvin suorituskykyinen järjestelmätason ohjelmointikieli, muistuttaen monilta osin C ja C++ kieliä. Rustin tarkoituksena on säilyttää näiden vanhojen kielien suorituskyky, mutta kuitenkin tarjota samalla muun muassa vahva tyypitys ja taattu turvallinen rinnakaisajo. Lisäksi tyypilliset C-kielien muistinhallintaongelmat on pyritty ratkaisemaan käytännöillä, jotka ovat samalla tehokkaita suorituskyvyn näkökulmasta, mutta myös helppoja käyttää ohjelmoijalle.
Rust \cite{rust:lang} on Mozillan 2010 julkaisema ohjelmointikieli. Se on hyvin suorituskykyinen järjestelmätason ohjelmointikieli, muistuttaen monilta osin C- ja C++ -kieliä. Rustin tarkoituksena on säilyttää näiden vanhojen kielien suorituskyky, mutta kuitenkin tarjota samalla muun muassa vahva tyypitys ja taattu turvallinen rinnakaisajo. Lisäksi tyypilliset C-kielien muistinhallintaongelmat on pyritty ratkaisemaan käytännöillä, jotka ovat samalla tehokkaita suorituskyvyn näkökulmasta, mutta myös helppoja käyttää ohjelmoijalle.
\subsection{Muistinhallinta}
Monissa korkean tason ohjelmointikielissä, esimerkiksi JavaScriptissä, on automaattinen roskienkeräys\cite{wiki:garbagecollection} (engl. garbage collector). Se on prosessi, joka siivoaa muistista käyttämättömiä tietoja ja näin ollen vapauttaa muistia. Automaattisen roskienkeräyksen ongelma on, että se itsessään käyttää järjestelmän resursseja, ja roskienkeruu on hidasta.
Monissa korkean tason ohjelmointikielissä, esimerkiksi JavaScriptissä, on automaattinen roskienkeräys \cite{wiki:garbagecollection} (engl. garbage collector). Se on prosessi, joka siivoaa muistista käyttämättömiä tietoja ja näin ollen vapauttaa muistia. Automaattisen roskienkeräyksen ongelma on, että se itsessään käyttää järjestelmän resursseja, ja roskienkeruu on hidasta.
Automaattiselle roskienkeruulle on aikaisemmin ollut vaihtoehtona vain manuaalinen muistinhallinta, missä ohjelmoija varaa ja vapauttaa muistia tarpeen mukaan. Tämä on taas verratuna automaattiseen roskien keruuseen melko työlästä ja virheherkkää.
Rustin yhtenä pääominaisuutena on mainostettu sen uudenlaista näkökulmaa muistinhallintaan: omistajuutta\cite{rust:ownership}. Siinä jokaisella arvolla on omistaja, ja kun omistaja menee näkyvyysalueen ulkopuolelle, niin menevät myös sen omistamat arvotkin, eli ne vapautetaan muistista. Arvojen omistajuutta voi siirtää joko pysyvästi tai väliaikaisesti lainaamalla. Otetaan esimerkkinä seuraava yksinkertainen koodi:
\clearpage
Rustin yhtenä pääominaisuutena on mainostettu sen uudenlaista näkökulmaa muistinhallintaan: omistajuutta \cite{rust:ownership}. Siinä jokaisella arvolla on omistaja, ja kun omistaja menee näkyvyysalueen ulkopuolelle, niin menevät myös sen omistamat arvotkin, eli ne vapautetaan muistista. Arvojen omistajuutta voi siirtää joko pysyvästi tai väliaikaisesti lainaamalla.
\begin{minted}{Rust}
fn say_hello(name: String) {
println!("Hello {}!", name);
}
\begin{code}
\inputminted{Rust}{code/ownership.rs}
\captionof{listing}{Omistajuus}
\label{code:rust:ownership}
\end{code}
fn main() {
let name = String::from("Marko");
say_hello(name);
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:
println!("{}", name);
}
\end{minted}
\begin{code}
\inputminted{Rust}{code/borrow.rs}
\captionof{listing}{Lainaus}
\label{code:rust:borrow}
\end{code}
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:
\begin{minted}{Rust}
fn say_hello(name: &String) {
println!("Hello {}!", name);
}
fn main() {
let name = String::from("Marko");
say_hello(&name);
println!("{}", name);
}
\end{minted}
Tässä esimerkissä omistajuuden siirtäminen on korvattu lainauksella. Lainauksessa arvon omistajuus säilyy nykyisellään ja lainaaja antaa itse arvon sijasta viitteen(eng. reference). Viite on osoitin, joka osoittaa samaan muistipaikkaan kuin missä alkuperäinen arvo on. Lainaaminen tehdään käyttämällä merkkiä ''\char`&''.
Koodiesimerkissä \ref{code:rust:borrow} omistajuuden siirtäminen on korvattu lainauksella. Tämä muutos pitää tehdä sekä funktion parametrien määritykseen riville 1, että funktion kutsuun riville 7. Lainauksessa arvon omistajuus säilyy nykyisellään ja lainaaja antaa itse arvon sijasta viitteen(eng. reference). Viite on osoitin, joka osoittaa samaan muistipaikkaan kuin missä alkuperäinen arvo on. Lainaaminen tehdään käyttämällä merkkiä ''\char`&''.
\clearpage
\subsection{Vahva tyypitys}
Rust on vahvasti tyypitetty kieli, mikä tarkoittaa sitä, että kaikkien arvojen tyypit pitää olla tiedossa ohjelman kokoamisen aikana. Tähän sisältyy myös funktioiden parametrit ja paluuarvot. Usein Rustin kääntäjä osaa päätellä arvojen tyypit itse, varsinkin yksinkertaisissa tapauksissa:
\begin{minted}{Rust}
let name = "Marko"; // string slice
let age = 26; // integer
\end{minted}
Rust on vahvasti tyypitetty kieli, mikä tarkoittaa sitä, että kaikkien arvojen tyypit pitää olla tiedossa ohjelman kokoamisen aikana. Tähän sisältyy myös funktioiden parametrit ja paluuarvot. Usein Rustin kääntäjä osaa päätellä (eng. inference) arvojen tyypit itse, varsinkin yksinkertaisissa tapauksissa:
\begin{code}
\inputminted{Rust}{code/type-inference.rs}
\captionof{listing}{Tyypin päättely}
\label{code:rust:inference}
\end{code}
Niissä tapauksissa joissa tyypille voi olla useita vaihtoehtoja, tai silloin jos arvon määrityksen yhteydessä tapahtuu konversio, ohjelmoijan tulee määrittää tyyppi. Tyypin voi määrittää näin:
\begin{minted}{Rust}
let name: &str = "Marko";
let age: u8 = 26;
\end{minted}
\begin{code}
\inputminted{Rust}{code/type-notation.rs}
\captionof{listing}{Tyypin merkintä}
\label{code:rust:type-notation}
\end{code}
Valitsin arvolle ''age'' tyypin \code{u8}, koska se on pienin kokonaislukutyypeistä ja sen arvo voi olla välillä 0-255. Näin voi potentiaalisesti vähentää ohjelman muistin käyttöä. Lisäksi tyypeillä voi myös karkeasti rajata funktion parametrejen arvojen vaihteluväliä. Esimerkiksi jos on kirjoittamassa funktiota joka ottaa parametrina henkilön iän, \code{u8} on hyvä valinta koska ihmisen ikä ei voi olla negatiivinen ja ihmiset myös harvoin elävät yli 255 vuotta.
Valitsin arvolle ''age'' tyypin u8, koska se on pienin kokonaislukutyypeistä ja sen arvo voi olla välillä 0-255. Näin voi potentiaalisesti vähentää ohjelman muistin käyttöä. Lisäksi tyypeillä voi myös karkeasti rajata funktion parametrejen arvojen vaihteluväliä. Esimerkiksi jos on kirjoittamassa funktiota joka ottaa parametrina henkilön iän, u8 on hyvä valinta koska ihmisen ikä ei voi olla negatiivinen ja ihmiset myös harvoin elävät yli 255 vuotta.
Edellä mainittu tyyppi \code{u8} on niin kutsuttu allekirjoittamaton kokonaisluku (eng. \textbf{u}nsigned). Allekirjoitetuissa kokonaisluvuissa (eng. signed) käytetään yksi bitti merkkaamaan sitä, onko luku positiivinen vai negatiivinen. Allekirjoittamattamattomissa luvuissa tätä ei tehdä, joten luku voi olla hieman isompi kuin allekirjoitettu luku, mutta se ei voi olla negatiivinen.
Edellä mainittu tyyppi u8 on niin kutsuttu allekirjoittamaton kokonaisluku (eng. \textbf{u}nsigned). Allekirjoitetuissa kokonaisluvuissa (eng. signed) käytetään yksi bitti merkkaamaan sitä, onko luku positiivinen vai negatiivinen. Allekirjoittamattamattomissa luvuissa tätä ei tehdä, joten luku voi olla hieman isompi kuin allekirjoitettu luku, mutta se ei voi olla negatiivinen.
\begin{table}[h!]
\label{tab:table1}
\begin{center}
\begin{minipage}{2in}
\begin{minipage}{2.5in}
\begin{tabular}{|c|c|c|}
\hline
\multicolumn{3}{|c|}{\textbf{Allekirjoittamaton}} \\
\hline
Tyyppi & Minimi & Maksimi \\
\hline
u8 & 0 & $2^{8}-1$\\
u16 & 0 & $2^{16}-1$\\
u32 & 0 & $2^{32}-1$\\
u64 & 0 & $2^{64}-1$\\
u128 & 0 & $2^{128}-1$\\
u8 & 0 & $ 2^{8}-1 $\\
u16 & 0 & $ 2^{16}-1 $\\
u32 & 0 & $ 2^{32}-1 $\\
u64 & 0 & $ 2^{64}-1 $\\
u128 & 0 & $ 2^{128}-1 $\\
\hline
\end{tabular}
\end{minipage}
@ -87,76 +75,48 @@ Edellä mainittu tyyppi \code{u8} on niin kutsuttu allekirjoittamaton kokonaislu
\hline
Tyyppi & Minimi & Maksimi \\
\hline
i8 & $-2^{7}$ & $2^{7}-1$\\
i16 & $-2^{15}$ & $2^{15}-1$\\
i32 & $-2^{31}$ & $2^{31}-1$\\
i64 & $-2^{64}$ & $2^{63}-1$\\
i128 & $-2^{127}$ & $2^{127}-1$\\
i8 & $ -2^{7} $ & $ 2^{7}-1 $\\
i16 & $ -2^{15} $ & $ 2^{15}-1 $\\
i32 & $ -2^{31} $ & $ 2^{31}-1 $\\
i64 & $ -2^{64} $ & $ 2^{63}-1 $\\
i128 & $ -2^{127} $ & $ 2^{127}-1 $\\
\hline
\end{tabular}
\end{minipage}
\end{center}
\caption{\textit{Rustin kokonaislukutyypit ja niiden vaihteluvälit}}
\caption{Rustin kokonaislukutyypit ja niiden vaihteluvälit}
\end{table}
Toisin kuin C- ja C++ -kielissä, Rustissa on oletuksena allekirjoittamattomien kokonaislukujen ylivuoto pois päältä. Tämä tarkoittaa sitä, että jos 8-bittisen allekirjoittamattoman kokonaisluvun arvo on esimerkiksi 256, siitä tulee 0. Rustin kääntäjä siis ei anna tällaisen tapahtua vaan kääntämisen yhteydessä tulee virheviesti:
\begin{minted}{shell}
error: literal out of range for `u8`
--> types.rs:2:19
|
2 | let age: u8 = 256;
| ^^^
|
= note: `#[deny(overflowing_literals)]` on by default
error: aborting due to previous error
\end{minted}
Toisin kuin C- ja C++ -kielissä, Rustissa on oletuksena allekirjoittamattomien kokonaislukujen ylivuoto pois päältä. Tämä tarkoittaa sitä, että jos 8-bittisen allekirjoittamattoman kokonaisluvun (u8) arvo on esimerkiksi 256, siitä tulee 0. Rustin kääntäjä siis ei anna tällaisen tapahtua vaan kääntämisen yhteydessä tulee virheviesti:
\begin{code}
\inputminted{shell}{code/integer-overflow}
\captionof{listing}{Allekirjoittamattoman kokonaisluvun ylivuoto}
\label{code:rust:integer-overflow}
\end{code}
\subsection{Muuttumaton data}
Rustissa kaikki arvot ovat oletuksena muuttumattomia (engl.\ immutable). Jos muuttumattoman datan sijasta tarvitsee muuttujia (engl. mutable), voi käyttää avainsanaa ''mut'', esimerkiksi \code{let mut name = "Marko"}. Myös lainaukset suoritetaan oletuksena muuttumattomasti ja muutettavan lainauksen voi tehdä samalla avainsanalla, esimerkiksi \code{say\char`_hello(\char`&mut name)}.
Rustissa kaikki arvot ovat oletuksena muuttumattomia (engl.\ immutable). Jos muuttumattoman datan sijasta tarvitsee muuttujia (engl. mutable), voi käyttää avainsanaa ''mut'', esimerkiksi \mintinline{Rust}{let mut name = "Marko"}. Myös lainaukset suoritetaan oletuksena muuttumattomasti ja muutettavan lainauksen voi tehdä samalla avainsanalla, esimerkiksi \mintinline{Rust}{say_hello(&mut name)}
Yleinen konsensus ohjelmoinnin maailmassa on se, että kaikki arvot mitä ei tarvitse muuttaa pitäisi nimenomaan määrittää muuttumattomana. Tämä on tuttua kaikille, jotka ovat tutustuneet funktionaalisiin ohjelmointikieliin. Muuttumaton data on myös todella tärkeää rinnakkaisajossa, missä useampi prosessi suorittaa samoja funktioita ja käsittelee samoja arvoja samaan aikaan.
Yleinen konsensus ohjelmoinnin maailmassa on se, että kaikki arvot, joita ei tarvitse muuttaa, pitäisi nimenomaan määrittää muuttumattomana. Tämä on tuttua kaikille, jotka ovat tutustuneet funktionaalisiin ohjelmointikieliin. Muuttumaton data on myös todella tärkeää rinnakkaisajossa, missä useampi prosessi suorittaa samoja funktioita ja käsittelee samoja arvoja samaan aikaan.
\subsection{Luotettavuus}
Rustia kehitettäessä on aina ollut tavoitteena luotettavuus. Tämä tarkoittaa sitä, että ohjelman virheet huomataan jo koontivaiheessa, eikä vasta suorituksen aikana ohjelman tietyssä tilassa. Tämän mahdollistavat edellä mainitut omistajuusmalli ja vahva tyypitys. Omistajuusmalli varmistaa sen, että ohjelmoija joutuu koodia kirjoittaessaan miettimään arvojen eliniän, joka tekee muistivuodoista harvinaisia. Vahva tyypitys varmistaa taas sen, että data on kaikkialla ohjelmassa yhteensopivaa.
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.
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.
\subsection{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.
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 \code{Cargo.toml} tiedostossa. Tässä esimerkki tämän opinnäytetyön yhteydessä tehdyn projektin palvelinpuolen \code{Cargo.toml}:
Laatikon tiedot ja riippuvuudet määritetään Cargo.toml tiedostossa.
\begin{minted}{TOML}
[package]
name = "thesis-backend"
version = "0.1.0"
authors = ["Marko Korhonen <marko.korhonen@reekynet.com>"]
edition = "2018"
\begin{code}
\inputminted{TOML}{code/Cargo.toml}
\captionof{listing}{Projektin palvelinpuolen Cargo.toml}
\label{code:rust:cargo-toml}
\end{code}
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "2.0.0"
actix-rt = "1.0.0"
serde = { version = "1.0.104", features = ["derive"] }
diesel = { version = "1.4.3", features = ["mysql", "r2d2", "chrono"] }
dotenv = "0.15.0"
bcrypt = "0.6.2"
env_logger = "0.7.1"
r2d2 = "0.8.8"
crypto = "0.0.2"
jsonwebtoken = "7.1.0"
chrono = { version = "0.4.11", features = ["serde"] }
actix-cors = "0.2.0"
actix-identity = "0.2.1"
futures = "0.3.4"
actix-files = "0.2.1"
\end{minted}
Cargoon on saatavilla myös useita liitännäisiä, esimerkiksi \code{cargo-watch}, joka suorittaa halutun toiminnon aina kun projektin sisällä tapahtuu muutoksia ja tässäkin projektissa käytetty \code{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 ja tässäkin projektissa käytetty cargo-web, joka helpottaa WebAssembly-ohjelmien kehittämistä.
\subsection{Dokumentaatio ja yhteisö}
Rust on tunnettu todella laajasta dokumentaatiostaan ja vahvasta yhteisöstään. Molemmista on paljon apua varsinkin aloittejioille.
@ -164,6 +124,21 @@ Rust on tunnettu todella laajasta dokumentaatiostaan ja vahvasta yhteisöstään
Aloitin itsekin opiskelemaan Rustia vain hieman ennen tämän opinnäytetyön alkua ja vahvasta yhteisöstä oli monesti apua.
\section{WebAssembly}
WebAssembly on kehitteillä oleva asiakaspuolen ohjelmointikieli. Sitä on suunniteltu JavaScriptin seuraajaksi ja sen suurinpana etuna JavaScriptiin verrattuna on huomattavasti matalemman tason esitysmuoto, minkä ansiosta se on suorituskykyisempi.
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 suoraan, vaan käyttää työkaluja, joilla olemassa olevia ohjelmointikieliä voi koota WebAssemblyksi. Rust on tästä hyvä esimerkki, sillä WebAssembly on yksi sen kääntäjän natiiveista ''targeteista'', samalla tavalla kuin vaikka x86-prosessorit.
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.
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ää S-lauseita, joten se muistuttaa syntaxiltaan hyvin paljon Lisp-ohjelmointikieltä.
\begin{code}
\inputminted{Lisp}{code/plus.wat}
\captionof{listing}{Lukujen yhteenlasku WebAssembly text -formaatissa}
\label{code:webassembly:plus}
\end{code}
\clearpage
\begin{code}
\inputminted{Rust}{code/plus.rs}
\captionof{listing}{Lukujen yhteenlasku Rustilla}
\label{code:rust:plus}
\end{code}

View file

@ -5,69 +5,81 @@ Tein opinnäytetyön yhteydessä fullstack-projektin, missä sekä palvelin- ett
\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.
Actix web on puolestaan rakennettu hyödyntämällä Actix frameworkiä, 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 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 (struct). Esimerkiksi näin:
\begin{minted}{rust}
struct RegisterUser {
username: String,
password: String,
admin: bool,
password_confirmation: String,
}
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).
#[post("/auth/register")]
fn register(
new_user: actix_web::web::Json<RegisterUser>,
) -> Result<HttpResponse, HttpResponse> {
register(new_user);
}
\end{minted}
\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. Jatkaakseni edellistä esimerkkiä, otetaan seuraava JSON-merkkijono:
\begin{minted}{JSON}
{
"username": "TestUser",
"password": "verysecurepassword",
"admin": 3,
}
\end{minted}
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.
Koska tässä JSON-objektissa ''admin'' ei ole tyyppiä boolean, kun se saapuu palvelimelle, Actix huomaa väärän tyypin ja vastaa statuskoodilla ''400 Bad Request''.
\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}.
\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ä.
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). Tässä esimerkkinä yhden tokenin sisältö tästä projektista:
\begin{minted}{JSON}
{
"sub": 5,
"name": "TestUser",
"admin": false,
"exp": 1588262665
}
\end{minted}
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}
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.
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 \code{"admin = false"} sanomaan \code{true}, palvelimella ajettava JWT validointifunktio, joka tietää oikean salasanan, näkee että tämä tunniste ei ole validi.
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.
Tokenin sisällön voi päättää kokonaan itse, vaikkakin joitakin standardeja kenttiä on määritetty, esimerkiksi: iss(issuer), sub(subject), 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.
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.
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.
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.
\subsection{Tietokanta}
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ää.
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 \code{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 \code{diesel migration run} ja taulun voi poistaa ja luoda uudelleen komennolla \code{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.
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.
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.
\section{Asiakaspuoli}
Projektin asiakaspuolen, eli selaimessa suoritettavan osan kehykseksi valitsin Yew:n. Yew\cite{yew:home} muistuttaa hyvin paljon JavaScript-maailmassa suosittua Reactia, eli sen on komponenttipohjainen. Tämä tarkoittaa sitä että kaikki ohjelman osien täytyy implementoida Yew:n Component-rajapintaa, jossa on funktiot create, change, update ja view. Näiden avulla funktioiden Yew pystyy orkestroimaan kullakin hetkellä näytettävien komponenttien tilaa.
\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.
Tämän projektin osan aloittaminen vaati aika paljon tutkimustyötä. Rust-koodi pitää kääntää WebAssemblyksi, johon on olemassa useita eri työkaluja. clearpage
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.
\clearpage
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.
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.
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.

View file

@ -1 +1,10 @@
\chapter{Tulokset}
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{illustration/login-process.pdf}
\caption{Kirjautumisprosessi projektissa}
\label{fig:login}
\end{figure}
Tuloksena syntyi yksinkertainen web-applikaatio, missä sekä asiakaspuoli että palvelinpuoli on toteutettu Rustilla.

24
tex/code/Cargo.toml Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "thesis-backend"
version = "0.1.0"
authors = ["Marko Korhonen <marko.korhonen@reekynet.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "2.0.0"
actix-rt = "1.0.0"
serde = { version = "1.0.104", features = ["derive"] }
diesel = { version = "1.4.3", features = ["mysql", "r2d2", "chrono"] }
dotenv = "0.15.0"
bcrypt = "0.6.2"
env_logger = "0.7.1"
r2d2 = "0.8.8"
crypto = "0.0.2"
jsonwebtoken = "7.1.0"
chrono = { version = "0.4.11", features = ["serde"] }
actix-cors = "0.2.0"
actix-identity = "0.2.1"
futures = "0.3.4"
actix-files = "0.2.1"

10
tex/code/borrow.rs Normal file
View file

@ -0,0 +1,10 @@
fn say_hello(name: &String) {
println!("Hello {}!", name);
}
fn main() {
let name = String::from("Marko");
say_hello(&name);
println!("{}", name);
}

View file

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HTML5 Document Template</title>
</head>
<body>
Some text here (visible to the users)
</body>
</html>

View file

@ -0,0 +1,9 @@
error: literal out of range for `u8`
--> types.rs:2:19
|
2 | let age: u8 = 256;
| ^^^
|
= note: `#[deny(overflowing_literals)]` on by default
error: aborting due to previous error

6
tex/code/jwt.json Normal file
View file

@ -0,0 +1,6 @@
{
"sub": 5,
"name": "TestUser",
"admin": false,
"exp": 1588262665
}

10
tex/code/ownership.rs Normal file
View file

@ -0,0 +1,10 @@
fn say_hello(name: String) {
println!("Hello {}!", name);
}
fn main() {
let name = String::from("Marko");
say_hello(name);
println!("{}", name);
}

3
tex/code/plus.rs Normal file
View file

@ -0,0 +1,3 @@
fn plus(num1: i32, num2: i32) -> i32 {
num1 + num2
}

7
tex/code/plus.wat Normal file
View file

@ -0,0 +1,7 @@
(module
(func $plus (param $num1 i32) (param $num2 i32) (result i32)
local.get $num1
local.get $num2
i32.add)
(export "plus" (func $plus))
)

View file

@ -0,0 +1,5 @@
{
"username": "TestUser",
"password": "verysecurepassword",
"admin": 3
}

11
tex/code/registeruser.rs Normal file
View file

@ -0,0 +1,11 @@
struct RegisterUser {
username: String,
password: String,
admin: bool,
password_confirmation: String,
}
#[post("/auth/register")]
fn register(new_user: actix_web::web::Json<RegisterUser>) -> Result<HttpResponse, HttpResponse> {
register(new_user);
}

View file

@ -0,0 +1,2 @@
let name = "Marko"; // string slice
let age = 26; // integer

View file

@ -0,0 +1,2 @@
let name: &str = "Marko";
let age: u8 = 26;

BIN
tex/illustration/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -3,8 +3,6 @@
\input{style/title.tex}
\input{chapters/0-abbr.tex}
\def\code#1{\texttt{#1}}
\begin{document}
\counterwithout{listing}{chapter}

View file

@ -8,7 +8,7 @@ xelatex -shell-escape -8bit main
makeglossaries main
find chapters | entr -cp xelatex -shell-escape -8bit main &
ls biblio.bib | entr -cp bibtex8 main &
#ls chapters/0abbr.tex | entr -cp makeglossaries main &
ls chapters/0abbr.tex | entr -cp makeglossaries main &
zathura >/dev/null 2>&1 main.pdf
trap finish EXIT