Browse Source

Add rust/roa framework (#5574)

* add roa framework

* add Rust/roa in .travis.yml

* complete default test of roa framework

* roa framework pass the default test

* update roa to v0.5.0-beta.3

* roa framework: add pprof to profile

* update version of roa framework

* add hyper control benchmark for roa framework

* add framework benchmark roa-core

* complete roa-diesel

* update updates of roa-diesel

* roa-diesel pass TEST

* add roa-pg and roa-tokio

* update roa framework to 0.5.0-rc.3

* add roa-sqlx

* add roa-sqlx

* resize connection pool of roa-diesel

* update README for roa framework

* remove control hyper test of roa framework

* update roa-framework to v0.5.0-rc.4

* update roa framework to v0.5.0
Li Chenxi 5 years ago
parent
commit
d7fc507f68

+ 1 - 0
.travis.yml

@@ -72,6 +72,7 @@ env:
     - 'TESTDIR="Rust/may-minihttp Rust/nickel Rust/rocket"'
     - 'TESTDIR="Rust/may-minihttp Rust/nickel Rust/rocket"'
     - 'TESTDIR="Rust/rouille Rust/thruster Rust/tokio-minihttp"'
     - 'TESTDIR="Rust/rouille Rust/thruster Rust/tokio-minihttp"'
     - 'TESTDIR="Rust/warp-rust"'
     - 'TESTDIR="Rust/warp-rust"'
+    - 'TESTDIR="Rust/roa"'
     - 'TESTDIR="Scala/akka-http Scala/blaze Scala/cask Scala/colossus Scala/finagle"'
     - 'TESTDIR="Scala/akka-http Scala/blaze Scala/cask Scala/colossus Scala/finagle"'
     - 'TESTDIR="Scala/finatra Scala/finch Scala/http4s"'
     - 'TESTDIR="Scala/finatra Scala/finch Scala/http4s"'
     - 'TESTDIR="Scala/play2-scala Scala/scalene Scala/youi"'
     - 'TESTDIR="Scala/play2-scala Scala/scalene Scala/youi"'

+ 1 - 0
frameworks/Rust/roa/.env

@@ -0,0 +1 @@
+DATABASE_URL="postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world"

+ 46 - 0
frameworks/Rust/roa/Cargo.toml

@@ -0,0 +1,46 @@
+[package]
+name = "roa-techempower"
+version = "0.1.0"
+edition = "2018"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[[bin]]
+name = "roa"
+path = "src/main.rs"
+
+[[bin]]
+name = "roa-db"
+path = "src/main-db.rs"
+
+[[bin]]
+name = "roa-core"
+path = "src/main-core.rs"
+
+[dependencies]
+roa = { version = "0.5.0", features = ["json", "template", "router"] }
+roa-diesel = { version = "0.5.0", optional = true }
+roa-pg = { version = "0.5.0", optional = true }
+roa-tokio = { version = "0.5.0", optional = true }
+async-std = { version = "1.5", features = ["attributes"] }
+askama = "0.9"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+rand = { version = "0.7", features = ["small_rng"] }
+bytes = "0.5.3"
+futures = "0.3.4"
+http = "0.2"
+lazy_static = "1.4.0"
+dotenv_codegen = "0.15.0"
+
+tokio = { version = "0.2.13", features = ["full"], optional = true }
+diesel = { version = "1.4.3", features = ["postgres"], optional = true }
+sqlx = { version = "0.2", features = ["postgres"], optional = true }
+#hyper = "0.13"
+
+[features]
+orm = ["diesel", "roa-diesel"]
+pg = ["roa-pg"]
+sqlx-pg = ["sqlx"]
+tokio_rt = ["tokio", "roa-tokio"]

+ 111 - 0
frameworks/Rust/roa/README.md

@@ -0,0 +1,111 @@
+<div align="center">
+  <h1>Roa</h1>
+  <p><strong>Roa is an async web framework inspired by koajs, lightweight but powerful. </strong> </p>
+  <p>
+
+[![Stable Test](https://github.com/Hexilee/roa/workflows/Stable%20Test/badge.svg)](https://github.com/Hexilee/roa/actions)
+[![codecov](https://codecov.io/gh/Hexilee/roa/branch/master/graph/badge.svg)](https://codecov.io/gh/Hexilee/roa) 
+[![wiki](https://img.shields.io/badge/roa-wiki-purple.svg)](https://github.com/Hexilee/roa/wiki)
+[![Rust Docs](https://docs.rs/roa/badge.svg)](https://docs.rs/roa)
+[![Crate version](https://img.shields.io/crates/v/roa.svg)](https://crates.io/crates/roa)
+[![Download](https://img.shields.io/crates/d/roa.svg)](https://crates.io/crates/roa)
+[![Version](https://img.shields.io/badge/rustc-1.40+-lightgray.svg)](https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html)
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Hexilee/roa/blob/master/LICENSE)
+
+  </p>
+
+  <h3>
+    <a href="https://github.com/Hexilee/roa/tree/master/examples">Examples</a>
+    <span> | </span>
+    <a href="https://github.com/Hexilee/roa/wiki/Guide">Guide</a>
+    <span> | </span>
+    <a href="https://github.com/Hexilee/roa/wiki/Cookbook">Cookbook</a>
+  </h3>
+</div>
+<br>
+
+
+#### Feature highlights
+
+- A lightweight, solid and well extensible core.
+    - Supports HTTP/1.x and HTTP/2.0 protocols.
+    - Full streaming.
+    - Highly extensible middleware system.
+    - Based on [`hyper`](https://github.com/hyperium/hyper), runtime-independent, you can chose async runtime as you like.
+- Many useful extensions.
+    - Official runtime schemes:
+        - [async-std](https://github.com/async-rs/async-std) runtime and TcpStream;
+        - [tokio](https://github.com/tokio-rs/tokio) runtime and TcpStream.
+    - Transparent content compression (br, gzip, deflate, zstd).
+    - Configurable and nestable router.
+    - Named uri parameters(query and router parameter).
+    - Cookie and jwt support.
+    - HTTPS support.
+    - WebSocket support.
+    - Asynchronous multipart form support.
+    - Other middlewares(logger, CORS .etc).
+- Integrations
+    - roa-diesel, integration with [diesel](https://github.com/diesel-rs/diesel).
+    - roa-juniper, integration with [juniper](https://github.com/graphql-rust/juniper).
+    - roa-pg, integration with [tokio-postgres](https://crates.io/crates/tokio-postgres).
+- Works on stable Rust.
+
+#### Get start
+
+```text
+# Cargo.toml
+
+[dependencies]
+roa = "0.5.0"
+async-std = { version = "1.5", features = ["attributes"] }
+```
+
+```rust,no_run
+use roa::App;
+use roa::preload::*;
+use std::error::Error as StdError;
+
+#[async_std::main]
+async fn main() -> Result<(), Box<dyn StdError>> {
+    let app = App::new().end("Hello, World");
+    app.listen("127.0.0.1:8000", |addr| {
+        println!("Server is listening on {}", addr)
+    })?
+    .await?;
+    Ok(())
+}
+```
+Refer to [wiki](https://github.com/Hexilee/roa/wiki) for more details.
+
+## Database
+
+PostgreSQL.
+
+* [diesel](http://diesel.rs) \/ [tokio-postgres](https://crates.io/crates/tokio-postgres) \/ [sqlx](https://github.com/launchbadge/sqlx)
+
+## Test URLs
+
+### Test 1: JSON Encoding
+
+    http://localhost:8080/json
+
+### Test 2: Single Row Query
+
+    http://localhost:8080/db
+
+### Test 3: Multi Row Query
+
+    http://localhost:8080/queries?q=20
+
+### Test 4: Fortunes (Template rendering)
+
+    http://localhost:8080/fortune
+
+### Test 5: Update Query
+
+    http://localhost:8080/updates?q=20
+
+### Test 6: Plaintext
+
+    http://localhost:8080/plaintext
+

+ 119 - 0
frameworks/Rust/roa/benchmark_config.json

@@ -0,0 +1,119 @@
+{
+  "framework": "roa",
+  "tests": [{
+    "default": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "roa",
+      "language": "Rust",
+      "orm": "Raw",
+      "platform": "None",
+      "webserver": "hyper",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Roa",
+      "notes": "",
+      "versus": ""
+    },
+    "core": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "roa",
+      "language": "Rust",
+      "orm": "Raw",
+      "platform": "None",
+      "webserver": "hyper",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Roa [Core]",
+      "notes": "",
+      "versus": ""
+    },
+    "tokio": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "roa",
+      "language": "Rust",
+      "orm": "Raw",
+      "platform": "None",
+      "webserver": "hyper",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Roa [Tokio]",
+      "notes": "",
+      "versus": ""
+    },
+    "diesel": {
+      "db_url": "/db",
+      "fortune_url": "/fortune",
+      "query_url": "/queries?q=",
+      "update_url": "/updates?q=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "roa",
+      "language": "Rust",
+      "orm": "Full",
+      "platform": "None",
+      "webserver": "hyper",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Roa [Diesel]",
+      "notes": "",
+      "versus": ""
+    },
+    "pg": {
+      "db_url": "/db",
+      "fortune_url": "/fortune",
+      "query_url": "/queries?q=",
+      "update_url": "/updates?q=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "roa",
+      "language": "Rust",
+      "orm": "Raw",
+      "platform": "None",
+      "webserver": "hyper",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Roa [Postgres]",
+      "notes": "",
+      "versus": ""
+    },
+    "sqlx": {
+      "db_url": "/db",
+      "fortune_url": "/fortune",
+      "query_url": "/queries?q=",
+      "update_url": "/updates?q=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "roa",
+      "language": "Rust",
+      "orm": "Raw",
+      "platform": "None",
+      "webserver": "hyper",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Roa [Sqlx]",
+      "notes": "",
+      "versus": ""
+    }
+  }]
+}

+ 11 - 0
frameworks/Rust/roa/roa-core.dockerfile

@@ -0,0 +1,11 @@
+FROM rust:1.42
+
+RUN apt update -yqq && apt install -yqq cmake g++
+
+ADD ./ /roa
+WORKDIR /roa
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin roa-core
+
+CMD ./target/release/roa-core

+ 11 - 0
frameworks/Rust/roa/roa-diesel.dockerfile

@@ -0,0 +1,11 @@
+FROM rust:1.42
+
+RUN apt update -yqq && apt install -yqq cmake g++
+
+ADD ./ /roa
+WORKDIR /roa
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin roa-db --features "orm"
+
+CMD ./target/release/roa-db

+ 11 - 0
frameworks/Rust/roa/roa-pg.dockerfile

@@ -0,0 +1,11 @@
+FROM rust:1.42
+
+RUN apt update -yqq && apt install -yqq cmake g++
+
+ADD ./ /roa
+WORKDIR /roa
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin roa-db --features "pg"
+
+CMD ./target/release/roa-db

+ 11 - 0
frameworks/Rust/roa/roa-sqlx.dockerfile

@@ -0,0 +1,11 @@
+FROM rust:1.42
+
+RUN apt update -yqq && apt install -yqq cmake g++
+
+ADD ./ /roa
+WORKDIR /roa
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin roa-db --features "sqlx-pg"
+
+CMD ./target/release/roa-db

+ 11 - 0
frameworks/Rust/roa/roa-tokio.dockerfile

@@ -0,0 +1,11 @@
+FROM rust:1.42
+
+RUN apt update -yqq && apt install -yqq cmake g++
+
+ADD ./ /roa
+WORKDIR /roa
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin roa-core --features "tokio_rt"
+
+CMD ./target/release/roa-core

+ 11 - 0
frameworks/Rust/roa/roa.dockerfile

@@ -0,0 +1,11 @@
+FROM rust:1.42
+
+RUN apt update -yqq && apt install -yqq cmake g++
+
+ADD ./ /roa
+WORKDIR /roa
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin roa
+
+CMD ./target/release/roa

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

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

+ 97 - 0
frameworks/Rust/roa/src/db_diesel.rs

@@ -0,0 +1,97 @@
+use diesel::pg::PgConnection;
+use diesel::prelude::*;
+use diesel::r2d2::ConnectionManager;
+use roa::http::StatusCode;
+use roa_diesel::preload::*;
+use roa_diesel::Pool;
+
+use rand::rngs::SmallRng;
+use rand::{Rng, SeedableRng};
+
+use crate::models::Fortune;
+use crate::{async_trait, throw, Context, Result, Service, StdResult, World};
+use futures::stream::{FuturesUnordered, TryStreamExt};
+
+#[derive(Clone)]
+pub struct State {
+    pool: Pool<PgConnection>,
+    rng: SmallRng,
+}
+
+impl AsRef<Pool<PgConnection>> for State {
+    #[inline]
+    fn as_ref(&self) -> &Pool<PgConnection> {
+        &self.pool
+    }
+}
+
+impl State {
+    pub async fn bind(pg_url: &str) -> StdResult<Self> {
+        let pool = Pool::builder()
+            .max_size(50)
+            .build(ConnectionManager::<PgConnection>::new(pg_url))?;
+        Ok(Self {
+            pool,
+            rng: SmallRng::from_entropy(),
+        })
+    }
+}
+
+#[async_trait(?Send)]
+impl Service for Context<State> {
+    #[inline]
+    fn random_id(&mut self) -> i32 {
+        self.rng.gen_range(0, 10_001)
+    }
+
+    #[inline]
+    fn get_queries(&self) -> usize {
+        use std::cmp::{max, min};
+        let query = self.uri().query();
+        let nums = query
+            .and_then(|query| Some((query, query.find("q")?)))
+            .and_then(|(query, pos)| query.split_at(pos + 2).1.parse().ok())
+            .unwrap_or(1);
+        min(500, max(1, nums))
+    }
+
+    #[inline]
+    async fn query_world(&self, wid: i32) -> Result<World> {
+        use crate::schema::world::dsl::*;
+        let data = self.first(world.filter(id.eq(wid))).await?;
+        match data {
+            None => throw!(StatusCode::NOT_FOUND),
+            Some(item) => Ok(item),
+        }
+    }
+
+    #[inline]
+    async fn fortunes(&self) -> Result<Vec<Fortune>> {
+        use crate::schema::fortune::dsl::*;
+        Ok(self.load_data(fortune).await?)
+    }
+
+    #[inline]
+    async fn update_worlds(&mut self) -> Result<Vec<World>> {
+        let worlds = FuturesUnordered::new();
+        let random_ids: Vec<_> =
+            (0..self.get_queries()).map(|_| self.random_id()).collect();
+        for wid in random_ids {
+            worlds.push(update_world(self, wid));
+        }
+        worlds.try_collect().await
+    }
+}
+
+async fn update_world(ctx: &Context<State>, wid: i32) -> Result<World> {
+    use crate::schema::world::dsl::*;
+    let mut data = ctx.query_world(wid).await?;
+    data.randomnumber = wid;
+    ctx.execute(
+        diesel::update(world)
+            .filter(id.eq(wid))
+            .set(randomnumber.eq(wid)),
+    )
+    .await?;
+    Ok(data)
+}

+ 126 - 0
frameworks/Rust/roa/src/db_pg.rs

@@ -0,0 +1,126 @@
+use crate::models::Fortune;
+use crate::{async_trait, throw, Context, Result, Service, StdResult, World};
+use rand::rngs::SmallRng;
+use rand::{Rng, SeedableRng};
+use roa::http::StatusCode;
+use roa_pg::types::ToSql;
+use roa_pg::{connect, Client, Statement};
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::sync::Arc;
+
+#[derive(Clone)]
+pub struct State {
+    client: Arc<Client>,
+    queries: Arc<Queries>,
+    rng: SmallRng,
+}
+
+pub struct Queries {
+    fortune: Statement,
+    world: Statement,
+    updates: HashMap<usize, Statement>,
+}
+
+impl State {
+    pub async fn bind(url: &str) -> StdResult<Self> {
+        let (client, conn) = connect(&url.parse()?).await?;
+
+        async_std::task::spawn(conn);
+
+        let fortune = client.prepare("SELECT * FROM fortune").await?;
+        let world = client.prepare("SELECT * FROM world WHERE id=$1").await?;
+        let mut updates = HashMap::new();
+        for num in 1..=500 {
+            let mut pl = 1;
+            let mut q = String::new();
+            q.push_str("UPDATE world SET randomnumber = CASE id ");
+            for _ in 1..=num {
+                write!(&mut q, "when ${} then ${} ", pl, pl + 1)?;
+                pl += 2;
+            }
+            q.push_str("ELSE randomnumber END WHERE id IN (");
+            for _ in 1..=num {
+                write!(&mut q, "${},", pl)?;
+                pl += 1;
+            }
+            q.pop();
+            q.push(')');
+            updates.insert(num, client.prepare(&q).await?);
+        }
+        Ok(State {
+            client: Arc::new(client),
+            queries: Arc::new(Queries {
+                fortune,
+                world,
+                updates,
+            }),
+            rng: SmallRng::from_entropy(),
+        })
+    }
+}
+
+#[async_trait(?Send)]
+impl Service for Context<State> {
+    #[inline]
+    fn random_id(&mut self) -> i32 {
+        self.rng.gen_range(0, 10_001)
+    }
+
+    #[inline]
+    fn get_queries(&self) -> usize {
+        use std::cmp::{max, min};
+        let query = self.uri().query();
+        let nums = query
+            .and_then(|query| Some((query, query.find("q")?)))
+            .and_then(|(query, pos)| query.split_at(pos + 2).1.parse().ok())
+            .unwrap_or(1);
+        min(500, max(1, nums))
+    }
+
+    #[inline]
+    async fn query_world(&self, wid: i32) -> Result<World> {
+        match self.client.query_opt(&self.queries.world, &[&wid]).await? {
+            None => throw!(StatusCode::NOT_FOUND),
+            Some(row) => Ok(World {
+                id: row.get(0),
+                randomnumber: row.get(1),
+            }),
+        }
+    }
+
+    #[inline]
+    async fn fortunes(&self) -> Result<Vec<Fortune>> {
+        let fortunes = self
+            .client
+            .query(&self.queries.fortune, &[])
+            .await?
+            .iter()
+            .map(|row| Fortune {
+                id: row.get(0),
+                message: row.get(1),
+            })
+            .collect();
+        Ok(fortunes)
+    }
+
+    #[inline]
+    async fn update_worlds(&mut self) -> Result<Vec<World>> {
+        let mut worlds = self.query_worlds().await?;
+        let nums = worlds.len();
+        let mut params: Vec<&(dyn ToSql + Sync)> = Vec::with_capacity(nums * 3);
+        for w in worlds.iter_mut() {
+            w.randomnumber = w.id;
+        }
+        for w in &worlds {
+            params.push(&w.id);
+            params.push(&w.randomnumber);
+        }
+        for w in &worlds {
+            params.push(&w.id);
+        }
+        let statement = &self.queries.updates[&nums];
+        self.client.execute(statement, &params).await?;
+        Ok(worlds)
+    }
+}

+ 108 - 0
frameworks/Rust/roa/src/db_sqlx.rs

@@ -0,0 +1,108 @@
+use crate::models::Fortune;
+use crate::{async_trait, throw, Context, Result, Service, StdResult, World};
+use futures::TryStreamExt;
+use rand::rngs::SmallRng;
+use rand::{Rng, SeedableRng};
+use roa::http::StatusCode;
+use sqlx::{PgPool, Row};
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::sync::Arc;
+
+#[derive(Clone)]
+pub struct State {
+    client: PgPool,
+    updates: Arc<HashMap<usize, String>>,
+    rng: SmallRng,
+}
+
+impl State {
+    pub async fn bind(url: &str) -> StdResult<Self> {
+        let client = PgPool::new(url).await?;
+        let mut updates = HashMap::new();
+        for num in 1..=500 {
+            let mut pl = 1;
+            let mut q = String::new();
+            q.push_str("UPDATE world SET randomnumber = CASE id ");
+            for _ in 1..=num {
+                write!(&mut q, "when ${} then ${} ", pl, pl + 1)?;
+                pl += 2;
+            }
+            q.push_str("ELSE randomnumber END WHERE id IN (");
+            for _ in 1..=num {
+                write!(&mut q, "${},", pl)?;
+                pl += 1;
+            }
+            q.pop();
+            q.push(')');
+            updates.insert(num, q);
+        }
+        Ok(State {
+            client,
+            updates: Arc::new(updates),
+            rng: SmallRng::from_entropy(),
+        })
+    }
+}
+
+#[async_trait(?Send)]
+impl Service for Context<State> {
+    #[inline]
+    fn random_id(&mut self) -> i32 {
+        self.rng.gen_range(0, 10_001)
+    }
+
+    #[inline]
+    fn get_queries(&self) -> usize {
+        use std::cmp::{max, min};
+        let query = self.uri().query();
+        let nums = query
+            .and_then(|query| Some((query, query.find("q")?)))
+            .and_then(|(query, pos)| query.split_at(pos + 2).1.parse().ok())
+            .unwrap_or(1);
+        min(500, max(1, nums))
+    }
+
+    #[inline]
+    async fn query_world(&self, wid: i32) -> Result<World> {
+        match sqlx::query("SELECT * FROM world WHERE id=$1")
+            .bind(wid)
+            .fetch_optional(&mut &self.client)
+            .await?
+        {
+            None => throw!(StatusCode::NOT_FOUND),
+            Some(row) => Ok(World {
+                id: row.get(0),
+                randomnumber: row.get(1),
+            }),
+        }
+    }
+
+    #[inline]
+    async fn fortunes(&self) -> Result<Vec<Fortune>> {
+        let fortunes: Vec<_> = sqlx::query("SELECT * FROM fortune")
+            .fetch(&mut &self.client)
+            .map_ok(|row| Fortune {
+                id: row.get(0),
+                message: row.get(1),
+            })
+            .try_collect()
+            .await?;
+        Ok(fortunes)
+    }
+
+    #[inline]
+    async fn update_worlds(&mut self) -> Result<Vec<World>> {
+        let mut worlds = self.query_worlds().await?;
+        let mut query = sqlx::query(&self.updates[&worlds.len()]);
+        for w in worlds.iter_mut() {
+            w.randomnumber = w.id;
+            query = query.bind(w.id).bind(w.randomnumber);
+        }
+        for w in &worlds {
+            query = query.bind(w.id);
+        }
+        query.execute(&mut &self.client).await?;
+        Ok(worlds)
+    }
+}

+ 23 - 0
frameworks/Rust/roa/src/endpoints.rs

@@ -0,0 +1,23 @@
+use crate::utils::{Message, JSON_LEN, PLAINTEXT_LEN};
+use roa::http::header::CONTENT_LENGTH;
+use roa::preload::*;
+use roa::{Context, Result};
+
+static HELLO_WORLD: &str = "Hello, World!";
+
+#[inline]
+pub async fn json(ctx: &mut Context) -> Result {
+    ctx.resp.headers.insert(CONTENT_LENGTH, JSON_LEN.clone());
+    ctx.write_json(&Message {
+        message: HELLO_WORLD,
+    })
+}
+
+#[inline]
+pub async fn plaintext(ctx: &mut Context) -> Result {
+    ctx.resp
+        .headers
+        .insert(CONTENT_LENGTH, PLAINTEXT_LEN.clone());
+    ctx.write(HELLO_WORLD);
+    Ok(())
+}

+ 44 - 0
frameworks/Rust/roa/src/main-core.rs

@@ -0,0 +1,44 @@
+use roa::http::header::SERVER;
+use roa::{App, Context, Result};
+use std::error::Error as StdError;
+use std::result::Result as StdResult;
+
+pub mod endpoints;
+pub mod utils;
+use endpoints::{json, plaintext};
+use utils::SERVER_HEADER;
+
+#[inline]
+async fn endpoint(ctx: &mut Context<()>) -> Result {
+    // avoid to re-allocate a header map
+    ctx.resp.headers = std::mem::take(&mut ctx.req.headers);
+    ctx.resp.headers.clear();
+    ctx.resp.headers.insert(SERVER, SERVER_HEADER.clone());
+    match ctx.uri().path() {
+        "/plaintext" => plaintext(ctx).await,
+        _ => json(ctx).await,
+    }
+}
+
+#[cfg(not(feature = "tokio_rt"))]
+#[async_std::main]
+async fn main() -> StdResult<(), Box<dyn StdError>> {
+    use roa::preload::*;
+    let app = App::new().end(endpoint);
+    app.listen("0.0.0.0:8080", |addr| {
+        println!("Server listen on {}...", addr);
+    })?
+    .await?;
+    Ok(())
+}
+
+#[cfg(feature = "tokio_rt")]
+#[tokio::main]
+async fn main() -> StdResult<(), Box<dyn StdError>> {
+    use roa_tokio::{Exec, TcpIncoming};
+    let app = App::with_exec((), Exec).end(endpoint);
+    let incoming = TcpIncoming::bind("0.0.0.0:8080")?;
+    println!("Server listen on {}...", incoming.local_addr());
+    app.accept(incoming).await?;
+    Ok(())
+}

+ 118 - 0
frameworks/Rust/roa/src/main-db.rs

@@ -0,0 +1,118 @@
+#[cfg(feature = "orm")]
+#[macro_use]
+extern crate diesel;
+
+#[cfg(feature = "orm")]
+mod db_diesel;
+
+#[cfg(feature = "orm")]
+mod schema;
+
+#[cfg(feature = "orm")]
+use db_diesel::State;
+
+#[cfg(feature = "pg")]
+mod db_pg;
+
+#[cfg(feature = "pg")]
+use db_pg::State;
+
+#[cfg(feature = "sqlx-pg")]
+mod db_sqlx;
+
+#[cfg(feature = "sqlx-pg")]
+use db_sqlx::State;
+
+use futures::stream::{FuturesUnordered, TryStreamExt};
+use roa::http::header::SERVER;
+use roa::preload::*;
+use roa::router::{get, RouteTable, Router};
+use roa::{async_trait, throw, App, Context, Next, Result};
+mod models;
+pub mod utils;
+use dotenv_codegen::dotenv;
+use models::*;
+use utils::SERVER_HEADER;
+
+type StdResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
+
+#[async_trait(?Send)]
+trait Service {
+    fn random_id(&mut self) -> i32;
+    fn get_queries(&self) -> usize;
+    async fn query_world(&self, wid: i32) -> Result<World>;
+    async fn fortunes(&self) -> Result<Vec<Fortune>>;
+    async fn update_worlds(&mut self) -> Result<Vec<World>>;
+    async fn query_worlds(&mut self) -> Result<Vec<World>> {
+        let worlds = FuturesUnordered::new();
+        let random_ids: Vec<_> =
+            (0..self.get_queries()).map(|_| self.random_id()).collect();
+        for id in random_ids {
+            worlds.push(self.query_world(id));
+        }
+        worlds.try_collect().await
+    }
+}
+
+#[inline]
+async fn gate(ctx: &mut Context<State>, next: Next<'_>) -> Result {
+    // avoid to re-allocate a header map
+    ctx.resp.headers = std::mem::take(&mut ctx.req.headers);
+    ctx.resp.headers.clear();
+    ctx.resp.headers.insert(SERVER, SERVER_HEADER.clone());
+    next.await
+}
+
+#[inline]
+async fn db(ctx: &mut Context<State>) -> Result {
+    let id = ctx.random_id();
+    let data = ctx.query_world(id).await?;
+    ctx.write_json(&data)?;
+    Ok(())
+}
+
+#[inline]
+async fn queries(ctx: &mut Context<State>) -> Result {
+    let data = ctx.query_worlds().await?;
+    ctx.write_json(&data)?;
+    Ok(())
+}
+
+#[inline]
+async fn fortune(ctx: &mut Context<State>) -> Result {
+    let mut fortunes = ctx.fortunes().await?;
+    fortunes.push(Fortune {
+        id: 0,
+        message: "Additional fortune added at request time.".to_owned(),
+    });
+    fortunes.sort_by(|it, next| it.message.cmp(&next.message));
+    ctx.render(&Fortunes { items: &fortunes })
+}
+
+#[inline]
+async fn updates(ctx: &mut Context<State>) -> Result {
+    let data = ctx.update_worlds().await?;
+    ctx.write_json(&data)?;
+    Ok(())
+}
+
+fn routes(prefix: &'static str) -> StdResult<RouteTable<State>> {
+    Router::new()
+        .gate(gate)
+        .on("/db", get(db))
+        .on("/queries", get(queries))
+        .on("/fortune", get(fortune))
+        .on("/updates", get(updates))
+        .routes(prefix)
+        .map_err(Into::into)
+}
+
+#[async_std::main]
+async fn main() -> StdResult<()> {
+    let app = App::state(State::bind(dotenv!("DATABASE_URL")).await?).end(routes("/")?);
+    app.listen("0.0.0.0:8080", |addr| {
+        println!("Server listen on {}...", addr);
+    })?
+    .await?;
+    Ok(())
+}

+ 34 - 0
frameworks/Rust/roa/src/main.rs

@@ -0,0 +1,34 @@
+use roa::http::header::SERVER;
+use roa::preload::*;
+use roa::router::{get, Router};
+use roa::{App, Context, Next, Result};
+use std::error::Error;
+use std::result::Result as StdResult;
+
+pub mod endpoints;
+pub mod utils;
+use endpoints::{json, plaintext};
+use utils::SERVER_HEADER;
+
+#[inline]
+async fn gate(ctx: &mut Context<()>, next: Next<'_>) -> Result {
+    // avoid to re-allocate a header map
+    ctx.resp.headers = std::mem::take(&mut ctx.req.headers);
+    ctx.resp.headers.clear();
+    ctx.resp.headers.insert(SERVER, SERVER_HEADER.clone());
+    next.await
+}
+
+#[async_std::main]
+async fn main() -> StdResult<(), Box<dyn Error>> {
+    let router = Router::new()
+        .gate(gate)
+        .on("/json", get(json))
+        .on("/plaintext", get(plaintext));
+    let app = App::new().end(router.routes("/")?);
+    app.listen("0.0.0.0:8080", |addr| {
+        println!("Server listen on {}...", addr);
+    })?
+    .await?;
+    Ok(())
+}

+ 30 - 0
frameworks/Rust/roa/src/models.rs

@@ -0,0 +1,30 @@
+#![allow(dead_code)]
+
+use askama::Template;
+use serde::Serialize;
+
+#[derive(Serialize)]
+pub struct Message {
+    pub message: &'static str,
+}
+
+#[cfg_attr(feature = "orm", derive(Queryable))]
+#[allow(non_snake_case)]
+#[derive(Serialize, Debug)]
+pub struct World {
+    pub id: i32,
+    pub randomnumber: i32,
+}
+
+#[cfg_attr(feature = "orm", derive(Queryable))]
+#[derive(Serialize, Debug)]
+pub struct Fortune {
+    pub id: i32,
+    pub message: String,
+}
+
+#[derive(Template)]
+#[template(path = "fortune.html")]
+pub struct Fortunes<'a> {
+    pub items: &'a [Fortune],
+}

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

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

+ 20 - 0
frameworks/Rust/roa/src/utils.rs

@@ -0,0 +1,20 @@
+use lazy_static::lazy_static;
+use roa::http::header::HeaderValue;
+use serde::{Deserialize, Serialize};
+
+lazy_static! {
+    pub static ref SERVER_HEADER: HeaderValue = HeaderValue::from_static("roa");
+    pub static ref JSON_LEN: HeaderValue = HeaderValue::from_static("27");
+    pub static ref PLAINTEXT_LEN: HeaderValue = HeaderValue::from_static("13");
+}
+
+#[derive(Serialize, Debug)]
+pub struct Fortune {
+    pub id: i32,
+    pub message: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Message {
+    pub message: &'static str,
+}

+ 12 - 0
frameworks/Rust/roa/templates/fortune.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head><title>Fortunes</title></head>
+  <body>
+    <table>
+      <tr><th>id</th><th>message</th></tr>
+      {% for item in items %}
+      <tr><td>{{item.id}}</td><td>{{item.message}}</td></tr>
+      {% endfor %}
+    </table>
+  </body>
+</html>