Browse Source

add rust web framework salvo (#6414)

* add framework [Rust]Salvo

* update rust version to 1.50

* Add testing for [Rust]Salvo

* Add [Rust]Salvo

* Rust Salvo update

* Update Rust Salvo

* update rust web farmwork salvo

* upgrade to 0.7.0

* update code

* enable pipeline
Chrislearn Young 4 years ago
parent
commit
53f6bac5f4

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

@@ -0,0 +1,46 @@
+[package]
+name = "salvo_test"
+version = "0.6.5"
+edition = "2018"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[[bin]]
+name = "main-db"
+path = "src/main_db.rs"
+
+[[bin]]
+name = "main-pg"
+path = "src/main_pg.rs"
+
+[dependencies]
+anyhow = "1.0.38"
+askama = "0.8"
+diesel = { version = "1.4.3", features = ["postgres", "r2d2"] }
+salvo = { version = "0.7.0", features = ["anyhow"] }
+tokio = { version = "1.2", features = ["full"] }
+snmalloc-rs = { version = "0.2.24", features = ["1mib", "native-cpu"] }
+random-fast-rng = "0.1.1"
+futures = "0.3.12"
+smallvec = "1.6.1"
+simd-json = "0.3"
+simd-json-derive = "0.1.15"
+serde = { version = "1.0", features = ["derive"] }
+tokio-postgres = "0.7"
+yarte = { version = "0.15", features = ["bytes-buf"] }
+once_cell = "1.5.2"
+rand = { version = "0.8.3", features = ["small_rng"] }
+hyper = "0.14.4"
+
+[profile.release]
+lto = true
+opt-level = 3
+codegen-units = 1
+
+# [patch.crates-io]
+# salvo = { path = "D:/Kenorld/salvo-rs/salvo" }
+# salvo_core = { path = "D:/Kenorld/salvo-rs/salvo/core" }
+# salvo_macros = { path = "D:/Kenorld/salvo-rs/salvo/macros" }
+# salvo_extra = { path = "D:/Kenorld/salvo-rs/salvo/extra" }

+ 5 - 0
frameworks/Rust/salvo/README.md

@@ -0,0 +1,5 @@
+# [salvo](https://salvo.rs) web framework
+
+## Description
+
+A easy to use web framework written by rust.

+ 66 - 0
frameworks/Rust/salvo/benchmark_config.json

@@ -0,0 +1,66 @@
+{
+  "framework": "salvo",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "salvo",
+        "language": "Rust",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Salvo",
+        "notes": "",
+        "versus": "None"
+      },
+      "db": {
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?q=",
+        "update_url": "/updates?q=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "salvo",
+        "language": "Rust",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Salvo [Diesel]",
+        "notes": "",
+        "versus": ""
+      },
+      "pg": {
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?q=",
+        "update_url": "/updates?q=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "salvo",
+        "language": "Rust",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Salvo [PG]",
+        "notes": "",
+        "versus": ""
+      }
+    }
+  ]
+}

+ 43 - 0
frameworks/Rust/salvo/config.toml

@@ -0,0 +1,43 @@
+[framework]
+name = "salvo"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "salvo"
+versus = ""
+
+[db]
+urls.db = "/db"
+urls.query = "/query?q="
+urls.update = "/update?q="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "salvo"
+versus = ""
+
+[raw]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Platform"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "salvo"
+versus = ""

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

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

+ 13 - 0
frameworks/Rust/salvo/salvo-db.dockerfile

@@ -0,0 +1,13 @@
+FROM rust:1.50
+
+RUN apt-get update -yqq && apt-get install -yqq cmake g++
+
+ADD ./ /salvo
+WORKDIR /salvo
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release
+
+EXPOSE 8080
+
+CMD ./target/release/main-db

+ 13 - 0
frameworks/Rust/salvo/salvo-pg.dockerfile

@@ -0,0 +1,13 @@
+FROM rust:1.50
+
+RUN apt-get update -yqq && apt-get install -yqq cmake g++
+
+ADD ./ /salvo
+WORKDIR /salvo
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release
+
+EXPOSE 8080
+
+CMD ./target/release/main-pg

+ 16 - 0
frameworks/Rust/salvo/salvo.dockerfile

@@ -0,0 +1,16 @@
+FROM rust:1.50
+
+# Disable simd at jsonescape
+ENV CARGO_CFG_JSONESCAPE_DISABLE_AUTO_SIMD=
+
+RUN apt-get update -yqq && apt-get install -yqq cmake g++
+
+ADD ./ /salvo
+WORKDIR /salvo
+
+RUN cargo clean
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release
+
+EXPOSE 8080
+
+CMD ./target/release/main

+ 41 - 0
frameworks/Rust/salvo/src/main.rs

@@ -0,0 +1,41 @@
+#[global_allocator]
+static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;
+
+use salvo::http::header::{self, HeaderValue};
+use salvo::prelude::*;
+use simd_json_derive::Serialize;
+use hyper::server::conn::AddrIncoming;
+
+static HELLO_WORLD: &'static [u8] = b"Hello, world!";
+#[derive(Serialize)]
+pub struct Message {
+    pub message: &'static str,
+}
+
+#[fn_handler]
+async fn json(res: &mut Response) {
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    let msg = Message {
+        message: "Hello, World!",
+    };
+    res.render_binary(HeaderValue::from_static("application/json"), &msg.json_vec().unwrap());
+}
+
+#[fn_handler]
+async fn plaintext(res: &mut Response) {
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_binary(HeaderValue::from_static("text/plain"), HELLO_WORLD);
+}
+
+#[tokio::main]
+async fn main() {
+    println!("Started http server: 127.0.0.1:8080");
+    let router = Router::new()
+        .push(Router::new().path("plaintext").get(plaintext))
+        .push(Router::new().path("json").get(json));
+    // Server::new(router).bind(([0, 0, 0, 0], 8080)).await;
+
+    let mut incoming = AddrIncoming::bind(&(([0, 0, 0, 0], 8080)).into()).unwrap();
+    incoming.set_nodelay(true);
+    Server::builder(incoming).http1_pipeline_flush(true).serve(Service::new(router)).await.unwrap();
+}

+ 134 - 0
frameworks/Rust/salvo/src/main_db.rs

@@ -0,0 +1,134 @@
+#[global_allocator]
+static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;
+
+#[macro_use]
+extern crate diesel;
+
+use anyhow::Error;
+use askama::Template;
+use diesel::prelude::*;
+use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection};
+use once_cell::sync::OnceCell;
+use rand::rngs::SmallRng;
+use rand::{Rng, SeedableRng};
+use salvo::http::header::{self, HeaderValue};
+use salvo::prelude::*;
+use std::{cmp, io};
+
+mod models;
+mod schema;
+use models::*;
+use schema::*;
+
+const DB_URL: &str = "postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world";
+pub type PgPool = Pool<ConnectionManager<PgConnection>>;
+
+pub static DB_POOL: OnceCell<PgPool> = OnceCell::new();
+
+pub fn connect() -> Result<PooledConnection<ConnectionManager<PgConnection>>, PoolError> {
+    DB_POOL.get().unwrap().get()
+}
+pub fn build_pool(database_url: &str) -> Result<PgPool, PoolError> {
+    let manager = ConnectionManager::<PgConnection>::new(database_url);
+    diesel::r2d2::Pool::builder().max_size(50).build(manager)
+}
+
+#[fn_handler]
+async fn world_row(_req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let mut rng = SmallRng::from_entropy();
+    let random_id = rng.gen_range(1..10_001);
+    let conn = connect()?;
+    let row = world::table.find(random_id).first::<World>(&conn)?;
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_json(&row);
+    Ok(())
+}
+
+#[fn_handler]
+async fn queries(req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let count = req.get_query::<usize>("q").unwrap_or(1);
+    let count = cmp::min(500, cmp::max(1, count));
+
+    let mut worlds = Vec::with_capacity(count);
+    let mut rng = SmallRng::from_entropy();
+    let conn = connect()?;
+    for _ in 0..count {
+        let id = rng.gen_range(1..10_001);
+        let w = world::table.find(id).get_result::<World>(&conn)?;
+        worlds.push(w);
+    }
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_json(&worlds);
+    Ok(())
+}
+#[fn_handler]
+async fn updates(req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let count = req.get_query::<usize>("q").unwrap_or(1);
+    let count = cmp::min(500, cmp::max(1, count));
+
+    let conn = connect()?;
+    let mut worlds = Vec::with_capacity(count);
+    let mut rng = SmallRng::from_entropy();
+    for _ in 0..count {
+        let w_id: i32 = rng.gen_range(1..10_001);
+        let mut w = world::table.find(w_id).first::<World>(&conn)?;
+        w.randomnumber = rng.gen_range(1..10_001);
+        worlds.push(w);
+    }
+    worlds.sort_by_key(|w| w.id);
+    conn.transaction::<(), Error, _>(|| {
+        for w in &worlds {
+            diesel::update(world::table)
+                .filter(world::id.eq(w.id))
+                .set(world::randomnumber.eq(w.randomnumber))
+                .execute(&conn)?;
+        }
+        Ok(())
+    })?;
+
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_json(&worlds);
+    Ok(())
+}
+
+#[derive(Template)]
+#[template(path = "fortune.html")]
+struct FortuneTemplate<'a> {
+    items: &'a Vec<Fortune>,
+}
+
+#[fn_handler]
+async fn fortunes(_req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let conn = connect()?;
+    let rows = match fortune::table.get_results::<Fortune>(&conn) {
+        Ok(mut items) => {
+            items.push(Fortune {
+                id: 0,
+                message: "Additional fortune added at request time.".to_string(),
+            });
+            items.sort_by(|it, next| it.message.cmp(&next.message));
+            Ok(items)
+        }
+        Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
+    }?;
+    let tmpl = FortuneTemplate { items: &rows };
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_html_text(&tmpl.render().unwrap());
+    Ok(())
+}
+
+#[tokio::main]
+async fn main() {
+    println!("Starting http server: 127.0.0.1:8080");
+
+    DB_POOL
+        .set(build_pool(&DB_URL).expect(&format!("Error connecting to {}", &DB_URL)))
+        .ok();
+
+    let router = Router::new()
+        .push(Router::new().path("db").get(world_row))
+        .push(Router::new().path("fortunes").get(fortunes))
+        .push(Router::new().path("queries").get(queries))
+        .push(Router::new().path("updates").get(updates));
+    Server::new(router).bind(([0, 0, 0, 0], 8080)).await;
+}

+ 198 - 0
frameworks/Rust/salvo/src/main_pg.rs

@@ -0,0 +1,198 @@
+#[global_allocator]
+static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;
+
+use anyhow::Error;
+use futures::{pin_mut, TryStreamExt};
+use once_cell::sync::OnceCell;
+use rand::rngs::SmallRng;
+use rand::{Rng, SeedableRng};
+use salvo::http::header::{self, HeaderValue};
+use salvo::prelude::*;
+use serde::Serialize;
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::{cmp, io};
+use tokio_postgres::types::ToSql;
+use tokio_postgres::{self, Client, NoTls, Statement};
+use yarte::ywrite_html;
+
+mod models;
+use models::*;
+
+const DB_URL: &str = "postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world";
+pub static DB_CONN: OnceCell<PgConnection> = OnceCell::new();
+pub fn connect() -> &'static PgConnection {
+    DB_CONN.get().unwrap()
+}
+pub struct PgConnection {
+    client: Client,
+    fortune: Statement,
+    world: Statement,
+    updates: HashMap<u16, Statement>,
+}
+
+#[derive(Serialize, Debug)]
+pub struct CowFortune {
+    pub id: i32,
+    pub message: Cow<'static, str>,
+}
+
+impl PgConnection {
+    pub async fn connect(db_url: &str) -> Result<PgConnection, io::Error> {
+        let (client, conn) = tokio_postgres::connect(db_url, NoTls)
+            .await
+            .expect("can not connect to postgresql");
+        tokio::spawn(async move {
+            if let Err(e) = conn.await {
+                eprintln!("connection error: {}", e);
+            }
+        });
+
+        let fortune = client.prepare("SELECT * FROM fortune").await.unwrap();
+        let world = client.prepare("SELECT * FROM world WHERE id=$1").await.unwrap();
+        let mut updates_statements = HashMap::new();
+        for num in 1..=500u16 {
+            let mut pl: u16 = 1;
+            let mut q = String::new();
+            q.push_str("UPDATE world SET randomnumber = CASE id ");
+            for _ in 1..=num {
+                let _ = write!(&mut q, "when ${} then ${} ", pl, pl + 1);
+                pl += 2;
+            }
+            q.push_str("ELSE randomnumber END WHERE id IN (");
+            for _ in 1..=num {
+                let _ = write!(&mut q, "${},", pl);
+                pl += 1;
+            }
+            q.pop();
+            q.push(')');
+            updates_statements.insert(num, client.prepare(&q).await.unwrap());
+        }
+
+        Ok(PgConnection {
+            client,
+            fortune,
+            world,
+            updates: updates_statements,
+        })
+    }
+}
+
+#[fn_handler]
+async fn world_row(_req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let conn = connect();
+    let mut rng = SmallRng::from_entropy();
+    let random_id = (rng.gen::<u32>() % 10_000 + 1) as i32;
+    let row = conn.client.query_one(&conn.world, &[&random_id]).await?;
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    let world = &World {
+        id: row.get(0),
+        randomnumber: row.get(1),
+    };
+    res.render_json(&world);
+    Ok(())
+}
+
+#[fn_handler]
+async fn queries(req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let count = req.get_query::<usize>("q").unwrap_or(1);
+    let count = cmp::min(500, cmp::max(1, count));
+
+    let mut worlds = Vec::with_capacity(count);
+    let mut rng = SmallRng::from_entropy();
+    let conn = connect();
+    for _ in 0..count {
+        let w_id = (rng.gen::<u32>() % 10_000 + 1) as i32;
+        let row = conn.client.query_one(&conn.world, &[&w_id]).await?;
+        worlds.push(World {
+            id: row.get(0),
+            randomnumber: row.get(1),
+        });
+    }
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_json(&worlds);
+    Ok(())
+}
+
+#[fn_handler]
+async fn updates(req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let count = req.get_query::<usize>("q").unwrap_or(1);
+    let count = cmp::min(500, cmp::max(1, count));
+
+    let mut worlds = Vec::with_capacity(count);
+    let mut rng = SmallRng::from_entropy();
+    let conn = connect();
+    for _ in 0..count {
+        let id = (rng.gen::<u32>() % 10_000 + 1) as i32;
+        let w_id = (rng.gen::<u32>() % 10_000 + 1) as i32;
+        let row = conn.client.query_one(&conn.world, &[&w_id]).await?;
+        worlds.push(World {
+            id: row.get(0),
+            randomnumber: id,
+        });
+    }
+
+    let st = conn.updates.get(&(count as u16)).unwrap().clone();
+
+    let mut params: Vec<&(dyn ToSql + std::marker::Sync)> = Vec::with_capacity(count * 3);
+    for w in &worlds {
+        params.push(&w.id);
+        params.push(&w.randomnumber);
+    }
+    for w in &worlds {
+        params.push(&w.id);
+    }
+
+    conn.client.query(&st, &params).await?;
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_json(&worlds);
+    Ok(())
+}
+
+#[fn_handler]
+async fn fortunes(_req: &mut Request, res: &mut Response) -> Result<(), Error> {
+    let mut items = vec![CowFortune {
+        id: 0,
+        message: Cow::Borrowed("Additional fortune added at request time."),
+    }];
+    let conn = connect();
+    let params: [&str; 0] = [];
+    let stream = conn.client.query_raw(&conn.fortune, &params).await?;
+    pin_mut!(stream);
+
+    while let Some(row) = stream.try_next().await? {
+        items.push(CowFortune {
+            id: row.get(0),
+            message: Cow::Owned(row.get(1)),
+        });
+    }
+
+    items.sort_by(|it, next| it.message.cmp(&next.message));
+
+    let mut body = Vec::with_capacity(2048);
+    ywrite_html!(body, "{{> fortune }}");
+
+    res.headers_mut().insert(header::SERVER, HeaderValue::from_static("S"));
+    res.render_binary("text/html; charset=utf-8".parse().unwrap(), &body);
+    Ok(())
+}
+
+#[tokio::main]
+async fn main() {
+    println!("Started http server: 127.0.0.1:8080");
+
+    DB_CONN
+        .set(
+            PgConnection::connect(DB_URL)
+                .await
+                .expect(&format!("Error connecting to {}", &DB_URL)),
+        )
+        .ok();
+    let router = Router::new()
+        .push(Router::new().path("db").get(world_row))
+        .push(Router::new().path("fortunes").get(fortunes))
+        .push(Router::new().path("queries").get(queries))
+        .push(Router::new().path("updates").get(updates));
+    Server::new(router).bind(([0, 0, 0, 0], 8080)).await;
+}

+ 21 - 0
frameworks/Rust/salvo/src/models.rs

@@ -0,0 +1,21 @@
+use diesel::Queryable;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize)]
+pub struct Message {
+    pub message: &'static str,
+}
+
+#[allow(non_snake_case)]
+#[derive(Serialize, Queryable, Debug)]
+pub struct World {
+    pub id: i32,
+    pub randomnumber: i32,
+}
+
+#[allow(non_snake_case)]
+#[derive(Serialize, Queryable, Debug)]
+pub struct Fortune {
+    pub id: i32,
+    pub message: String,
+}

+ 13 - 0
frameworks/Rust/salvo/src/schema.rs

@@ -0,0 +1,13 @@
+table! {
+    world (id) {
+        id -> Integer,
+        randomnumber -> Integer,
+    }
+}
+
+table! {
+    fortune (id) {
+        id -> Integer,
+        message -> Text,
+    }
+}

+ 5 - 0
frameworks/Rust/salvo/templates/fortune.hbs

@@ -0,0 +1,5 @@
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>
+      {{~# each items ~}}
+      <tr><td>{{id}}</td><td>{{message}}</td></tr>
+      {{~/each ~}}
+</table></body></html>

+ 12 - 0
frameworks/Rust/salvo/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>