presentation-rust-webapps/rust_web.org

577 lines
15 KiB
Org Mode
Raw Normal View History

#+OPTIONS: ':nil *:t -:t ::t <:t H:3 \n:nil ^:t arch:headline
#+OPTIONS: author:t broken-links:nil c:nil creator:nil
#+OPTIONS: d:(not "LOGBOOK") date:t e:t email:nil f:t inline:t num:t
#+OPTIONS: p:nil pri:nil prop:nil stat:t tags:t tasks:t tex:t
#+OPTIONS: timestamp:t title:t toc:t todo:t |:t
2020-12-21 14:09:32 +01:00
#+TITLE: Rust, Rocket and Diesel
#+SUBTITLE: Web apps with rust
#+DESCRIPTION: An introduction to Rust, Rocket and Diesel.
#+KEYWORDS: rust rocket diesel
2020-12-21 11:04:02 +01:00
#+AUTHOR: \href{mailto:rocket-presentation@onders.org}{finga}
#+EMAIL: rocket-presentation@onders.org
#+LANGUAGE: en
#+SELECT_TAGS: export
#+EXCLUDE_TAGS: noexport
#+CREATOR: Emacs 26.1 (Org mode 9.1.9)
#+OPTIONS: H:2
#+LATEX_CLASS: beamer
#+LATEX_CLASS_OPTIONS: [aspectration=1610]
2020-12-21 11:04:02 +01:00
# #+LATEX_HEADER: \hypersetup{colorlinks=true,linkcolor=gray}
#+LATEX_HEADER: \hypersetup{colorlinks=true,linkcolor=black,urlcolor=gray}
#+COLUMNS: %45ITEM %10BEAMER_env(Env) %10BEAMER_act(Act) %4BEAMER_col(Col) %8BEAMER_opt(Opt)
#+BEAMER_THEME: Frankfurt
#+BEAMER_COLOR_THEME: seagull
#+BEAMER_FONT_THEME:
#+BEAMER_INNER_THEME:
#+BEAMER_OUTER_THEME:
#+BEAMER_HEADER: \institute[INST]{\href{https://cccsbg.at}{cccsbg}}
* Rust
** What is Rust?
2020-12-21 11:04:02 +01:00
#+BEGIN_CENTER
#+LaTeX:\includegraphics[width = 0.65\textwidth]{img/Bruine_roest_op_tarwe_(Puccinia_recondita_f.sp._tritici_on_Triticum_aestivum).jpg}
#+END_CENTER
2020-12-21 14:09:32 +01:00
*** Rust, the language
2021-01-11 22:44:15 +01:00
- About 10 years young (2010)
2020-12-21 11:04:02 +01:00
- Memory safe without gcing, optional ref counting
- Ownership, lifetimes, traits
- Variables are immutable by default and can be shadowed
- Performance of idiomatic Rust is comparable to the performance
of idiomatic cpp
2020-12-21 15:11:43 +01:00
** Where to get further information?
2020-12-21 11:04:02 +01:00
2020-12-21 14:09:32 +01:00
*** Docs...
2021-01-11 22:44:15 +01:00
- [[https://doc.rust-lang.org/book/][The rust book]] (~$ rustup docs --book~)
- [[https://doc.rust-lang.org/rust-by-example/][Rust by example]] (~$ rustup docs --rust-by-example~)
- [[https://doc.rust-lang.org/std/][The Rust Standard Library]] (~$ rustup docs --std~)
- Use '~$ rustup help docs~' to get an overview
2020-12-21 11:04:02 +01:00
- [[https://cheats.rs][cheats.rs]]
2021-01-11 22:44:15 +01:00
- [[https://programming-idioms.org/cheatsheet/Rust][programming-idioms.org]]
2020-12-21 11:04:02 +01:00
- [[https://cargo.io][cargo.io]]/[[https://lib.rs][lib.rs]]
2020-12-21 14:09:32 +01:00
*** Chat (Discord)
- [[https://discord.gg/rust-lang][The Rust Programming Language]]
- [[https://discord.com/invite/tcbkpyQ][The Rust Programming Language Community Server]]
2020-12-21 11:04:02 +01:00
** What are we going to use?
*** Definitely
2020-12-21 15:11:43 +01:00
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- [[https://rocket.rs][rocket]]
- [[https://diesel.rs][diesel]]
2021-01-11 22:44:15 +01:00
- log/env_logger
2020-12-21 11:04:02 +01:00
*** Maybe
2020-12-21 15:11:43 +01:00
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- anyhow
- lettre
- sha3
- serde
- chrono
2020-12-21 11:04:02 +01:00
** How to install
*** Install Rust
#+BEGIN_SRC sh
$ curl --proto '=https' --tlsv1.2 -sSf \
https://sh.rustup.rs | sh
#+END_SRC
2020-12-21 14:09:32 +01:00
*** Install nightly Rust (needed for Rocket)
2020-12-21 11:04:02 +01:00
#+BEGIN_SRC sh
$ rustup toolchain install nightly
#+END_SRC
2020-12-21 14:09:32 +01:00
*** Install rustfmt (code formatter)
2020-12-21 11:04:02 +01:00
#+BEGIN_SRC sh
$ rustup component add rustfmt
#+END_SRC
2020-12-21 14:09:32 +01:00
*** Install clippy (linter and static code analysis)
2020-12-21 11:04:02 +01:00
#+BEGIN_SRC sh
$ rustup component add clippy
#+END_SRC
* Rocket
** What is Rocket?
2020-12-21 14:09:32 +01:00
#+BEGIN_QUOTE
Rocket is a web framework for Rust that makes it simple to write
fast, secure web applications without sacrificing flexibility,
usability, or type safety.
#+END_QUOTE
*** Features
**** Col left
:PROPERTIES:
:BEAMER_col: 0.45
:END:
- Type Safe
- Extensible
- Templating
- Cookies
**** Col right
:PROPERTIES:
:BEAMER_col: 0.45
:END:
- Streams
- Testing (Unit tests)
- Typed URIs
2020-12-21 15:11:43 +01:00
** Where to get further information?
2020-12-21 14:09:32 +01:00
*** Docs...
2020-12-21 15:11:43 +01:00
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- [[https://rocket.rs][rocket.rs]]
2020-12-21 14:09:32 +01:00
- [[https://rocket.rs/v0.4/guide/][Rocket Guide]]
- [[https://api.rocket.rs/v0.4/rocket/][Rocket API docs]]
- [[https://github.com/SergioBenitez/Rocket/tree/v0.4/examples][Examples]]
*** Chat
2020-12-21 15:11:43 +01:00
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
2020-12-21 14:09:32 +01:00
- Matrix: [[https://chat.mozilla.org/#/room/%23rocket:mozilla.org][#rocket:mozilla.org]]
- IRC on Freenode: [[https://kiwiirc.com/client/chat.freenode.net/#rocket][#rocket]]
2020-12-21 11:04:02 +01:00
* Diesel
** What is an ORM?
2020-12-21 15:11:43 +01:00
Object-relational-mapping is a programming technique for converting
data between incompatible type systems using the paradigms of your
programming language.
*** Pros
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- Models are DRY (don't repeat yourself)
- Cleaner separation
- Prepared and sanitised queries
- Changes are versioned
*** Cons
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- Debugging can get more complicated (performance)
- No need to learn SQL
** Prerequisites ~diesel_cli~
*** Setup Diesel for default DBMS
#+BEGIN_SRC sh
$ sudo apt install libpq-dev libsqlite3-dev \
default-libmysqlclient-dev
$ cargo install diesel_cli
#+END_SRC
*** Setup Diesel for SQLite only
#+BEGIN_SRC sh
$ sudo apt install libsqlite3-dev
$ cargo install diesel_cli -no-default-features \
--features sqlite
#+END_SRC
** How does that look in Rust
*** Define a struct
:PROPERTIES:
:BEAMER_col: 0.50
:BEAMER_env: block
:END:
#+BEGIN_SRC rust
#[derive(Queryable)]
pub struct NewComment<'a> {
name: &'a str,
email: &'a str,
comment: &'a str,
}
#+END_SRC
*** Insert something
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
#+BEGIN_SRC rust
let user = NewUser {
name: "foo",
email: "foo@bar.baz",
comment: "A comment",
};
insert_into(comments)
.values(&user)
.execute(conn);
#+END_SRC
2020-12-21 11:04:02 +01:00
2020-12-21 15:11:43 +01:00
** Where to get further information?
*** Docs...
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- [[https://diesel.rs][diesel.rs]]
- [[https://diesel.rs/guides/][Diesel Guides]]
- [[https://docs.diesel.rs/master/diesel/index.html][Diesel API docs]]
- [[https://github.com/diesel-rs/diesel/tree/master/examples][Examples]] (minimalistic)
*** Support
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- [[https://gitter.im/diesel-rs/diesel][Gitter]]
- [[https://discourse.diesel.rs/][Discourse Forum]]
2020-12-21 11:04:02 +01:00
* Lets code something
2020-12-21 15:11:43 +01:00
** Lets create a small web app
2021-01-11 22:44:15 +01:00
A mini text board where everybody can post and reply.
*** Caution
HTML is not disabled!
** Let us start
#+BEGIN_CENTER
#+LaTeX:\includegraphics[width = 0.7\textwidth]{img/challenge-accepted-lets-code-it.jpg}
#+END_CENTER
2020-12-21 15:11:43 +01:00
*** Steps to reproduce :noexport:
**** Create new crate
2021-01-12 17:55:25 +01:00
#+BEGIN_SRC sh
cargo init simple_text_board
cd simple_text_board
rustup override set nightly
cargo r
#+END_SRC
2021-01-11 23:10:26 +01:00
**** Setup Rocket
2021-01-12 17:55:25 +01:00
***** Add Rocket to ~Cargo.toml~
#+BEGIN_SRC toml
[dependencies]
rocket = "0.4"
#+END_SRC
***** Add ~Rocket.toml~
#+BEGIN_SRC toml
[development]
address = "0.0.0.0"
port = 8000
workers = 2
keep_alive = 5
log = "normal"
#+END_SRC
***** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program
#+BEGIN_SRC rust
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
fn main() {
rocket::ignite().mount("/", routes![index]).launch();
}
#+END_SRC
**** Create first templates
***** Create the base template at ~templates/base.html.tera~
#+BEGIN_SRC html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>simple text board</title>
</head>
<body>
<ul>
<li><a href="/">home</a></li>
<li><a href="/create">create post</a></li>
</ul>
{% block content %} {% endblock %}
</body>
</html>
#+END_SRC
***** Create the create template at ~templates/create.html.tera~
#+BEGIN_SRC html
{% extends "base" %}
{% block content %}
<h1>simple text board</h1>
<h2>create a post</h2>
<form action="/create" method="post" accept-charset="utf-8">
<label for="author">author name</label>
<input type="text" placeholder="author" name="author" required>
<label for="email">email</label>
<input type="text" placeholder="email" name="email">
<label for="title">title</label>
<input type="text" placeholder="title" name="title" required>
<label for="content">content</label>
<textarea type="text" placeholder="content" name="content" rows="8" cols="50" required>
</textarea>
<button type="submit">create</button>
{% if flash %}<p>{{ flash }}</p>{% endif %}
</form>
{% endblock content %}
#+END_SRC
***** Create the ~create_form~ function and add it to routing
#+BEGIN_SRC rust
#![feature(proc_macro_hygiene, decl_macro)]
use std::collections::HashMap;
use rocket::{get, request::FlashMessage, routes};
use rocket_contrib::templates::Template;
#[get("/create")]
fn create_form(flash: Option<FlashMessage>) -> Template {
let mut context = HashMap::new();
if let Some(ref msg) = flash {
context.insert("flash", msg.msg());
}
Template::render("create", &context)
}
...
fn main() {
rocket::ignite()
.mount("/", routes![index, create_form])
.attach(Template::fairing())
.launch();
}
#+END_SRC
**** Setup Database and posts
***** Create the ~.env~ file
#+BEGIN_SRC sh
DATABASE_URL=simple_text_board.sqlite
#+END_SRC
***** Add diesel to the dependencies and to ~rocket_contrib~ in ~Cargo.toml~
#+BEGIN_SRC toml
...
chrono = "0.4"
diesel = { version = "1.4", features = ["sqlite", "chrono"] }
log = "0.4"
...
[dependencies.rocket_contrib]
...
features = ["diesel_sqlite_pool", "tera_templates"]
#+END_SRC
***** Configure database for Rocket in ~Rocket.toml~
#+BEGIN_SRC toml
[global.databases]
sqlite = { url = "simple_text_board.sqlite" }
#+END_SRC
***** Create the first table schema
#+BEGIN_SRC sh
diesel setup
diesel migration generate create_posts
#+END_SRC
***** How to create the table
#+BEGIN_SRC sql
CREATE TABLE posts (
id INTEGER NOT NULL PRIMARY KEY,
parent INTEGER,
timestamp DATETIME NOT NULL,
author VARCHAR NOT NULL,
email VARCHAR NOT NULL,
title VARCHAR NOT NULL,
content VARCHAR NOT NULL,
FOREIGN KEY (parent) REFERENCES posts(id)
);
#+END_SRC
***** How to destroy it
#+BEGIN_SRC sql
DROP TABLE posts;
#+END_SRC
***** Applay and test rollback
#+BEGIN_SRC sh
diesel migration run
diesel migration redo
#+END_SRC
***** Create data model in ~src/models.rs~
#+BEGIN_SRC rust
use crate::schema::posts::{self, dsl::posts as table_posts};
use chrono::NaiveDateTime;
use diesel::{QueryResult, RunQueryDsl};
#[derive(Insertable)]
#[table_name = "posts"]
pub struct NewPost<'a> {
pub parent: Option<&'a i32>,
pub timestamp: NaiveDateTime,
pub author: &'a str,
pub email: &'a str,
pub title: &'a str,
pub content: &'a str,
}
impl NewPost<'_> {
pub fn insert(&self, conn: &diesel::SqliteConnection) -> QueryResult<usize> {
diesel::insert_into(table_posts).values(self).execute(conn)
}
}
#+END_SRC
***** Add create post functionality to ~src/main.rs~
#+BEGIN_SRC rust
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate diesel;
use std::{collections::HashMap, net::SocketAddr};
use chrono::Local;
use diesel::SqliteConnection;
use log::{error, info};
use rocket::{
get, post,
request::{FlashMessage, Form},
response::{Flash, Redirect},
routes, uri, FromForm,
};
use rocket_contrib::{database, templates::Template};
mod models;
mod schema;
use models::NewPost;
#[database("sqlite")]
struct DbCon(SqliteConnection);
#[derive(FromForm)]
pub struct PostForm {
author: String,
email: String,
title: String,
content: String,
}
...
#[post("/create", data = "<post>")]
fn create_post(
conn: DbCon,
remote_address: SocketAddr,
post: Form<PostForm>,
) -> Result<Redirect, Flash<Redirect>> {
let post = NewPost {
parent: None,
timestamp: Local::now().naive_local(),
author: &post.author.trim(),
email: &post.email.trim(),
title: &post.title.trim(),
content: &post.content.trim(),
};
match post.insert(&conn) {
Ok(_) => {
info!("New post from {}", remote_address);
Ok(Redirect::to(uri!(index)))
}
Err(e) => {
error!("Could not create post from {}: {:?}", remote_address, e);
Err(Flash::error(
Redirect::to(uri!(create_form)),
"Could not create post.".to_string(),
))
}
}
}
...
fn main() {
rocket::ignite()
.mount("/", routes![index, create_form, create_post])
.attach(DbCon::fairing())
.attach(Template::fairing())
.launch();
}
#+END_SRC
2021-01-11 23:10:26 +01:00
2020-12-21 11:04:02 +01:00
* Conclusion
** What did we do?
2021-01-11 22:44:15 +01:00
- Setup Rust and Diesel
- Create a project with Rocket and Diesel prerequisites
- Handle a SQLite DB with Diesel
- Create Tera templates
2020-12-21 11:04:02 +01:00
** What did you hopefully learn!
2020-12-21 14:09:32 +01:00
** Two small projects
*** [[https://git.onders.org/finga/ldap0r][ldap0r]]
2020-12-21 15:11:43 +01:00
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
2020-12-21 14:09:32 +01:00
- LDAP password reset tool
- ($\sim$ 300 SloC)
- First release
- Tests
*** [[https://git.onders.org/finga/filerly][filerly]]
2020-12-21 15:11:43 +01:00
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
2020-12-21 14:09:32 +01:00
- File sharing a la NextCloud
2021-01-11 22:44:15 +01:00
- Not yet ready, work in progress...
2020-12-21 11:04:02 +01:00
** The End
#+BEGIN_CENTER
#+LaTeX:\includegraphics[width = 0.3\textwidth]{img/goodbye.jpg}
\Huge
Thanks for your attention!!1!
This is the end..
#+END_CENTER