Browse Source

Rocket async using sqlx

Dragos Varovici 3 years ago
parent
commit
bf35d78d2a

+ 1 - 0
frameworks/Rust/rocket/.gitignore

@@ -0,0 +1 @@
+.env

+ 21 - 21
frameworks/Rust/rocket/Cargo.toml

@@ -1,26 +1,26 @@
 [package]
-name = "rocket"
-version = "0.1.0"
-authors = ["Marcelo Barbosa <[email protected]>", "Brendan Hansknecht <[email protected]>"]
+name = "rocket_benchmark"
+version = "0.2.0"
+authors = ["Marcelo Barbosa <[email protected]>", "Brendan Hansknecht <[email protected]>", "Dragos Varovici <[email protected]>"]
 edition = "2018"
 
 [dependencies]
-diesel = { version = "1.4.5", features = ["postgres", "r2d2"] }
-num_cpus = "1.13.0"
-rand = "0.7.3"
-rocket = "0.4.5"
-serde = "1.0.115"
-serde_json = "1.0.57"
-serde_derive = "1.0.115"
-yarte = "0.12.2"
-lazy_static = "1.4.0"
+num_cpus = { version = "^1.13" }
+rand = { version = "^0.8" }
+yarte = { version = "^0.15" }
+lazy_static = { version = "^1.4" }
+async-stream = { version = "^0.3" }
+async-trait = { version = "0.1" }
+futures = { version = "^0.3" }
+futures-util = { version = "^0.3" }
+rocket = { git = "https://github.com/SergioBenitez/Rocket", features = [
+    "json",
+] }
+sqlx = { version = "^0.5", features = [ "postgres", "macros", "migrate", "sqlite" ] }
+rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket", features = [ "sqlx_postgres" ] }
+figment = { version = "^0.10" }
+dotenv = { version = "^0.15" }
 
-[dependencies.rocket_contrib]
-version = "*"
-default-features = false
-features = ["json"]
-
-[profile.release]
-lto = true
-opt-level = 3
-codegen-units = 1
+serde = { version = "^1" }
+serde_json = { version = "^1" }
+serde_derive = { version = "^1" }

+ 1 - 1
frameworks/Rust/rocket/config.toml

@@ -13,7 +13,7 @@ classification = "Fullstack"
 database = "Postgres"
 database_os = "Linux"
 os = "Linux"
-orm = "Full"
+orm = "Raw"
 platform = "Rust"
 webserver = "Hyper"
 versus = "None"

+ 10 - 0
frameworks/Rust/rocket/db/migrations/20211027024424_create-world-table.sql

@@ -0,0 +1,10 @@
+CREATE TABLE World (
+    id INTEGER PRIMARY KEY,
+    randomnumber INTEGER NOT NULL
+);
+
+INSERT INTO World (id, randomnumber) VALUES (1, 101);
+INSERT INTO World (id, randomnumber) VALUES (2, 102);
+INSERT INTO World (id, randomnumber) VALUES (3, 103);
+INSERT INTO World (id, randomnumber) VALUES (4, 104);
+INSERT INTO World (id, randomnumber) VALUES (5, 105);

+ 17 - 0
frameworks/Rust/rocket/db/migrations/20211028024424_create-fortune-table.sql

@@ -0,0 +1,17 @@
+CREATE TABLE Fortune (
+    id INTEGER PRIMARY KEY,
+    message VARCHAR NOT NULL
+);
+
+INSERT INTO Fortune (id, message) VALUES (1, 'fortune: No such file or directory');
+INSERT INTO Fortune (id, message) VALUES (2, 'A computer scientist is someone who fixes things that aren''t broken.');
+INSERT INTO Fortune (id, message) VALUES (3, 'After enough decimal places, nobody gives a damn.');
+INSERT INTO Fortune (id, message) VALUES (4, 'A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1');
+INSERT INTO Fortune (id, message) VALUES (5, 'A computer program does what you tell it to do, not what you want it to do.');
+INSERT INTO Fortune (id, message) VALUES (6, 'Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen');
+INSERT INTO Fortune (id, message) VALUES (7, 'Any program that runs right is obsolete.');
+INSERT INTO Fortune (id, message) VALUES (8, 'A list is only as strong as its weakest link. — Donald Knuth');
+INSERT INTO Fortune (id, message) VALUES (9, 'Feature: A bug with seniority.');
+INSERT INTO Fortune (id, message) VALUES (10, 'Computers make very fast, very accurate mistakes.');
+INSERT INTO Fortune (id, message) VALUES (11, '<script>alert("This should not be displayed in a browser alert box.");</script>');
+INSERT INTO Fortune (id, message) VALUES (12, 'フレームワークのベンチマーク');

+ 5 - 5
frameworks/Rust/rocket/rocket.dockerfile

@@ -1,18 +1,18 @@
-FROM rust:1.46.0-slim-buster
+FROM rust:1.55-slim-buster
 
-ENV DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
+ENV ROCKET_BENCHMARK_DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
 
 RUN apt-get update && apt-get install -y --no-install-recommends \
     libpq-dev \
 && rm -rf /var/lib/apt/lists/*
 
-RUN rustup toolchain install nightly-2020-08-29 -t x86_64-unknown-linux-gnu --no-self-update --profile minimal
+RUN rustup toolchain install nightly-2021-09-15 -t x86_64-unknown-linux-gnu --no-self-update --profile minimal
 
 ADD ./ /rocket
 WORKDIR /rocket
 
-RUN RUSTFLAGS="-C target-cpu=native" cargo +nightly-2020-08-29 build --release
+RUN RUSTFLAGS="-C target-cpu=native" cargo +nightly-2021-09-15 build --release
 
 EXPOSE 8000
 
-CMD ./target/release/rocket
+CMD ./target/release/rocket_benchmark

+ 11 - 0
frameworks/Rust/rocket/src/database.rs

@@ -0,0 +1,11 @@
+use rocket_db_pools::{sqlx, Database};
+
+#[derive(Database)]
+#[database("hello_world")]
+#[cfg(not(test))]
+pub struct HelloWorld(sqlx::PgPool);
+
+#[derive(Database)]
+#[database("hello_world")]
+#[cfg(test)]
+pub struct HelloWorld(sqlx::SqlitePool);

+ 0 - 35
frameworks/Rust/rocket/src/db.rs

@@ -1,35 +0,0 @@
-use diesel::pg::PgConnection;
-use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
-use rocket::http::Status;
-use rocket::request::{self, FromRequest};
-use rocket::{Outcome, Request, State};
-use std::ops::Deref;
-
-type PgPool = Pool<ConnectionManager<PgConnection>>;
-
-pub struct DbConn(pub PooledConnection<ConnectionManager<PgConnection>>);
-
-impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
-    type Error = ();
-
-    fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
-        let pool = request.guard::<State<PgPool>>()?;
-        match pool.get() {
-            Ok(conn) => Outcome::Success(DbConn(conn)),
-            Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
-        }
-    }
-}
-
-impl Deref for DbConn {
-    type Target = PgConnection;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-pub fn init_pool() -> PgPool {
-    let manager = ConnectionManager::<PgConnection>::new(env!("DATABASE_URL"));
-    Pool::builder().max_size((num_cpus::get()*16) as u32).build(manager).expect("db pool")
-}

+ 193 - 120
frameworks/Rust/rocket/src/main.rs

@@ -1,103 +1,63 @@
 #![feature(proc_macro_hygiene, decl_macro)]
-
-extern crate rand;
-#[macro_use]
-extern crate rocket;
-extern crate rocket_contrib;
 #[macro_use]
-extern crate diesel;
+extern crate lazy_static;
 #[macro_use]
+extern crate rocket;
 extern crate serde_derive;
-extern crate lazy_static;
-
-use diesel::prelude::*;
-use diesel::result::Error;
-use lazy_static::lazy_static;
-use rand::seq::SliceRandom;
-use rand::thread_rng;
-use rocket::config::{Config, Environment, LoggingLevel};
-use rocket::response::content;
-use rocket_contrib::json::Json;
-use std::sync::Mutex;
-use yarte::Template;
+extern crate dotenv;
 
-mod db;
 mod models;
-mod schema;
-
-struct RandomArray {
-    pointer: usize,
-    size: i32,
-    data: Vec<i32>,
-}
-
-impl RandomArray {
-    fn new(size: i32) -> Self {
-        let mut data: Vec<i32> = (1..=size).collect();
-        let mut rng = thread_rng();
-        data.shuffle(&mut rng);
-
-        RandomArray {
-            pointer: 0,
-            size,
-            data,
-        }
-    }
-
-    fn next(&mut self) -> i32 {
-        if self.pointer >= self.size as usize {
-            self.pointer = 1;
-        } else {
-            self.pointer += 1;
-        }
-        self.data[self.pointer - 1]
-    }
-}
+mod random;
+mod database;
+mod request;
+
+use dotenv::dotenv;
+use std::net::{IpAddr, Ipv4Addr};
+use std::env;
+use rocket::{Rocket, Build};
+use rocket::serde::json::Json;
+use rocket::response::content::RawHtml;
+use rocket::config::{Config, LogLevel};
+use yarte::Template;
+use rocket_db_pools::{sqlx, Database, Connection};
+use sqlx::Acquire;
+use figment::Figment;
 
-lazy_static! {
-    static ref RANDOM_ARRAY: Mutex<RandomArray> = Mutex::new(RandomArray::new(10000));
-}
-fn random_number() -> i32 {
-    RANDOM_ARRAY
-        .lock()
-        .expect("Failed to lock RANDOM_ARRAY")
-        .next()
-}
+use models::{World, Fortune, Message};
+use database::HelloWorld;
+use random::random_number;
+use request::RequestId;
 
 #[get("/plaintext")]
-fn plaintext() -> &'static str {
+async fn plaintext() -> &'static str {
     "Hello, World!"
 }
 
 #[get("/json")]
-fn json() -> Json<models::Message> {
-    let message = models::Message {
+async fn json() -> Json<models::Message> {
+    let message = Message {
         message: "Hello, World!",
     };
     Json(message)
 }
 
 #[get("/db")]
-fn db(conn: db::DbConn) -> Json<models::World> {
-    use schema::world::dsl::*;
+async fn db(mut db: Connection<HelloWorld>, id: RequestId) -> Json<World> {
+    let number = random_number(&id);
 
-    let result = world
-        .filter(id.eq(random_number()))
-        .first::<models::World>(&*conn)
-        .expect("error loading world");
+    let result : World = sqlx::query_as("SELECT id, randomnumber FROM World WHERE id = $1").bind(number)
+        .fetch_one(&mut *db).await.ok().expect("error loading world");
 
     Json(result)
 }
 
 #[get("/queries")]
-fn queries_empty(conn: db::DbConn) -> Json<Vec<models::World>> {
-    queries(conn, 1)
+async fn queries_empty(db: Connection<HelloWorld>, id: RequestId) -> Json<Vec<World>> {
+    queries(db, id,1).await
 }
 
 #[get("/queries?<q>")]
-fn queries(conn: db::DbConn, q: u16) -> Json<Vec<models::World>> {
-    use schema::world::dsl::*;
-
+async fn queries(mut db: Connection<HelloWorld>, id: RequestId, q: u16) -> Json<Vec<World>> {
     let q = if q == 0 {
         1
     } else if q > 500 {
@@ -109,11 +69,11 @@ fn queries(conn: db::DbConn, q: u16) -> Json<Vec<models::World>> {
     let mut results = Vec::with_capacity(q as usize);
 
     for _ in 0..q {
-        let query_id = random_number();
-        let result = world
-            .filter(id.eq(query_id))
-            .first::<models::World>(&*conn)
-            .unwrap_or_else(|_| panic!("error loading world, id={}", query_id));
+        let query_id = random_number(&id);
+
+        let result :World = sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
+            .fetch_one(&mut *db).await.ok().expect("error loading world");
+
         results.push(result);
     }
 
@@ -123,25 +83,22 @@ fn queries(conn: db::DbConn, q: u16) -> Json<Vec<models::World>> {
 #[derive(Template)]
 #[template(path = "fortunes.html.hbs")]
 pub struct FortunesTemplate<'a> {
-    pub fortunes: &'a Vec<models::Fortune>,
+    pub fortunes: &'a Vec<Fortune>,
 }
 
 #[get("/fortunes")]
-fn fortunes(conn: db::DbConn) -> content::Html<String> {
-    use schema::fortune::dsl::*;
-
-    let mut fortunes = fortune
-        .load::<models::Fortune>(&*conn)
-        .expect("error loading fortunes");
+async fn fortunes(mut db: Connection<HelloWorld>) -> RawHtml<String> {
+    let mut fortunes: Vec<Fortune> = sqlx::query_as("SELECT * FROM Fortune").fetch_all(&mut *db).await
+        .ok().expect("Could not load Fortunes");
 
-    fortunes.push(models::Fortune {
+    fortunes.push(Fortune {
         id: 0,
         message: "Additional fortune added at request time.".to_string(),
     });
 
     fortunes.sort_by(|a, b| a.message.cmp(&b.message));
 
-    content::Html(
+    RawHtml(
         FortunesTemplate {
             fortunes: &fortunes,
         }
@@ -151,14 +108,12 @@ fn fortunes(conn: db::DbConn) -> content::Html<String> {
 }
 
 #[get("/updates")]
-fn updates_empty(conn: db::DbConn) -> Json<Vec<models::World>> {
-    updates(conn, 1)
+async fn updates_empty(db: Connection<HelloWorld>, id: RequestId) -> Json<Vec<World>> {
+    updates(db, id,1).await
 }
 
 #[get("/updates?<q>")]
-fn updates(conn: db::DbConn, q: u16) -> Json<Vec<models::World>> {
-    use schema::world::dsl::*;
-
+async fn updates(mut db: Connection<HelloWorld>, id: RequestId, q: u16) -> Json<Vec<World>> {
     let q = if q == 0 {
         1
     } else if q > 500 {
@@ -170,40 +125,57 @@ fn updates(conn: db::DbConn, q: u16) -> Json<Vec<models::World>> {
     let mut results = Vec::with_capacity(q as usize);
 
     for _ in 0..q {
-        let query_id = random_number();
-        let mut result = world
-            .filter(id.eq(query_id))
-            .first::<models::World>(&*conn)
-            .unwrap_or_else(|_| panic!("error loading world, id={}", query_id));
-        result.randomNumber = random_number();
+        let query_id = random_number(&id);
+        let mut result :World = sqlx::query_as("SELECT * FROM World WHERE id = $1").bind(query_id)
+            .fetch_one(&mut *db).await.ok().expect("World was not found");
+
+        result.random_number = random_number(&id);
         results.push(result);
     }
 
-    let _ = conn.transaction::<(), Error, _>(|| {
-        for w in &results {
-            let _ = diesel::update(world)
-                .filter(id.eq(w.id))
-                .set(randomnumber.eq(w.randomNumber))
-                .execute(&*conn);
-        }
-        Ok(())
-    });
+    let mut pool = db.into_inner();
+    let mut tx = pool.begin().await.ok().expect("could not start transaction");
+
+    for w in &results {
+        sqlx::query("UPDATE World SET randomnumber = $1 WHERE id = $2")
+            .bind(w.random_number).bind(w.id)
+            .execute(&mut tx)
+            .await.ok().expect("Could not update World");
+    }
+
+    tx.commit().await.ok().expect("could not update worlds");
 
     Json(results)
 }
 
-fn main() {
-    let mut config = Config::build(Environment::Production)
-        .address("0.0.0.0")
-        .port(8000)
-        .log_level(LoggingLevel::Off)
-        .workers((num_cpus::get() * 16) as u16)
-        .keep_alive(0)
-        .expect("failed to generate config");
-    config
-        .set_secret_key("dY+Rj2ybjGxKetLawKGSWi6EzESKejvENbQ3stffZg0=")
-        .expect("failed to set secret");
-    rocket::custom(config)
+#[launch]
+pub fn launch() -> Rocket<Build> {
+    if cfg!(not(test)) {
+        dotenv().ok();
+    }
+
+    let config = Config {
+        address: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
+        port: 8000,
+        keep_alive: 0,
+        log_level: LogLevel::Off,
+        workers: num_cpus::get() * 16,
+        ..Default::default()
+    };
+
+    let database_url = env::var("ROCKET_BENCHMARK_DATABASE_URL").ok()
+        .expect("ROCKET_BENCHMARK_DATABASE_URL environment variable was not set");
+
+    let figment = Figment::from(config)
+        .merge(("databases.hello_world", rocket_db_pools::Config {
+                url: database_url,
+                min_connections: None,
+                max_connections: 100,
+                connect_timeout: 3,
+                idle_timeout: None,
+            }));
+
+    rocket::custom(figment)
         .mount(
             "/",
             routes![
@@ -217,6 +189,107 @@ fn main() {
                 updates_empty,
             ],
         )
-        .manage(db::init_pool())
-        .launch();
+        .attach(HelloWorld::init())
+}
+
+#[cfg(test)]
+mod tests
+{
+    use std::env;
+    use rocket::{Rocket, Build};
+    use rocket::{local::blocking::Client};
+    use crate::database::HelloWorld;
+    use rocket::fairing::{self, AdHoc};
+    use rocket_db_pools::Database;
+
+    #[test]
+    fn plaintext() {
+        let client = create_client();
+
+        let response = client.get("/plaintext").dispatch();
+        assert_eq!(response.into_string().unwrap(), "Hello, World!");
+    }
+
+    #[test]
+    fn json() {
+        let client = create_client();
+
+        let response = client.get("/json").dispatch();
+        assert_eq!(response.into_string().unwrap(), "{\"message\":\"Hello, World!\"}");
+    }
+
+    #[test]
+    fn db() {
+        let client = create_client();
+
+        let response = client.get("/db").dispatch();
+        assert_eq!(response.into_string().unwrap(), "{\"id\":1,\"randomNumber\":101}");
+    }
+
+    #[test]
+    fn queries_empty() {
+        let client = create_client();
+
+        let response = client.get("/queries").dispatch();
+        assert_eq!(response.into_string().unwrap(), "[{\"id\":1,\"randomNumber\":101}]");
+    }
+
+    #[test]
+    fn queries_non_empty() {
+        let client = create_client();
+
+        let response = client.get("/queries?q=3").dispatch();
+        assert_eq!(response.into_string().unwrap(), "[{\"id\":1,\"randomNumber\":101},{\"id\":2,\"randomNumber\":102},{\"id\":3,\"randomNumber\":103}]");
+    }
+
+    #[test]
+    fn fortunes() {
+        let client = create_client();
+
+        let response = client.get("/fortunes").dispatch();
+        assert_eq!(response.into_string().unwrap(), "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><tr><td>11</td><td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;&#x2f;script&gt;</td></tr><tr><td>4</td><td>A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1</td></tr><tr><td>5</td><td>A computer program does what you tell it to do, not what you want it to do.</td></tr><tr><td>2</td><td>A computer scientist is someone who fixes things that aren&#x27;t broken.</td></tr><tr><td>8</td><td>A list is only as strong as its weakest link. — Donald Knuth</td></tr><tr><td>0</td><td>Additional fortune added at request time.</td></tr><tr><td>3</td><td>After enough decimal places, nobody gives a damn.</td></tr><tr><td>7</td><td>Any program that runs right is obsolete.</td></tr><tr><td>10</td><td>Computers make very fast, very accurate mistakes.</td></tr><tr><td>6</td><td>Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen</td></tr><tr><td>9</td><td>Feature: A bug with seniority.</td></tr><tr><td>1</td><td>fortune: No such file or directory</td></tr><tr><td>12</td><td>フレームワークのベンチマーク</td></tr></table></body></html>");
+    }
+
+    #[test]
+    fn updates_empty() {
+        let client = create_client();
+
+        let response = client.get("/updates").dispatch();
+        assert_eq!(response.into_string().unwrap(), "[{\"id\":1,\"randomNumber\":2}]");
+    }
+
+    #[test]
+    fn updates_non_empty() {
+        let client = create_client();
+
+        let response = client.get("/updates?q=3").dispatch();
+        assert_eq!(response.into_string().unwrap(), "[{\"id\":1,\"randomNumber\":2},{\"id\":3,\"randomNumber\":4},{\"id\":5,\"randomNumber\":6}]");
+    }
+
+    fn create_client() -> Client {
+        env::set_var("ROCKET_BENCHMARK_DATABASE_URL", "sqlite::memory:");
+
+        let rocket = create_rocket();
+        let client = Client::debug(rocket).unwrap();
+        client
+    }
+
+    fn create_rocket() -> Rocket<Build> {
+        super::launch().attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations))
+    }
+
+    async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
+        match HelloWorld::fetch(&rocket) {
+            Some(db) => match sqlx::migrate!("db/migrations").run(&**db).await {
+                Ok(_) => Ok(rocket),
+                Err(e) => {
+                    error!("Failed to initialize HelloWorld database: {}", e);
+                    Err(rocket)
+                }
+            }
+            None => Err(rocket),
+        }
+    }
 }
+
+

+ 17 - 6
frameworks/Rust/rocket/src/models.rs

@@ -1,17 +1,28 @@
+use rocket::serde::{Deserialize, Serialize};
+use sqlx::FromRow;
+
 #[derive(Serialize)]
 pub struct Message {
     pub message: &'static str,
 }
 
 #[allow(non_snake_case)]
-#[derive(Serialize, Queryable)]
-pub struct World {
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
+#[serde(crate = "rocket::serde")]
+pub struct Fortune {
     pub id: i32,
-    pub randomNumber: i32,
+    pub message: String
 }
 
-#[derive(Serialize, Queryable)]
-pub struct Fortune {
+#[allow(non_snake_case)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
+#[serde(crate = "rocket::serde")]
+pub struct World {
     pub id: i32,
-    pub message: String,
+    #[sqlx(rename = "randomnumber")]
+    #[serde(rename = "randomNumber")]
+    pub random_number: i32
 }
+
+
+

+ 73 - 0
frameworks/Rust/rocket/src/random.rs

@@ -0,0 +1,73 @@
+extern crate lazy_static;
+extern crate rand;
+
+#[cfg(test)]
+use std::collections::HashMap;
+use crate::request::RequestId;
+#[cfg(not(test))]
+use rand::seq::SliceRandom;
+#[cfg(not(test))]
+use rand::thread_rng;
+use std::sync::Mutex;
+#[cfg(test)]
+use std::sync::atomic::{AtomicI32, Ordering};
+
+#[cfg(not(test))]
+lazy_static! {
+    static ref RANDOM_ARRAY: Mutex<RandomArray> = Mutex::new(RandomArray::new(10000));
+}
+
+#[cfg(test)]
+lazy_static! {
+    static ref COUNTER_MAP: Mutex<HashMap<RequestId, AtomicI32>> = Mutex::new(HashMap::new());
+}
+
+#[cfg(not(test))]
+pub fn random_number(_request_id: &RequestId) -> i32 {
+    RANDOM_ARRAY
+        .lock()
+        .expect("Failed to lock RANDOM_ARRAY")
+        .next()
+}
+
+#[cfg(test)]
+pub fn random_number(request_id: &RequestId) -> i32 {
+    if !COUNTER_MAP.lock().expect("Failed to lock COUNTER_MAP").contains_key(&request_id) {
+        COUNTER_MAP.lock().expect("Failed to lock COUNTER_MAP").insert(*request_id, AtomicI32::new(1));
+    }
+
+    COUNTER_MAP.lock().expect("Failed to lock COUNTER_MAP").get_key_value(request_id).expect("Could not get RequestId")
+        .1.fetch_add(1, Ordering::Relaxed)
+}
+
+#[cfg(not(test))]
+struct RandomArray {
+    pointer: usize,
+    size: i32,
+    data: Vec<i32>,
+}
+
+#[cfg(not(test))]
+impl RandomArray {
+    fn new(size: i32) -> Self {
+        let mut data: Vec<i32> = (1..=size).collect();
+        let mut rng = thread_rng();
+        data.shuffle(&mut rng);
+
+        RandomArray {
+            pointer: 0,
+            size,
+            data,
+        }
+    }
+
+    fn next(&mut self) -> i32 {
+        if self.pointer >= self.size as usize {
+            self.pointer = 1;
+        } else {
+            self.pointer += 1;
+        }
+        self.data[self.pointer - 1]
+    }
+}
+

+ 24 - 0
frameworks/Rust/rocket/src/request.rs

@@ -0,0 +1,24 @@
+use rocket::request::{self, Request, FromRequest, Outcome};
+use std::sync::atomic::{AtomicI32, Ordering};
+
+/// A global atomic counter for generating IDs.
+static ID_COUNTER: AtomicI32 = AtomicI32::new(0);
+
+/// A type that represents a request's ID.
+#[derive(Hash, Eq, PartialEq, Copy, Clone)]
+pub struct RequestId(pub i32);
+
+/// Returns the current request's ID, assigning one only as necessary.
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for RequestId {
+    type Error = ();
+
+    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+        // The closure passed to `local_cache` will be executed at most once per
+             // request: the first time the `RequestId` guard is used. If it is
+             // requested again, `local_cache` will return the same value.
+             request::Outcome::Success(*request.local_cache(|| {
+                 RequestId(ID_COUNTER.fetch_add(1, Ordering::Relaxed))
+             }))
+    }
+}

+ 0 - 15
frameworks/Rust/rocket/src/schema.rs

@@ -1,15 +0,0 @@
-#![allow(non_snake_case)]
-
-table! {
-    world (id) {
-        id -> Integer,
-        randomnumber -> Integer,
-    }
-}
-
-table! {
-    fortune (id) {
-        id -> Integer,
-        message -> Text,
-    }
-}