Browse Source

Merge branch 'axum-full' into axum-sqlx-minimal

Dragos Varovici 3 years ago
parent
commit
e705cdbe5f

+ 154 - 22
frameworks/Rust/axum/Cargo.lock

@@ -114,6 +114,33 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
+[[package]]
+name = "axum"
+version = "0.1.0"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum 0.3.0",
+ "bb8",
+ "bb8-postgres",
+ "dotenv",
+ "futures",
+ "futures-util",
+ "hyper",
+ "rand",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sqlx",
+ "tokio",
+ "tokio-pg-mapper",
+ "tokio-pg-mapper-derive",
+ "tokio-postgres",
+ "tower",
+ "tower-http",
+ "yarte",
+]
+
 [[package]]
 name = "axum"
 version = "0.3.0"
@@ -143,28 +170,6 @@ dependencies = [
  "tower-service",
 ]
 
-[[package]]
-name = "axum_techempower"
-version = "0.1.0"
-dependencies = [
- "async-stream",
- "async-trait",
- "axum",
- "dotenv",
- "futures",
- "futures-util",
- "hyper",
- "rand",
- "serde",
- "serde_derive",
- "serde_json",
- "sqlx",
- "tokio",
- "tower",
- "tower-http",
- "yarte",
-]
-
 [[package]]
 name = "base64"
 version = "0.13.0"
@@ -196,6 +201,31 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "bb8"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9f4fa9768efd269499d8fba693260cfc670891cf6de3adc935588447a77cc8"
+dependencies = [
+ "async-trait",
+ "futures-channel",
+ "futures-util",
+ "parking_lot",
+ "tokio",
+]
+
+[[package]]
+name = "bb8-postgres"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61fdf56d52b2cca401d2380407e5c35d3d25d3560224ecf74d6e4ca13e51239b"
+dependencies = [
+ "async-trait",
+ "bb8",
+ "tokio",
+ "tokio-postgres",
+]
+
 [[package]]
 name = "bincode"
 version = "1.3.3"
@@ -582,6 +612,12 @@ dependencies = [
  "version_check 0.9.3",
 ]
 
+[[package]]
+name = "fallible-iterator"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
+
 [[package]]
 name = "fancy-regex"
 version = "0.7.1"
@@ -1280,6 +1316,24 @@ dependencies = [
  "ucd-trie",
 ]
 
+[[package]]
+name = "phf"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
 [[package]]
 name = "pin-project"
 version = "1.0.8"
@@ -1332,6 +1386,35 @@ dependencies = [
  "xml-rs",
 ]
 
+[[package]]
+name = "postgres-protocol"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b145e6a4ed52cb316a27787fc20fe8a25221cb476479f61e4e0327c15b98d91a"
+dependencies = [
+ "base64",
+ "byteorder",
+ "bytes 1.1.0",
+ "fallible-iterator",
+ "hmac",
+ "md-5",
+ "memchr",
+ "rand",
+ "sha2",
+ "stringprep",
+]
+
+[[package]]
+name = "postgres-types"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04619f94ba0cc80999f4fc7073607cb825bc739a883cb6d20900fc5e009d6b0d"
+dependencies = [
+ "bytes 1.1.0",
+ "fallible-iterator",
+ "postgres-protocol",
+]
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.15"
@@ -1648,6 +1731,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "siphasher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
+
 [[package]]
 name = "slab"
 version = "0.4.5"
@@ -1951,6 +2040,49 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-pg-mapper"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f2b78f3566383ffabc553c72bbb2f129962a54886c5c4d8e8c706f84eceab8"
+dependencies = [
+ "tokio-postgres",
+]
+
+[[package]]
+name = "tokio-pg-mapper-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8548f756cd6eb4069c5af0fb0cec57001fb42bd1fb7330d8f24067ee3fa62608"
+dependencies = [
+ "quote",
+ "syn",
+ "tokio-postgres",
+]
+
+[[package]]
+name = "tokio-postgres"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6c8b33df661b548dcd8f9bf87debb8c56c05657ed291122e1188698c2ece95"
+dependencies = [
+ "async-trait",
+ "byteorder",
+ "bytes 1.1.0",
+ "fallible-iterator",
+ "futures",
+ "log",
+ "parking_lot",
+ "percent-encoding",
+ "phf",
+ "pin-project-lite",
+ "postgres-protocol",
+ "postgres-types",
+ "socket2",
+ "tokio",
+ "tokio-util",
+]
+
 [[package]]
 name = "tokio-stream"
 version = "0.1.8"

+ 22 - 4
frameworks/Rust/axum/Cargo.toml

@@ -1,9 +1,21 @@
 [package]
-name = "axum_techempower"
+name = "axum"
 version = "0.1.0"
 authors = ["Dragos Varovici <[email protected]>"]
 edition = "2018"
 
+[[bin]]
+name = "axum"
+path = "src/main.rs"
+
+[[bin]]
+name = "axum-sqlx"
+path = "src/main_sqlx.rs"
+
+[[bin]]
+name = "axum-bb8"
+path = "src/main_bb8.rs"
+
 [dependencies]
 rand = { version = "^0.8", features = ["small_rng"]}
 yarte = { version = "^0.15" }
@@ -12,15 +24,21 @@ async-trait = { version = "0.1" }
 futures = { version = "^0.3" }
 futures-util = { version = "^0.3" }
 dotenv = { version = "^0.15" }
-
 serde = { version = "^1", features = ["derive"] }
 serde_json = { version = "^1" }
 serde_derive = { version = "^1" }
-
 axum = { version = "^0.3" }
 tokio = { version = "1.0", features = ["full"] }
 hyper = "0.14"
 tower = { version = "0.4", features = ["util"] }
 tower-http = { version = "0.1", features = ["set-header"] }
+sqlx = { version = "^0.5", features = [ "postgres", "macros", "runtime-tokio-native-tls" ] }
+bb8 = "0.7"
+bb8-postgres = "0.7"
+tokio-postgres = "0.7"
+tokio-pg-mapper = "0.2"
+tokio-pg-mapper-derive = "0.2"
 
-sqlx = { version = "^0.5", features = [ "postgres", "macros", "runtime-tokio-native-tls" ] }
+[profile.release]
+lto = true
+codegen-units = 1

+ 20 - 0
frameworks/Rust/axum/axum-bb8.dockerfile

@@ -0,0 +1,20 @@
+FROM rust:1.55-slim-buster
+
+ENV AXUM_TECHEMPOWER_DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+    libpq-dev pkg-config libssl-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /axum
+COPY ./src ./src
+COPY ./templates ./templates
+COPY ./Cargo.toml ./Cargo.toml
+COPY ./Cargo.lock ./Cargo.lock
+
+ENV RUSTFLAGS "-C target-cpu=native"
+RUN cargo build --release
+
+EXPOSE 8000
+
+CMD ["./target/release/axum-bb8"]

+ 20 - 0
frameworks/Rust/axum/axum-sqlx.dockerfile

@@ -0,0 +1,20 @@
+FROM rust:1.55-slim-buster
+
+ENV AXUM_TECHEMPOWER_DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+    libpq-dev pkg-config libssl-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /axum
+COPY ./src ./src
+COPY ./templates ./templates
+COPY ./Cargo.toml ./Cargo.toml
+COPY ./Cargo.lock ./Cargo.lock
+
+ENV RUSTFLAGS "-C target-cpu=native"
+RUN cargo build --release
+
+EXPOSE 8000
+
+CMD ["./target/release/axum-sqlx"]

+ 1 - 6
frameworks/Rust/axum/axum.dockerfile

@@ -1,8 +1,5 @@
 FROM rust:1.55-slim-buster
 
-ENV AXUM_TECHEMPOWER_DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
-ENV RUST_LOG=axum_techempower=debug
-
 RUN apt-get update && apt-get install -y --no-install-recommends \
     libpq-dev pkg-config libssl-dev \
     && rm -rf /var/lib/apt/lists/*
@@ -16,8 +13,6 @@ COPY ./Cargo.lock ./Cargo.lock
 ENV RUSTFLAGS "-C target-cpu=native"
 RUN cargo build --release
 
-RUN ls -la target/release/axum_techempower
-
 EXPOSE 8000
 
-CMD ["./target/release/axum_techempower"]
+CMD ["./target/release/axum"]

+ 46 - 0
frameworks/Rust/axum/benchmark_config.json

@@ -24,6 +24,52 @@
         "display_name": "Axum",
         "notes": "",
         "versus": "None"
+      },
+      "sqlx": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8000,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "postgres",
+        "framework": "Axum",
+        "language": "Rust",
+        "flavor": "None",
+        "orm": "Full",
+        "platform": "Rust",
+        "webserver": "Hyper",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Axum [sqlx]",
+        "notes": "",
+        "versus": "None"
+      },
+      "bb8": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
+        "port": 8000,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "postgres",
+        "framework": "Axum",
+        "language": "Rust",
+        "flavor": "None",
+        "orm": "Full",
+        "platform": "Rust",
+        "webserver": "Hyper",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Axum [bb8]",
+        "notes": "",
+        "versus": "None"
       }
     }
   ]

+ 2 - 0
frameworks/Rust/axum/rustfmt.toml

@@ -0,0 +1,2 @@
+max_width = 89
+reorder_imports = true

+ 17 - 0
frameworks/Rust/axum/src/common_handlers.rs

@@ -0,0 +1,17 @@
+use axum::http::StatusCode;
+use axum::Json;
+use axum::response::IntoResponse;
+
+use crate::models_common::{Message};
+
+pub async fn plaintext() -> &'static str {
+    "Hello, World!"
+}
+
+pub async fn json() -> impl IntoResponse {
+    let message = Message {
+        message: "Hello, World!",
+    };
+
+    (StatusCode::OK, Json(message))
+}

+ 36 - 0
frameworks/Rust/axum/src/database_bb8.rs

@@ -0,0 +1,36 @@
+use axum::extract::{Extension, FromRequest, RequestParts};
+use axum::http::StatusCode;
+use bb8::{Pool, PooledConnection};
+use bb8_postgres::PostgresConnectionManager;
+use bb8_postgres::tokio_postgres::NoTls;
+use crate::utils::internal_error;
+
+pub type ConnectionManager = PostgresConnectionManager<NoTls>;
+pub type ConnectionPool = Pool<ConnectionManager>;
+pub type Connection = PooledConnection<'static, ConnectionManager>;
+
+pub async fn create_bb8_pool(database_url: String) -> ConnectionPool {
+    let manager = PostgresConnectionManager::new_from_stringlike(database_url, NoTls).unwrap();
+
+    Pool::builder().build(manager).await.unwrap()
+}
+
+pub struct DatabaseConnection(pub Connection);
+
+#[async_trait]
+impl<B> FromRequest<B> for DatabaseConnection
+    where
+        B: Send,
+{
+    type Rejection = (StatusCode, String);
+
+    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
+        let Extension(pool) = Extension::<ConnectionPool>::from_request(req)
+            .await
+            .map_err(internal_error)?;
+
+        let conn = pool.get_owned().await.map_err(internal_error)?;
+
+        Ok(Self(conn))
+    }
+}

+ 1 - 8
frameworks/Rust/axum/src/database.rs → frameworks/Rust/axum/src/database_sqlx.rs

@@ -4,6 +4,7 @@ use axum::http::StatusCode;
 use sqlx::{PgPool, Postgres};
 use sqlx::pool::PoolConnection;
 use sqlx::postgres::PgPoolOptions;
+use crate::utils::internal_error;
 
 pub async fn create_pool(database_url: String) -> PgPool {
     PgPoolOptions::new().max_connections(100).connect(&*database_url).await.unwrap()
@@ -29,11 +30,3 @@ impl<B> FromRequest<B> for DatabaseConnection
     }
 }
 
-/// Utility function for mapping any error into a `500 Internal Server Error`
-/// response.
-pub fn internal_error<E>(err: E) -> (StatusCode, String)
-    where
-        E: std::error::Error,
-{
-    (StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
-}

+ 9 - 186
frameworks/Rust/axum/src/main.rs

@@ -1,176 +1,28 @@
 extern crate serde_derive;
 extern crate dotenv;
-#[macro_use]
 extern crate async_trait;
+extern crate tokio_pg_mapper_derive;
+extern crate tokio_pg_mapper;
 
-mod models;
-mod database;
+mod common_handlers;
+mod models_common;
 
-use std::convert::Infallible;
 use dotenv::dotenv;
 use std::net::{Ipv4Addr, SocketAddr};
-use std::env;
-use yarte::Template;
-use crate::database::{DatabaseConnection};
-use axum::{
-    extract::{Query},
-    http::StatusCode,
-    response::IntoResponse,
-    routing::get,
-    AddExtensionLayer, Json, Router,
-};
-use axum::body::{Bytes, Full};
-use axum::http::{header, HeaderValue, Response};
-use serde::{Deserialize};
+use axum::{Router, routing::get};
+use axum::http::{header, HeaderValue};
 use tower_http::set_header::SetResponseHeaderLayer;
 use hyper::Body;
-use rand::rngs::SmallRng;
-use rand::{Rng, SeedableRng};
-use sqlx::PgPool;
 
-use models::{World, Fortune, Message};
-use database::create_pool;
-
-#[derive(Debug, Deserialize)]
-struct Params {
-    queries: Option<String>,
-}
-
-async fn plaintext() -> &'static str {
-    "Hello, World!"
-}
-
-async fn json() -> impl IntoResponse {
-    let message = Message {
-        message: "Hello, World!",
-    };
-
-    (StatusCode::OK, Json(message))
-}
-
-async fn db(DatabaseConnection(mut conn): DatabaseConnection) -> impl IntoResponse {
-    let mut rng = SmallRng::from_entropy();
-    let number = random_number(&mut rng);
-
-    let world : World = sqlx::query_as("SELECT id, randomnumber FROM World WHERE id = $1").bind(number)
-        .fetch_one(&mut conn).await.ok().expect("error loading world");
-
-    (StatusCode::OK, Json(world))
-}
-
-async fn queries(DatabaseConnection(mut conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
-    let q = parse_params(params);
-
-    let mut rng = SmallRng::from_entropy();
-
-    let mut results = Vec::with_capacity(q as usize);
-
-    for _ in 0..q {
-        let query_id = random_number(&mut rng);
-
-        let result :World =  sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
-            .fetch_one(&mut conn).await.ok().expect("error loading world");
-
-        results.push(result);
-    }
-
-    (StatusCode::OK, Json(results))
-}
-
-#[derive(Template)]
-#[template(path = "fortunes.html.hbs")]
-pub struct FortunesTemplate<'a> {
-    pub fortunes: &'a Vec<Fortune>,
-}
-
-async fn fortunes(DatabaseConnection(mut conn): DatabaseConnection) -> impl IntoResponse {
-    let mut fortunes: Vec<Fortune> = sqlx::query_as("SELECT * FROM Fortune").fetch_all(&mut conn).await
-        .ok().expect("Could not load Fortunes");
-
-    fortunes.push(Fortune {
-        id: 0,
-        message: "Additional fortune added at request time.".to_string(),
-    });
-
-    fortunes.sort_by(|a, b| a.message.cmp(&b.message));
-
-    Utf8Html(
-        FortunesTemplate {
-            fortunes: &fortunes,
-        }
-        .call()
-        .expect("error rendering template"),
-    )
-}
-
-async fn updates(DatabaseConnection(mut conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
-    let q = parse_params(params);
-
-    let mut rng = SmallRng::from_entropy();
-
-    let mut results = Vec::with_capacity(q as usize);
-
-    for _ in 0..q {
-        let query_id = random_number(&mut rng);
-        let mut result :World =  sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
-            .fetch_one(&mut conn).await.ok().expect("error loading world");
-
-        result.random_number = random_number(&mut rng);
-        results.push(result);
-    }
-
-    for w in &results {
-        sqlx::query("UPDATE World SET randomnumber = $1 WHERE id = $2")
-            .bind(w.random_number).bind(w.id)
-            .execute(&mut conn)
-            .await.ok().expect("could not update world");
-    }
-
-    (StatusCode::OK, Json(results))
-}
-
-fn random_number(rng: &mut SmallRng) -> i32 {
-    (rng.gen::<u32>() % 10_000 + 1) as i32
-}
-
-fn parse_params(params: Params) -> i32 {
-    let mut q = 0;
-
-    if params.queries.is_some() {
-        let queries = params.queries.ok_or("could not get value").unwrap();
-
-        let queries_as_int = queries.parse::<i32>();
-
-        match queries_as_int {
-            Ok(_ok) => q = queries_as_int.unwrap(),
-            Err(_e) => q = 1,
-        }
-    }
-
-    let q = if q == 0 {
-        1
-    } else if q > 500 {
-        500
-    } else {
-        q
-    };
-
-    q
-}
+use common_handlers::{json, plaintext};
 
 #[tokio::main]
 async fn main() {
     dotenv().ok();
 
-    let database_url = env::var("AXUM_TECHEMPOWER_DATABASE_URL").ok()
-        .expect("AXUM_TECHEMPOWER_DATABASE_URL environment variable was not set");
-
     let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, 8000));
 
-    // setup connection pool
-    let pool = create_pool(database_url).await;
-
-    let app = router(pool).await;
+    let app = router().await;
 
     axum::Server::bind(&addr)
         .serve(app.into_make_service())
@@ -178,38 +30,9 @@ async fn main() {
         .unwrap();
 }
 
-async fn router(pool: PgPool) -> Router {
+async fn router() -> Router {
     Router::new()
         .route("/plaintext", get(plaintext))
         .route("/json", get(json))
-        .route("/fortunes", get(fortunes))
-        .route("/db", get(db))
-        .route("/queries", get(queries))
-        .route("/updates", get(updates))
-        .layer(AddExtensionLayer::new(pool))
         .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")))
 }
-
-#[derive(Clone, Copy, Debug)]
-pub struct Utf8Html<T>(pub T);
-
-impl<T> IntoResponse for Utf8Html<T>
-    where
-        T: Into<Full<Bytes>>,
-{
-    type Body = Full<Bytes>;
-    type BodyError = Infallible;
-
-    fn into_response(self) -> Response<Self::Body> {
-        let mut res = Response::new(self.0.into());
-        res.headers_mut()
-            .insert(header::CONTENT_TYPE, HeaderValue::from_static("text/html; charset=utf-8"));
-        res
-    }
-}
-
-impl<T> From<T> for Utf8Html<T> {
-    fn from(inner: T) -> Self {
-        Self(inner)
-    }
-}

+ 170 - 0
frameworks/Rust/axum/src/main_bb8.rs

@@ -0,0 +1,170 @@
+extern crate serde_derive;
+extern crate dotenv;
+#[macro_use]
+extern crate async_trait;
+
+mod common_handlers;
+mod models_common;
+mod models_bb8;
+mod database_bb8;
+mod utils;
+
+use dotenv::dotenv;
+use std::net::{Ipv4Addr, SocketAddr};
+use std::env;
+use crate::database_bb8::{Connection, create_bb8_pool, DatabaseConnection};
+use axum::{
+    extract::{Query},
+    http::StatusCode,
+    response::IntoResponse,
+    routing::get,
+    AddExtensionLayer, Json, Router,
+};
+use axum::http::{header, HeaderValue};
+use bb8_postgres::tokio_postgres::{Row, Statement};
+use tower_http::set_header::SetResponseHeaderLayer;
+use hyper::Body;
+use rand::rngs::SmallRng;
+use rand::{SeedableRng};
+use tokio_pg_mapper::FromTokioPostgresRow;
+use yarte::Template;
+
+use models_bb8::{World, Fortune};
+use common_handlers::{json, plaintext};
+use utils::{Params, parse_params, random_number};
+use crate::utils::Utf8Html;
+
+async fn db(DatabaseConnection(conn): DatabaseConnection) -> impl IntoResponse {
+    let mut rng = SmallRng::from_entropy();
+    let number = random_number(&mut rng);
+
+    let select = prepare_fetch_world_by_id_statement(&conn).await;
+    let world = fetch_world_by_id_using_statement(&conn, number, &select).await;
+
+    (StatusCode::OK, Json(world))
+}
+
+async fn fetch_world_by_id_using_statement(conn: &Connection, number: i32, select: &Statement) -> World {
+    let row: Row = conn.query_one(select, &[&number]).await.unwrap();
+
+    World::from_row(row).unwrap()
+}
+
+async fn queries(DatabaseConnection(conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
+    let q = parse_params(params);
+
+    let mut rng = SmallRng::from_entropy();
+
+    let mut results = Vec::with_capacity(q as usize);
+
+    let select = prepare_fetch_world_by_id_statement(&conn).await;
+
+    for _ in 0..q {
+        let query_id = random_number(&mut rng);
+
+        let result :World = fetch_world_by_id_using_statement(&conn, query_id, &select).await;
+
+        results.push(result);
+    }
+
+    (StatusCode::OK, Json(results))
+}
+
+async fn fortunes(DatabaseConnection(conn): DatabaseConnection) -> impl IntoResponse {
+    let select = prepare_fetch_all_fortunes_statement(&conn).await;
+
+    let rows: Vec<Row> = conn.query(&select, &[]).await.unwrap();
+
+    let mut fortunes: Vec<Fortune> = Vec::with_capacity(rows.capacity());
+
+    for row in rows {
+        fortunes.push(Fortune::from_row(row).unwrap());
+    }
+
+    fortunes.push(Fortune {
+        id: 0,
+        message: "Additional fortune added at request time.".to_string(),
+    });
+
+    fortunes.sort_by(|a, b| a.message.cmp(&b.message));
+
+    Utf8Html(
+        FortunesTemplate {
+            fortunes: &fortunes,
+        }
+        .call()
+        .expect("error rendering template"),
+    )
+}
+
+async fn updates(DatabaseConnection(conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
+    let q = parse_params(params);
+
+    let mut rng = SmallRng::from_entropy();
+
+    let mut results = Vec::with_capacity(q as usize);
+
+    let select = prepare_fetch_world_by_id_statement(&conn).await;
+
+    for _ in 0..q {
+        let query_id = random_number(&mut rng);
+        let mut result :World = fetch_world_by_id_using_statement(&conn, query_id, &select).await;
+
+        result.randomnumber = random_number(&mut rng);
+        results.push(result);
+    }
+
+    let update = prepare_update_world_by_id_statement(&conn).await;
+
+    for w in &results {
+        conn.execute(&update, &[&w.randomnumber, &w.id]).await.unwrap();
+    }
+
+    (StatusCode::OK, Json(results))
+}
+
+async fn prepare_fetch_all_fortunes_statement(conn: &Connection) -> Statement {
+    conn.prepare("SELECT * FROM Fortune").await.unwrap()
+}
+
+async fn prepare_fetch_world_by_id_statement(conn: &Connection) -> Statement {
+    conn.prepare("SELECT id, randomnumber FROM World WHERE id = $1").await.unwrap()
+}
+
+async fn prepare_update_world_by_id_statement(conn: &Connection) -> Statement {
+    conn.prepare("UPDATE World SET randomnumber = $1 WHERE id = $2").await.unwrap()
+}
+
+#[tokio::main]
+async fn main() {
+    dotenv().ok();
+
+    let database_url = env::var("AXUM_TECHEMPOWER_DATABASE_URL").ok()
+        .expect("AXUM_TECHEMPOWER_DATABASE_URL environment variable was not set");
+
+    let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, 8000));
+
+    // setup connection pool
+    let pool = create_bb8_pool(database_url).await;
+
+    let router = Router::new()
+        .route("/plaintext", get(plaintext))
+        .route("/json", get(json))
+        .route("/fortunes", get(fortunes))
+        .route("/db", get(db))
+        .route("/queries", get(queries))
+        .route("/updates", get(updates))
+        .layer(AddExtensionLayer::new(pool))
+        .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")));
+
+    axum::Server::bind(&addr)
+        .serve(router.into_make_service())
+        .await
+        .unwrap();
+}
+
+#[derive(Template)]
+#[template(path = "fortunes.html.hbs")]
+pub struct FortunesTemplate<'a> {
+    pub fortunes: &'a Vec<Fortune>,
+}

+ 147 - 0
frameworks/Rust/axum/src/main_sqlx.rs

@@ -0,0 +1,147 @@
+extern crate serde_derive;
+extern crate dotenv;
+#[macro_use]
+extern crate async_trait;
+
+mod common_handlers;
+mod models_common;
+mod models_sqlx;
+mod database_sqlx;
+mod utils;
+
+use dotenv::dotenv;
+use std::net::{Ipv4Addr, SocketAddr};
+use std::env;
+use crate::database_sqlx::{DatabaseConnection};
+use axum::{
+    extract::{Query},
+    http::StatusCode,
+    response::IntoResponse,
+    routing::get,
+    AddExtensionLayer, Json, Router,
+};
+use axum::http::{header, HeaderValue};
+use tower_http::set_header::SetResponseHeaderLayer;
+use hyper::Body;
+use rand::rngs::SmallRng;
+use rand::{SeedableRng};
+use sqlx::PgPool;
+use yarte::Template;
+
+use models_sqlx::{World, Fortune};
+use database_sqlx::create_pool;
+use common_handlers::{json, plaintext};
+use utils::{Params, parse_params, random_number, Utf8Html};
+
+async fn db(DatabaseConnection(mut conn): DatabaseConnection) -> impl IntoResponse {
+    let mut rng = SmallRng::from_entropy();
+    let number = random_number(&mut rng);
+
+    let world : World = sqlx::query_as("SELECT id, randomnumber FROM World WHERE id = $1").bind(number)
+        .fetch_one(&mut conn).await.ok().expect("error loading world");
+
+    (StatusCode::OK, Json(world))
+}
+
+async fn queries(DatabaseConnection(mut conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
+    let q = parse_params(params);
+
+    let mut rng = SmallRng::from_entropy();
+
+    let mut results = Vec::with_capacity(q as usize);
+
+    for _ in 0..q {
+        let query_id = random_number(&mut rng);
+
+        let result :World =  sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
+            .fetch_one(&mut conn).await.ok().expect("error loading world");
+
+        results.push(result);
+    }
+
+    (StatusCode::OK, Json(results))
+}
+
+async fn fortunes(DatabaseConnection(mut conn): DatabaseConnection) -> impl IntoResponse {
+    let mut fortunes: Vec<Fortune> = sqlx::query_as("SELECT * FROM Fortune").fetch_all(&mut conn).await
+        .ok().expect("Could not load Fortunes");
+
+    fortunes.push(Fortune {
+        id: 0,
+        message: "Additional fortune added at request time.".to_string(),
+    });
+
+    fortunes.sort_by(|a, b| a.message.cmp(&b.message));
+
+    Utf8Html(
+        FortunesTemplate {
+            fortunes: &fortunes,
+        }
+        .call()
+        .expect("error rendering template"),
+    )
+}
+
+async fn updates(DatabaseConnection(mut conn): DatabaseConnection, Query(params): Query<Params>) -> impl IntoResponse {
+    let q = parse_params(params);
+
+    let mut rng = SmallRng::from_entropy();
+
+    let mut results = Vec::with_capacity(q as usize);
+
+    for _ in 0..q {
+        let query_id = random_number(&mut rng);
+        let mut result :World =  sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
+            .fetch_one(&mut conn).await.ok().expect("error loading world");
+
+        result.random_number = random_number(&mut rng);
+        results.push(result);
+    }
+
+    for w in &results {
+        sqlx::query("UPDATE World SET randomnumber = $1 WHERE id = $2")
+            .bind(w.random_number).bind(w.id)
+            .execute(&mut conn)
+            .await.ok().expect("could not update world");
+    }
+
+    (StatusCode::OK, Json(results))
+}
+
+#[tokio::main]
+async fn main() {
+    dotenv().ok();
+
+    let database_url = env::var("AXUM_TECHEMPOWER_DATABASE_URL").ok()
+        .expect("AXUM_TECHEMPOWER_DATABASE_URL environment variable was not set");
+
+    let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, 8000));
+
+    // setup connection pool
+    let pool = create_pool(database_url).await;
+
+    let app = router(pool).await;
+
+    axum::Server::bind(&addr)
+        .serve(app.into_make_service())
+        .await
+        .unwrap();
+}
+
+async fn router(pool: PgPool) -> Router {
+    Router::new()
+        .route("/plaintext", get(plaintext))
+        .route("/json", get(json))
+        .route("/fortunes", get(fortunes))
+        .route("/db", get(db))
+        .route("/queries", get(queries))
+        .route("/updates", get(updates))
+        .layer(AddExtensionLayer::new(pool))
+        .layer(SetResponseHeaderLayer::<_, Body>::if_not_present(header::SERVER, HeaderValue::from_static("Axum")))
+}
+
+#[derive(Template)]
+#[template(path = "fortunes.html.hbs")]
+pub struct FortunesTemplate<'a> {
+    pub fortunes: &'a Vec<Fortune>,
+}

+ 22 - 0
frameworks/Rust/axum/src/models_bb8.rs

@@ -0,0 +1,22 @@
+use serde::{Deserialize, Serialize};
+use tokio_pg_mapper_derive::PostgresMapper;
+
+#[allow(non_snake_case)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, PostgresMapper)]
+#[pg_mapper(table = "Fortune")]
+pub struct Fortune {
+    pub id: i32,
+    pub message: String
+}
+
+#[allow(non_snake_case)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, PostgresMapper)]
+#[pg_mapper(table = "World")]
+pub struct World {
+    pub id: i32,
+    #[serde(rename = "randomNumber")]
+    pub randomnumber: i32
+}
+
+
+

+ 6 - 0
frameworks/Rust/axum/src/models_common.rs

@@ -0,0 +1,6 @@
+use serde::{Serialize};
+
+#[derive(Serialize)]
+pub struct Message {
+    pub message: &'static str,
+}

+ 0 - 7
frameworks/Rust/axum/src/models.rs → frameworks/Rust/axum/src/models_sqlx.rs

@@ -1,19 +1,12 @@
 use serde::{Deserialize, Serialize};
 use sqlx::FromRow;
 
-#[derive(Serialize)]
-pub struct Message {
-    pub message: &'static str,
-}
-
-#[allow(non_snake_case)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
 pub struct Fortune {
     pub id: i32,
     pub message: String
 }
 
-#[allow(non_snake_case)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
 pub struct World {
     pub id: i32,

+ 74 - 0
frameworks/Rust/axum/src/utils.rs

@@ -0,0 +1,74 @@
+use std::convert::Infallible;
+use axum::body::{Bytes, Full};
+use axum::http::{header, HeaderValue, Response, StatusCode};
+use axum::response::IntoResponse;
+use rand::Rng;
+use rand::rngs::SmallRng;
+use serde::{Deserialize};
+
+#[derive(Debug, Deserialize)]
+pub struct Params {
+    queries: Option<String>,
+}
+
+pub fn random_number(rng: &mut SmallRng) -> i32 {
+    (rng.gen::<u32>() % 10_000 + 1) as i32
+}
+
+pub fn parse_params(params: Params) -> i32 {
+    let mut q = 0;
+
+    if params.queries.is_some() {
+        let queries = params.queries.ok_or("could not get value").unwrap();
+
+        let queries_as_int = queries.parse::<i32>();
+
+        match queries_as_int {
+            Ok(_ok) => q = queries_as_int.unwrap(),
+            Err(_e) => q = 1,
+        }
+    }
+
+    let q = if q == 0 {
+        1
+    } else if q > 500 {
+        500
+    } else {
+        q
+    };
+
+    q
+}
+
+/// Utility function for mapping any error into a `500 Internal Server Error`
+/// response.
+pub fn internal_error<E>(err: E) -> (StatusCode, String)
+    where
+        E: std::error::Error,
+{
+    (StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct Utf8Html<T>(pub T);
+
+impl<T> IntoResponse for Utf8Html<T>
+    where
+        T: Into<Full<Bytes>>,
+{
+    type Body = Full<Bytes>;
+    type BodyError = Infallible;
+
+    fn into_response(self) -> Response<Self::Body> {
+        let mut res = Response::new(self.0.into());
+        res.headers_mut()
+            .insert(header::CONTENT_TYPE, HeaderValue::from_static("text/html; charset=utf-8"));
+        res
+    }
+}
+
+impl<T> From<T> for Utf8Html<T> {
+    fn from(inner: T) -> Self {
+        Self(inner)
+    }
+}