Browse Source

Add orm test for xitca-web (#6746)

* add orm test for xtica-web

* update dependency

* fix core affinity
fakeshadow 4 years ago
parent
commit
964466efa7

+ 15 - 5
frameworks/Rust/xitca-web/Cargo.toml

@@ -3,13 +3,23 @@ name = "xitca-web"
 version = "0.1.0"
 edition = "2018"
 
+[[bin]]
+name = "xitca-web"
+path = "./src/main.rs"
+
+[[bin]]
+name = "xitca-web-diesel"
+path = "./src/main_diesel.rs"
+
 [dependencies]
-xitca-http = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "0f4ab88883ac5656403154ab82284117f289d897" }
-xitca-web = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "0f4ab88883ac5656403154ab82284117f289d897" }
+xitca-http = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "7499e8a5aa4f7e1f17bbf6b6ee0816828dff3149" }
+xitca-web = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "7499e8a5aa4f7e1f17bbf6b6ee0816828dff3149" }
 
 ahash = { version = "0.7.4", features = ["compile-time-rng"] }
 atoi = "0.4.0"
 bytes = "1"
+core_affinity = "0.5.10"
+diesel = { version = "1.4.7", features = ["postgres", "r2d2"] }
 futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
 mimalloc = { version = "0.1.25", default-features = false }
 rand = { version = "0.8", default-features = false, features = ["min_const_gen", "small_rng"] }
@@ -26,6 +36,6 @@ codegen-units = 1
 panic = "abort"
 
 [patch.crates-io]
-xitca-http = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "0f4ab88883ac5656403154ab82284117f289d897" }
-xitca-server = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "0f4ab88883ac5656403154ab82284117f289d897" }
-xitca-service = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "0f4ab88883ac5656403154ab82284117f289d897" }
+xitca-http = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "7499e8a5aa4f7e1f17bbf6b6ee0816828dff3149" }
+xitca-server = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "7499e8a5aa4f7e1f17bbf6b6ee0816828dff3149" }
+xitca-service = { git = "https://github.com/fakeshadow/xitca-web.git", rev = "7499e8a5aa4f7e1f17bbf6b6ee0816828dff3149" }

+ 22 - 0
frameworks/Rust/xitca-web/benchmark_config.json

@@ -23,6 +23,28 @@
         "display_name": "xitca-web",
         "notes": "",
         "versus": ""
+      },
+      "diesel": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?q=",
+        "update_url": "/updates?q=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "xitca-web",
+        "language": "Rust",
+        "orm": "Full",
+        "platform": "None",
+        "webserver": "xitca-server",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "xitca-web [diesel]",
+        "notes": "",
+        "versus": ""
       }
     }
   ]

+ 17 - 0
frameworks/Rust/xitca-web/config.toml

@@ -17,3 +17,20 @@ orm = "Raw"
 platform = "None"
 webserver = "xitca-server"
 versus = ""
+
+[diesel]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?q="
+urls.update = "/updates?q="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Full"
+platform = "None"
+webserver = "xitca-server"
+versus = ""

+ 13 - 12
frameworks/Rust/xitca-web/src/db.rs

@@ -1,7 +1,7 @@
 use std::{cell::RefCell, error::Error, fmt::Write};
 
 use ahash::AHashMap;
-use futures_util::stream::{FuturesUnordered, TryStreamExt};
+use futures_util::stream::{FuturesUnordered, StreamExt, TryStreamExt};
 use rand::{rngs::SmallRng, Rng, SeedableRng};
 use tokio::pin;
 use tokio_postgres::{types::ToSql, NoTls, Statement};
@@ -62,23 +62,25 @@ pub async fn create(config: &str) -> Client {
 type DbResult<T> = Result<T, Box<dyn Error>>;
 
 impl Client {
-    pub async fn get_world(&self) -> DbResult<World> {
-        let random_id = (self.rng.borrow_mut().gen::<u32>() % 10_000 + 1) as i32;
-        let row = self.client.query_one(&self.world, &[&random_id]).await?;
-
+    async fn query_one_world(&self, id: i32) -> DbResult<World> {
+        let stream = self.client.query_raw(&self.world, &[&id]).await?;
+        pin!(stream);
+        let row = stream.next().await.unwrap()?;
         Ok(World::new(row.get(0), row.get(1)))
     }
 
+    pub async fn get_world(&self) -> DbResult<World> {
+        let id = (self.rng.borrow_mut().gen::<u32>() % 10_000 + 1) as i32;
+        self.query_one_world(id).await
+    }
+
     pub async fn get_worlds(&self, num: u16) -> DbResult<Vec<World>> {
         let worlds = {
             let mut rng = self.rng.borrow_mut();
             (0..num)
                 .map(|_| {
-                    let w_id = (rng.gen::<u32>() % 10_000 + 1) as i32;
-                    async move {
-                        let row = self.client.query_one(&self.world, &[&w_id]).await?;
-                        Ok(World::new(row.get(0), row.get(1)))
-                    }
+                    let id = (rng.gen::<u32>() % 10_000 + 1) as i32;
+                    self.query_one_world(id)
                 })
                 .collect::<FuturesUnordered<_>>()
         };
@@ -95,8 +97,7 @@ impl Client {
                     let id = (rng.gen::<u32>() % 10_000 + 1) as i32;
                     let w_id = (rng.gen::<u32>() % 10_000 + 1) as i32;
                     async move {
-                        let row = self.client.query_one(&self.world, &[&w_id]).await?;
-                        let mut world = World::new(row.get(0), row.get(1));
+                        let mut world = self.query_one_world(w_id).await?;
                         world.randomnumber = id;
                         Ok::<_, Box<dyn Error>>(world)
                     }

+ 133 - 0
frameworks/Rust/xitca-web/src/db_diesel.rs

@@ -0,0 +1,133 @@
+use std::{error::Error, io};
+
+use diesel::{prelude::*, r2d2};
+use rand::{rngs::SmallRng, Rng, SeedableRng};
+use tokio::task::spawn_blocking;
+
+use super::ser::{Fortune, Fortunes, World};
+
+type DbResult<T> = Result<T, Box<dyn Error + Send + Sync + 'static>>;
+
+#[derive(Clone)]
+pub struct DieselPool {
+    pool: r2d2::Pool<r2d2::ConnectionManager<PgConnection>>,
+    rng: SmallRng,
+}
+
+pub fn connect(config: &str) -> io::Result<DieselPool> {
+    let manager = r2d2::ConnectionManager::new(config);
+    let pool = r2d2::Builder::new()
+        .max_size(5)
+        .min_idle(Some(5))
+        .test_on_check_out(false)
+        .idle_timeout(None)
+        .max_lifetime(None)
+        .build(manager)
+        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+
+    Ok(DieselPool {
+        pool,
+        rng: SmallRng::from_entropy(),
+    })
+}
+
+impl DieselPool {
+    pub async fn get_world(&self) -> DbResult<World> {
+        let mut this = self.clone();
+
+        spawn_blocking(move || {
+            use crate::schema::world::dsl::*;
+
+            let conn = this.pool.get()?;
+
+            let random_id = this.rng.gen_range(1..10_001);
+            let w = world
+                .filter(id.eq(random_id))
+                .load::<World>(&conn)?
+                .pop()
+                .unwrap();
+
+            Ok(w)
+        })
+        .await?
+    }
+
+    pub async fn get_worlds(&self, num: u16) -> DbResult<Vec<World>> {
+        let mut this = self.clone();
+
+        spawn_blocking(move || {
+            use crate::schema::world::dsl::*;
+
+            let conn = this.pool.get()?;
+
+            (0..num)
+                .map(|_| {
+                    let w_id = this.rng.gen_range(1..10_001);
+                    let w = world
+                        .filter(id.eq(w_id))
+                        .load::<World>(&conn)?
+                        .pop()
+                        .unwrap();
+                    Ok(w)
+                })
+                .collect()
+        })
+        .await?
+    }
+
+    pub async fn update(&self, num: u16) -> DbResult<Vec<World>> {
+        let mut this = self.clone();
+
+        spawn_blocking(move || {
+            use crate::schema::world::dsl::*;
+
+            let conn = this.pool.get()?;
+
+            let mut worlds = (0..num)
+                .map(|_| {
+                    let w_id: i32 = this.rng.gen_range(1..10_001);
+                    let mut w = world
+                        .filter(id.eq(w_id))
+                        .load::<World>(&conn)?
+                        .pop()
+                        .unwrap();
+                    w.randomnumber = this.rng.gen_range(1..10_001);
+                    Ok(w)
+                })
+                .collect::<DbResult<Vec<_>>>()?;
+
+            worlds.sort_by_key(|w| w.id);
+
+            conn.transaction::<_, diesel::result::Error, _>(|| {
+                for w in &worlds {
+                    diesel::update(world)
+                        .filter(id.eq(w.id))
+                        .set(randomnumber.eq(w.randomnumber))
+                        .execute(&conn)?;
+                }
+                Ok(())
+            })?;
+
+            Ok(worlds)
+        })
+        .await?
+    }
+
+    pub async fn tell_fortune(&self) -> DbResult<Fortunes> {
+        let this = self.clone();
+
+        spawn_blocking(move || {
+            use crate::schema::fortune::dsl::*;
+
+            let conn = this.pool.get()?;
+
+            let mut items = fortune.load::<Fortune>(&conn)?;
+
+            items.push(Fortune::new(0, "Additional fortune added at request time."));
+            items.sort_by(|it, next| it.message.cmp(&next.message));
+
+            Ok(Fortunes::new(items))
+        })
+        .await?
+    }
+}

+ 34 - 112
frameworks/Rust/xitca-web/src/main.rs

@@ -3,42 +3,57 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
 
 mod db;
 mod ser;
+mod util;
 
-use std::{cell::RefCell, cmp, convert::Infallible, error::Error, io};
+use std::{
+    error::Error,
+    future::ready,
+    io,
+    sync::{Arc, Mutex},
+};
 
-use bytes::{Bytes, BytesMut};
-use serde::Serialize;
+use bytes::Bytes;
 use xitca_http::http::{
     header::{HeaderValue, CONTENT_TYPE, SERVER},
-    Method, StatusCode,
-};
-use xitca_web::{
-    dev::fn_service,
-    request::WebRequest,
-    response::{WebResponse, WebResponseBuilder},
-    App, HttpServer,
+    Method,
 };
+use xitca_web::{dev::fn_service, request::WebRequest, App, HttpServer};
 
 use self::db::Client;
-use self::ser::{Message, Writer};
+use self::util::{
+    internal, json, json_response, not_found, plain_text, AppState, HandleResult, QueryParse,
+};
+
+type State = AppState<Client>;
 
 #[tokio::main(flavor = "current_thread")]
 async fn main() -> io::Result<()> {
     let config = "postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world";
 
+    let cores = core_affinity::get_core_ids().unwrap_or_else(Vec::new);
+    let cores = Arc::new(Mutex::new(cores));
+
     HttpServer::new(move || {
-        App::with_async_state(move || AppState::init(config)).service(fn_service(handle))
+        App::with_async_state(move || async move {
+            let client = db::create(config).await;
+            AppState::new(client)
+        })
+        .service(fn_service(handle))
     })
     .force_flat_buf()
     .max_request_headers::<8>()
+    .on_worker_start(move || {
+        if let Some(core) = cores.lock().unwrap().pop() {
+            core_affinity::set_for_current(core);
+        }
+        ready(())
+    })
     .bind("0.0.0.0:8080")?
     .run()
     .await
 }
 
-type HandleResult = Result<WebResponse, Infallible>;
-
-async fn handle(req: &mut WebRequest<'_, AppState>) -> HandleResult {
+async fn handle(req: &mut WebRequest<'_, State>) -> HandleResult {
     let inner = req.request_mut();
 
     match (inner.method(), inner.uri().path()) {
@@ -52,30 +67,14 @@ async fn handle(req: &mut WebRequest<'_, AppState>) -> HandleResult {
     }
 }
 
-fn plain_text(req: &mut WebRequest<'_, AppState>) -> HandleResult {
-    let mut res = req.as_response(Bytes::from_static(b"Hello, World!"));
-
-    res.headers_mut()
-        .append(SERVER, HeaderValue::from_static("TFB"));
-    res.headers_mut()
-        .append(CONTENT_TYPE, HeaderValue::from_static("text/plain"));
-
-    Ok(res)
-}
-
-#[inline(always)]
-fn json(req: &mut WebRequest<'_, AppState>) -> HandleResult {
-    json_response(req, &Message::new())
-}
-
-async fn db(req: &mut WebRequest<'_, AppState>) -> HandleResult {
+async fn db(req: &mut WebRequest<'_, State>) -> HandleResult {
     match req.state().client().get_world().await {
         Ok(ref world) => json_response(req, world),
         Err(_) => internal(),
     }
 }
 
-async fn fortunes(req: &mut WebRequest<'_, AppState>) -> HandleResult {
+async fn fortunes(req: &mut WebRequest<'_, State>) -> HandleResult {
     match _fortunes(req.state().client()).await {
         Ok(body) => {
             let mut res = req.as_response(body);
@@ -93,7 +92,7 @@ async fn fortunes(req: &mut WebRequest<'_, AppState>) -> HandleResult {
     }
 }
 
-async fn queries(req: &mut WebRequest<'_, AppState>) -> HandleResult {
+async fn queries(req: &mut WebRequest<'_, State>) -> HandleResult {
     let num = req.request_mut().uri().query().parse_query();
 
     match req.state().client().get_worlds(num).await {
@@ -102,7 +101,7 @@ async fn queries(req: &mut WebRequest<'_, AppState>) -> HandleResult {
     }
 }
 
-async fn updates(req: &mut WebRequest<'_, AppState>) -> HandleResult {
+async fn updates(req: &mut WebRequest<'_, State>) -> HandleResult {
     let num = req.request_mut().uri().query().parse_query();
 
     match req.state().client().update(num).await {
@@ -111,86 +110,9 @@ async fn updates(req: &mut WebRequest<'_, AppState>) -> HandleResult {
     }
 }
 
-trait QueryParse {
-    fn parse_query(self) -> u16;
-}
-
-impl QueryParse for Option<&str> {
-    fn parse_query(self) -> u16 {
-        let num = self
-            .and_then(|this| {
-                use atoi::FromRadix10;
-                this.find('q')
-                    .map(|pos| u16::from_radix_10(this.split_at(pos + 2).1.as_ref()).0)
-            })
-            .unwrap_or(1);
-
-        cmp::min(500, cmp::max(1, num))
-    }
-}
-
 #[inline]
 async fn _fortunes(client: &Client) -> Result<Bytes, Box<dyn Error>> {
     use sailfish::TemplateOnce;
     let fortunes = client.tell_fortune().await?.render_once()?;
     Ok(fortunes.into())
 }
-
-#[inline]
-fn json_response<S>(req: &mut WebRequest<'_, AppState>, value: &S) -> HandleResult
-where
-    S: ?Sized + Serialize,
-{
-    let mut writer = req.state().writer();
-    simd_json::to_writer(&mut writer, value).unwrap();
-    let body = writer.take();
-
-    let mut res = req.as_response(body);
-    res.headers_mut()
-        .append(SERVER, HeaderValue::from_static("TFB"));
-    res.headers_mut()
-        .append(CONTENT_TYPE, HeaderValue::from_static("application/json"));
-
-    Ok(res)
-}
-
-struct AppState {
-    // postgres client
-    client: Client,
-    // a re-usable buffer for write response data.
-    write_buf: RefCell<BytesMut>,
-}
-
-impl AppState {
-    async fn init(config: &str) -> Self {
-        let client = db::create(config).await;
-        let write_buf = RefCell::new(BytesMut::new());
-
-        Self { client, write_buf }
-    }
-
-    #[inline]
-    fn writer(&self) -> Writer<'_> {
-        Writer(self.write_buf.borrow_mut())
-    }
-
-    #[inline]
-    fn client(&self) -> &Client {
-        &self.client
-    }
-}
-
-macro_rules! error {
-    ($error: ident, $code: path) => {
-        fn $error() -> HandleResult {
-            Ok(WebResponseBuilder::new()
-                .status($code)
-                .header(SERVER, HeaderValue::from_static("TFB"))
-                .body(Bytes::new().into())
-                .unwrap())
-        }
-    };
-}
-
-error!(not_found, StatusCode::NOT_FOUND);
-error!(internal, StatusCode::INTERNAL_SERVER_ERROR);

+ 109 - 0
frameworks/Rust/xitca-web/src/main_diesel.rs

@@ -0,0 +1,109 @@
+#[global_allocator]
+static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
+
+#[macro_use]
+extern crate diesel;
+
+mod db_diesel;
+mod schema;
+mod ser;
+mod util;
+
+use std::{error::Error, io};
+
+use bytes::Bytes;
+use xitca_http::http::{
+    header::{HeaderValue, CONTENT_TYPE, SERVER},
+    Method,
+};
+use xitca_web::{dev::fn_service, request::WebRequest, App, HttpServer};
+
+use self::db_diesel::{connect, DieselPool};
+use self::util::{
+    internal, json, json_response, not_found, plain_text, AppState, HandleResult, QueryParse,
+};
+
+type State = AppState<DieselPool>;
+
+#[tokio::main(flavor = "current_thread")]
+async fn main() -> io::Result<()> {
+    let config = "postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world";
+
+    HttpServer::new(move || {
+        let pool = connect(config).unwrap();
+        App::with_async_state(move || {
+            let pool = pool.clone();
+            async move { AppState::new(pool.clone()) }
+        })
+        .service(fn_service(handle))
+    })
+    .force_flat_buf()
+    .max_request_headers::<8>()
+    .bind("0.0.0.0:8080")?
+    .run()
+    .await
+}
+
+async fn handle(req: &mut WebRequest<'_, State>) -> HandleResult {
+    let inner = req.request_mut();
+
+    match (inner.method(), inner.uri().path()) {
+        (&Method::GET, "/plaintext") => plain_text(req),
+        (&Method::GET, "/json") => json(req),
+        (&Method::GET, "/db") => db(req).await,
+        (&Method::GET, "/fortunes") => fortunes(req).await,
+        (&Method::GET, "/queries") => queries(req).await,
+        (&Method::GET, "/updates") => updates(req).await,
+        _ => not_found(),
+    }
+}
+
+async fn db(req: &mut WebRequest<'_, State>) -> HandleResult {
+    match req.state().client().get_world().await {
+        Ok(world) => json_response(req, &world),
+        Err(_) => internal(),
+    }
+}
+
+async fn fortunes(req: &mut WebRequest<'_, State>) -> HandleResult {
+    match _fortunes(req.state().client()).await {
+        Ok(body) => {
+            let mut res = req.as_response(body);
+
+            res.headers_mut()
+                .append(SERVER, HeaderValue::from_static("TFB"));
+            res.headers_mut().append(
+                CONTENT_TYPE,
+                HeaderValue::from_static("text/html; charset=utf-8"),
+            );
+
+            Ok(res)
+        }
+        Err(_) => internal(),
+    }
+}
+
+async fn queries(req: &mut WebRequest<'_, State>) -> HandleResult {
+    let num = req.request_mut().uri().query().parse_query();
+
+    match req.state().client().get_worlds(num).await {
+        Ok(worlds) => json_response(req, worlds.as_slice()),
+        Err(_) => internal(),
+    }
+}
+
+async fn updates(req: &mut WebRequest<'_, State>) -> HandleResult {
+    let num = req.request_mut().uri().query().parse_query();
+
+    match req.state().client().update(num).await {
+        Ok(worlds) => json_response(req, worlds.as_slice()),
+        Err(_) => internal(),
+    }
+}
+
+#[inline]
+async fn _fortunes(pool: &DieselPool) -> Result<Bytes, Box<dyn Error + Send + Sync + 'static>> {
+    use sailfish::TemplateOnce;
+    let fortunes = pool.tell_fortune().await?.render_once()?;
+    Ok(fortunes.into())
+}

+ 13 - 0
frameworks/Rust/xitca-web/src/schema.rs

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

+ 7 - 27
frameworks/Rust/xitca-web/src/ser.rs

@@ -1,7 +1,6 @@
-use std::{borrow::Cow, cell::RefMut, io};
-
-use bytes::{Bytes, BytesMut};
+use std::borrow::Cow;
 
+use diesel::Queryable;
 use sailfish::TemplateOnce;
 use serde::{Deserialize, Serialize};
 
@@ -12,7 +11,8 @@ pub struct Message {
 
 impl Message {
     #[inline]
-    pub fn new() -> Self {
+    #[allow(dead_code)]
+    pub(super) fn new() -> Self {
         Self {
             message: "Hello, World!",
         }
@@ -20,7 +20,7 @@ impl Message {
 }
 
 #[allow(non_snake_case)]
-#[derive(Serialize, Debug)]
+#[derive(Debug, Serialize, Queryable)]
 pub struct World {
     pub id: i32,
     pub randomnumber: i32,
@@ -28,11 +28,13 @@ pub struct World {
 
 impl World {
     #[inline]
+    #[allow(dead_code)]
     pub fn new(id: i32, randomnumber: i32) -> Self {
         Self { id, randomnumber }
     }
 }
 
+#[derive(Queryable)]
 pub struct Fortune {
     pub id: i32,
     pub message: Cow<'static, str>,
@@ -60,25 +62,3 @@ impl Fortunes {
         Self { items }
     }
 }
-
-pub struct Writer<'a>(pub RefMut<'a, BytesMut>);
-
-impl Writer<'_> {
-    #[inline]
-    pub fn take(mut self) -> Bytes {
-        self.0.split().freeze()
-    }
-}
-
-impl io::Write for &mut Writer<'_> {
-    #[inline]
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        self.0.extend_from_slice(buf);
-        Ok(buf.len())
-    }
-
-    #[inline]
-    fn flush(&mut self) -> io::Result<()> {
-        Ok(())
-    }
-}

+ 135 - 0
frameworks/Rust/xitca-web/src/util.rs

@@ -0,0 +1,135 @@
+use std::{
+    cell::{RefCell, RefMut},
+    cmp,
+    convert::Infallible,
+    io,
+};
+
+use bytes::{Bytes, BytesMut};
+use serde::Serialize;
+use xitca_http::http::{
+    header::{HeaderValue, CONTENT_TYPE, SERVER},
+    StatusCode,
+};
+use xitca_web::{
+    request::WebRequest,
+    response::{WebResponse, WebResponseBuilder},
+};
+
+use super::ser::Message;
+
+pub(super) type HandleResult = Result<WebResponse, Infallible>;
+
+pub(super) struct Writer<'a>(RefMut<'a, BytesMut>);
+
+impl Writer<'_> {
+    #[inline]
+    pub fn take(mut self) -> Bytes {
+        self.0.split().freeze()
+    }
+}
+
+impl io::Write for &mut Writer<'_> {
+    #[inline]
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.0.extend_from_slice(buf);
+        Ok(buf.len())
+    }
+
+    #[inline]
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+pub(super) trait QueryParse {
+    fn parse_query(self) -> u16;
+}
+
+impl QueryParse for Option<&str> {
+    fn parse_query(self) -> u16 {
+        let num = self
+            .and_then(|this| {
+                use atoi::FromRadix10;
+                this.find('q')
+                    .map(|pos| u16::from_radix_10(this.split_at(pos + 2).1.as_ref()).0)
+            })
+            .unwrap_or(1);
+
+        cmp::min(500, cmp::max(1, num))
+    }
+}
+
+pub(super) struct AppState<C> {
+    client: C,
+    // a re-usable buffer for write response data.
+    write_buf: RefCell<BytesMut>,
+}
+
+impl<C> AppState<C> {
+    pub(super) fn new(client: C) -> Self {
+        let write_buf = RefCell::new(BytesMut::new());
+        Self { client, write_buf }
+    }
+
+    #[inline]
+    pub(super) fn writer(&self) -> Writer<'_> {
+        Writer(self.write_buf.borrow_mut())
+    }
+
+    #[inline]
+    pub(super) fn client(&self) -> &C {
+        &self.client
+    }
+}
+
+pub(super) fn plain_text<D>(req: &mut WebRequest<'_, D>) -> HandleResult {
+    let mut res = req.as_response(Bytes::from_static(b"Hello, World!"));
+
+    res.headers_mut()
+        .append(SERVER, HeaderValue::from_static("TFB"));
+    res.headers_mut()
+        .append(CONTENT_TYPE, HeaderValue::from_static("text/plain"));
+
+    Ok(res)
+}
+
+#[inline(always)]
+pub(super) fn json<D>(req: &mut WebRequest<'_, AppState<D>>) -> HandleResult {
+    json_response(req, &Message::new())
+}
+
+#[inline]
+pub(super) fn json_response<S, D>(req: &mut WebRequest<'_, AppState<D>>, value: &S) -> HandleResult
+where
+    S: ?Sized + Serialize,
+{
+    let mut writer = req.state().writer();
+    simd_json::to_writer(&mut writer, value).unwrap();
+    let body = writer.take();
+
+    let mut res = req.as_response(body);
+    res.headers_mut()
+        .append(SERVER, HeaderValue::from_static("TFB"));
+    res.headers_mut()
+        .append(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+
+    Ok(res)
+}
+
+macro_rules! error {
+    ($error: ident, $code: path) => {
+        #[cold]
+        #[inline(never)]
+        pub(super) fn $error() -> HandleResult {
+            Ok(WebResponseBuilder::new()
+                .status($code)
+                .header(SERVER, HeaderValue::from_static("TFB"))
+                .body(Bytes::new().into())
+                .unwrap())
+        }
+    };
+}
+
+error!(not_found, StatusCode::NOT_FOUND);
+error!(internal, StatusCode::INTERNAL_SERVER_ERROR);

+ 14 - 0
frameworks/Rust/xitca-web/xitca-web-diesel.dockerfile

@@ -0,0 +1,14 @@
+FROM rust:1.54
+
+RUN apt-get update -yqq && apt-get install -yqq cmake g++
+
+ADD ./ /xitca-web
+WORKDIR /xitca-web
+
+RUN cargo clean
+RUN rustup default nightly-2021-08-03
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin xitca-web-diesel
+
+EXPOSE 8080
+
+CMD ./target/release/xitca-web-diesel

+ 3 - 3
frameworks/Rust/xitca-web/xitca-web.dockerfile

@@ -1,4 +1,4 @@
-FROM rustlang/rust:nightly-slim
+FROM rust:1.54
 
 RUN apt-get update -yqq && apt-get install -yqq cmake g++
 
@@ -6,8 +6,8 @@ ADD ./ /xitca-web
 WORKDIR /xitca-web
 
 RUN cargo clean
-RUN rustup default nightly-2021-07-26
-RUN RUSTFLAGS="-C target-cpu=native" cargo build --release
+RUN rustup default nightly-2021-08-03
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release --bin xitca-web
 
 EXPOSE 8080