Browse Source

[xitca-web] add connection pool (#9252)

* [xitca-web] connection pool

* fix plaintext.

* update dep

* simplify api
fakeshadow 11 months ago
parent
commit
91cefe6b13

+ 34 - 9
frameworks/Rust/xitca-web/Cargo.lock

@@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
 name = "async-trait"
-version = "0.1.81"
+version = "0.1.82"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
+checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -149,9 +149,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
 
 [[package]]
 name = "cc"
-version = "1.1.15"
+version = "1.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
+checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b"
 dependencies = [
  "shlex",
 ]
@@ -218,9 +218,9 @@ dependencies = [
 
 [[package]]
 name = "diesel"
-version = "2.2.3"
+version = "2.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65e13bab2796f412722112327f3e575601a3e9cdcbe426f0d30dbf43f3f5dc71"
+checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e"
 dependencies = [
  "bitflags 2.6.0",
  "byteorder",
@@ -631,6 +631,24 @@ version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
+[[package]]
+name = "phf"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+dependencies = [
+ "siphasher",
+]
+
 [[package]]
 name = "pin-project"
 version = "1.1.5"
@@ -869,9 +887,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.127"
+version = "1.0.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
+checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
 dependencies = [
  "itoa",
  "memchr",
@@ -927,6 +945,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
 [[package]]
 name = "slab"
 version = "0.4.9"
@@ -1319,10 +1343,11 @@ dependencies = [
 [[package]]
 name = "xitca-postgres"
 version = "0.1.0"
-source = "git+https://github.com/HFQR/xitca-web.git?rev=6870448#687044829f0e89bab3efafcf7fb53e53167ad32c"
+source = "git+https://github.com/HFQR/xitca-web.git?rev=9835c0b#9835c0b79ebd77f01b74e3ccb1c9893f022db9f3"
 dependencies = [
  "fallible-iterator",
  "percent-encoding",
+ "phf",
  "postgres-protocol",
  "postgres-types",
  "tokio",

+ 1 - 1
frameworks/Rust/xitca-web/Cargo.toml

@@ -95,5 +95,5 @@ codegen-units = 1
 panic = "abort"
 
 [patch.crates-io]
-xitca-postgres = { git = "https://github.com/HFQR/xitca-web.git", rev = "6870448" }
+xitca-postgres = { git = "https://github.com/HFQR/xitca-web.git", rev = "9835c0b" }
 mio = { git = "https://github.com/fakeshadow/mio", rev = "9bae6012b7ecfc6083350785f71a5e8265358178" }

+ 1 - 0
frameworks/Rust/xitca-web/rustfmt.toml

@@ -0,0 +1 @@
+max_width = 120

+ 97 - 72
frameworks/Rust/xitca-web/src/db.rs

@@ -1,9 +1,7 @@
 use std::fmt::Write;
 
 use xitca_io::bytes::BytesMut;
-use xitca_postgres::{
-    pipeline::Pipeline, statement::Statement, AsyncLendingIterator, SharedClient,
-};
+use xitca_postgres::{pipeline::Pipeline, AsyncLendingIterator, Pool, Type};
 
 use super::{
     ser::{Fortune, Fortunes, World},
@@ -11,64 +9,63 @@ use super::{
 };
 
 pub struct Client {
-    client: SharedClient,
+    pool: Pool,
     #[cfg(not(feature = "pg-sync"))]
     shared: std::cell::RefCell<Shared>,
     #[cfg(feature = "pg-sync")]
     shared: std::sync::Mutex<Shared>,
-    fortune: Statement,
-    world: Statement,
-    updates: Box<[Statement]>,
+    updates: Box<[Box<str>]>,
 }
 
 type Shared = (Rand, BytesMut);
 
-pub async fn create() -> HandleResult<Client> {
-    let mut client = SharedClient::new(DB_URL.to_string()).await?;
+const FORTUNE_SQL: &str = "SELECT * FROM fortune";
 
-    let fortune = client.prepare_cached("SELECT * FROM fortune", &[]).await?;
+const FORTUNE_SQL_TYPES: &[Type] = &[];
 
-    let world = client
-        .prepare_cached("SELECT * FROM world WHERE id=$1", &[])
-        .await?;
+const WORLD_SQL: &str = "SELECT * FROM world WHERE id=$1";
 
-    let mut updates = Vec::new();
+const WORLD_SQL_TYPES: &[Type] = &[Type::INT4];
 
-    // a dummy statement as placeholder of 0 index.
-    // avoid off by one calculation when using non zero u16 as slicing index.
-    updates.push(Statement::default());
+fn update_query(num: usize) -> Box<str> {
+    const PREFIX: &str = "UPDATE world SET randomNumber = w.r FROM (VALUES ";
+    const SUFFIX: &str = ") AS w (i,r) WHERE world.id = w.i";
 
-    for num in 1..=500u16 {
-        let mut pl = 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(')');
+    let (_, mut query) = (1..=num).fold((1, String::from(PREFIX)), |(idx, mut query), _| {
+        write!(query, "(${}::int,${}::int),", idx, idx + 1).unwrap();
+        (idx + 2, query)
+    });
 
-        let st = client.prepare_cached(&q, &[]).await?;
-        updates.push(st);
-    }
+    query.pop();
+
+    query.push_str(SUFFIX);
+
+    query.into_boxed_str()
+}
+
+pub async fn create() -> HandleResult<Client> {
+    let pool = Pool::builder(DB_URL).capacity(1).build()?;
 
     let shared = (Rand::default(), BytesMut::new());
 
+    let updates = core::iter::once(Box::from(""))
+        .chain((1..=500).map(update_query))
+        .collect::<Box<[Box<str>]>>();
+
+    {
+        let mut conn = pool.get().await?;
+        for update in updates.iter().skip(1) {
+            conn.prepare(update, &[]).await?;
+        }
+    }
+
     Ok(Client {
-        client,
+        pool,
         #[cfg(not(feature = "pg-sync"))]
         shared: std::cell::RefCell::new(shared),
         #[cfg(feature = "pg-sync")]
         shared: std::sync::Mutex::new(shared),
-        fortune,
-        world,
-        updates: updates.into_boxed_slice(),
+        updates,
     })
 }
 
@@ -84,29 +81,26 @@ impl Client {
     }
 
     pub async fn get_world(&self) -> HandleResult<World> {
+        let mut conn = self.pool.get().await?;
+        let stmt = conn.prepare(WORLD_SQL, WORLD_SQL_TYPES).await?;
         let id = self.shared().0.gen_id();
-        self.client
-            .query_raw(&self.world, [id])
-            .await?
-            .try_next()
-            .await?
-            .map(|row| World::new(row.get_raw(0), row.get_raw(1)))
-            .ok_or_else(|| "World does not exist".into())
+        let mut res = conn.consume().query_raw(&stmt, [id])?;
+        let row = res.try_next().await?.ok_or_else(|| "World does not exist")?;
+        Ok(World::new(row.get_raw(0), row.get_raw(1)))
     }
 
     pub async fn get_worlds(&self, num: u16) -> HandleResult<Vec<World>> {
         let len = num as usize;
 
+        let mut conn = self.pool.get().await?;
+        let stmt = conn.prepare(WORLD_SQL, WORLD_SQL_TYPES).await?;
+
         let mut res = {
             let (ref mut rng, ref mut buf) = *self.shared();
-
             let mut pipe = Pipeline::with_capacity_from_buf(len, buf);
-
-            (0..num).try_for_each(|_| pipe.query_raw(&self.world, [rng.gen_id()]))?;
-
-            self.client.pipeline(pipe)
-        }
-        .await?;
+            (0..num).try_for_each(|_| pipe.query_raw(&stmt, [rng.gen_id()]))?;
+            conn.consume().pipeline(pipe)?
+        };
 
         let mut worlds = Vec::with_capacity(len);
 
@@ -122,37 +116,34 @@ impl Client {
     pub async fn update(&self, num: u16) -> HandleResult<Vec<World>> {
         let len = num as usize;
 
-        let mut params = Vec::new();
-        params.reserve(len * 3);
+        let update = self.updates.get(len).ok_or_else(|| "num out of bound")?;
+
+        let mut conn = self.pool.get().await?;
+        let world_stmt = conn.prepare(WORLD_SQL, WORLD_SQL_TYPES).await?;
+        let update_stmt = conn.prepare(&update, &[]).await?;
+
+        let mut params = Vec::with_capacity(len);
 
         let mut res = {
             let (ref mut rng, ref mut buf) = *self.shared();
-
             let mut pipe = Pipeline::with_capacity_from_buf(len + 1, buf);
-
             (0..num).try_for_each(|_| {
                 let w_id = rng.gen_id();
                 let r_id = rng.gen_id();
-                params.extend([w_id, r_id]);
-                pipe.query_raw(&self.world, [w_id])
+                params.push([w_id, r_id]);
+                pipe.query_raw(&world_stmt, [w_id])
             })?;
+            pipe.query_raw(&update_stmt, sort_update_params(&params))?;
+            conn.consume().pipeline(pipe)?
+        };
 
-            params.extend_from_within(..len);
-
-            let st = self.updates.get(len).unwrap();
-            pipe.query_raw(st, &params)?;
-
-            self.client.pipeline(pipe)
-        }
-        .await?;
+        let mut worlds = Vec::with_capacity(len);
 
-        let mut worlds = Vec::new();
-        worlds.reserve(len);
-        let mut r_ids = params.into_iter().skip(1).step_by(2);
+        let mut r_ids = params.into_iter();
 
         while let Some(mut item) = res.try_next().await? {
             while let Some(row) = item.try_next().await? {
-                let r_id = r_ids.next().unwrap();
+                let r_id = r_ids.next().unwrap()[1];
                 worlds.push(World::new(row.get_raw(0), r_id))
             }
         }
@@ -164,12 +155,46 @@ impl Client {
         let mut items = Vec::with_capacity(32);
         items.push(Fortune::new(0, "Additional fortune added at request time."));
 
-        let mut res = self.client.query_raw::<[i32; 0]>(&self.fortune, []).await?;
+        let mut conn = self.pool.get().await?;
+        let stmt = conn.prepare(FORTUNE_SQL, FORTUNE_SQL_TYPES).await?;
+        let mut res = conn.consume().query_raw::<[i32; 0]>(&stmt, [])?;
+
         while let Some(row) = res.try_next().await? {
             items.push(Fortune::new(row.get_raw(0), row.get_raw::<String>(1)));
         }
+
         items.sort_by(|it, next| it.message.cmp(&next.message));
 
         Ok(Fortunes::new(items))
     }
 }
+
+fn sort_update_params(params: &Vec<[i32; 2]>) -> impl ExactSizeIterator<Item = i32> {
+    let mut params = params.clone();
+    params.sort_by(|a, b| a[0].cmp(&b[0]));
+
+    struct ParamIter<I>(I);
+
+    impl<I> Iterator for ParamIter<I>
+    where
+        I: Iterator,
+    {
+        type Item = I::Item;
+
+        #[inline]
+        fn next(&mut self) -> Option<Self::Item> {
+            self.0.next()
+        }
+
+        #[inline]
+        fn size_hint(&self) -> (usize, Option<usize>) {
+            self.0.size_hint()
+        }
+    }
+
+    // impl depends on compiler optimization to flat Vec<[T]> to Vec<T> when inferring
+    // it's size hint. possible to cause runtime panic.
+    impl<I> ExactSizeIterator for ParamIter<I> where I: Iterator {}
+
+    ParamIter(params.into_iter().flatten())
+}

+ 1 - 5
frameworks/Rust/xitca-web/src/db_diesel.rs

@@ -43,11 +43,7 @@ impl _Pool {
 
         let w_id = self.rng.lock().unwrap().gen_id();
         let mut conn = self.pool.get()?;
-        world
-            .filter(id.eq(w_id))
-            .load(&mut conn)?
-            .pop()
-            .ok_or_else(not_found)
+        world.filter(id.eq(w_id)).load(&mut conn)?.pop().ok_or_else(not_found)
     }
 
     pub fn get_worlds(&self, num: u16) -> HandleResult<Vec<World>> {

+ 25 - 30
frameworks/Rust/xitca-web/src/main.rs

@@ -3,26 +3,20 @@ mod ser;
 mod util;
 
 use xitca_http::{
-    body::Once,
-    bytes::Bytes,
     h1::RequestBody,
-    http::{
-        self,
-        const_header_value::{TEXT, TEXT_HTML_UTF8},
-        header::{CONTENT_TYPE, SERVER},
-        IntoResponse, RequestExt,
+    http::{header::SERVER, StatusCode},
+    util::service::{
+        route::get,
+        router::{Router, RouterError},
     },
-    util::service::{route::get, router::Router},
     HttpServiceBuilder,
 };
 use xitca_service::{fn_service, Service, ServiceExt};
 
-use ser::{json_response, Message};
+use ser::{error_response, IntoResponse, Message, Request, Response};
 use util::{context_mw, HandleResult, QueryParse, SERVER_HEADER_VALUE};
 
-type Request = http::Request<RequestExt<RequestBody>>;
-type Response = http::Response<Once<Bytes>>;
-type Ctx<'a> = util::Ctx<'a, Request>;
+type Ctx<'a> = util::Ctx<'a, Request<RequestBody>>;
 
 fn main() -> std::io::Result<()> {
     let service = Router::new()
@@ -32,7 +26,7 @@ fn main() -> std::io::Result<()> {
         .insert("/fortunes", get(fn_service(fortunes)))
         .insert("/queries", get(fn_service(queries)))
         .insert("/updates", get(fn_service(updates)))
-        .enclosed_fn(middleware_fn)
+        .enclosed_fn(middleware)
         .enclosed(context_mw())
         .enclosed(HttpServiceBuilder::h1().io_uring());
     xitca_server::Builder::new()
@@ -41,53 +35,54 @@ fn main() -> std::io::Result<()> {
         .wait()
 }
 
-async fn middleware_fn<S, E>(service: &S, req: Ctx<'_>) -> Result<Response, E>
+async fn middleware<S>(service: &S, req: Ctx<'_>) -> Result<Response, core::convert::Infallible>
 where
-    S: for<'c> Service<Ctx<'c>, Response = Response, Error = E>,
+    S: for<'c> Service<Ctx<'c>, Response = Response, Error = RouterError<util::Error>>,
 {
-    service.call(req).await.map(|mut res| {
-        res.headers_mut().insert(SERVER, SERVER_HEADER_VALUE);
-        res
-    })
+    let mut res = service.call(req).await.unwrap_or_else(|e| match e {
+        RouterError::Match(_) => error_response(StatusCode::NOT_FOUND),
+        RouterError::NotAllowed(_) => error_response(StatusCode::METHOD_NOT_ALLOWED),
+        RouterError::Service(e) => {
+            println!("{e}");
+            error_response(StatusCode::INTERNAL_SERVER_ERROR)
+        }
+    });
+    res.headers_mut().insert(SERVER, SERVER_HEADER_VALUE);
+    Ok(res)
 }
 
 async fn plain_text(ctx: Ctx<'_>) -> HandleResult<Response> {
-    let (req, _) = ctx.into_parts();
-    let mut res = req.into_response(const { Bytes::from_static(b"Hello, World!") });
-    res.headers_mut().insert(CONTENT_TYPE, TEXT);
-    Ok(res)
+    ctx.into_parts().0.text_response()
 }
 
 async fn json(ctx: Ctx<'_>) -> HandleResult<Response> {
     let (req, state) = ctx.into_parts();
-    json_response(req, &mut state.write_buf.borrow_mut(), &Message::new())
+    req.json_response(state, &Message::new())
 }
 
 async fn db(ctx: Ctx<'_>) -> HandleResult<Response> {
     let (req, state) = ctx.into_parts();
     let world = state.client.get_world().await?;
-    json_response(req, &mut state.write_buf.borrow_mut(), &world)
+    req.json_response(state, &world)
 }
 
 async fn fortunes(ctx: Ctx<'_>) -> HandleResult<Response> {
     let (req, state) = ctx.into_parts();
     use sailfish::TemplateOnce;
     let fortunes = state.client.tell_fortune().await?.render_once()?;
-    let mut res = req.into_response(Bytes::from(fortunes));
-    res.headers_mut().insert(CONTENT_TYPE, TEXT_HTML_UTF8);
-    Ok(res)
+    req.html_response(fortunes)
 }
 
 async fn queries(ctx: Ctx<'_>) -> HandleResult<Response> {
     let (req, state) = ctx.into_parts();
     let num = req.uri().query().parse_query();
     let worlds = state.client.get_worlds(num).await?;
-    json_response(req, &mut state.write_buf.borrow_mut(), worlds.as_slice())
+    req.json_response(state, &worlds)
 }
 
 async fn updates(ctx: Ctx<'_>) -> HandleResult<Response> {
     let (req, state) = ctx.into_parts();
     let num = req.uri().query().parse_query();
     let worlds = state.client.update(num).await?;
-    json_response(req, &mut state.write_buf.borrow_mut(), worlds.as_slice())
+    req.json_response(state, &worlds)
 }

+ 5 - 19
frameworks/Rust/xitca-web/src/main_axum.rs

@@ -99,9 +99,7 @@ mod tower_compat {
         HttpServiceBuilder,
     };
     use xitca_io::net::io_uring::TcpStream;
-    use xitca_service::{
-        fn_build, middleware::UncheckedReady, ready::ReadyService, Service, ServiceExt,
-    };
+    use xitca_service::{fn_build, middleware::UncheckedReady, ready::ReadyService, Service, ServiceExt};
     use xitca_web::service::tower_http_compat::{CompatReqBody, CompatResBody};
 
     pub struct TowerHttp<S, B> {
@@ -112,17 +110,11 @@ mod tower_compat {
     impl<S, B> TowerHttp<S, B> {
         pub fn service<F, Fut>(
             func: F,
-        ) -> impl Service<
-            Response = impl ReadyService + Service<(TcpStream, SocketAddr)>,
-            Error = impl fmt::Debug,
-        >
+        ) -> impl Service<Response = impl ReadyService + Service<(TcpStream, SocketAddr)>, Error = impl fmt::Debug>
         where
             F: Fn() -> Fut + Send + Sync + Clone,
             Fut: Future<Output = Result<S, crate::util::Error>>,
-            S: tower::Service<
-                Request<CompatReqBody<RequestExt<RequestBody>, ()>>,
-                Response = Response<B>,
-            >,
+            S: tower::Service<Request<CompatReqBody<RequestExt<RequestBody>, ()>>, Response = Response<B>>,
             S::Error: fmt::Debug,
             B: Body<Data = Bytes> + Send + 'static,
         {
@@ -142,18 +134,12 @@ mod tower_compat {
 
     impl<S, B> Service<Request<RequestExt<RequestBody>>> for TowerHttp<S, B>
     where
-        S: tower::Service<
-            Request<CompatReqBody<RequestExt<RequestBody>, ()>>,
-            Response = Response<B>,
-        >,
+        S: tower::Service<Request<CompatReqBody<RequestExt<RequestBody>, ()>>, Response = Response<B>>,
     {
         type Response = Response<CompatResBody<B>>;
         type Error = S::Error;
 
-        async fn call(
-            &self,
-            req: Request<RequestExt<RequestBody>>,
-        ) -> Result<Self::Response, Self::Error> {
+        async fn call(&self, req: Request<RequestExt<RequestBody>>) -> Result<Self::Response, Self::Error> {
             let (parts, ext) = req.into_parts();
             let req = Request::from_parts(parts, CompatReqBody::new(ext, ()));
             let fut = self.service.borrow_mut().call(req);

+ 14 - 42
frameworks/Rust/xitca-web/src/main_iou.rs

@@ -12,31 +12,22 @@ use std::{convert::Infallible, fmt, future::poll_fn, io, pin::pin};
 
 use futures_core::stream::Stream;
 use xitca_http::{
-    body::Once,
     date::DateTimeService,
     h1::proto::context::Context,
-    http::{
-        self,
-        const_header_value::{TEXT, TEXT_HTML_UTF8},
-        header::{CONTENT_TYPE, SERVER},
-        IntoResponse, RequestExt, StatusCode,
-    },
+    http::{header::SERVER, StatusCode},
 };
 use xitca_io::{
-    bytes::{Bytes, BytesMut},
+    bytes::BytesMut,
     io_uring::BoundedBuf,
     net::{io_uring::TcpStream as IOUTcpStream, TcpStream},
 };
 use xitca_service::{fn_build, fn_service, middleware::UncheckedReady, Service, ServiceExt};
 
 use self::{
-    ser::{json_response, Message},
+    ser::{error_response, IntoResponse, Message, Request, Response},
     util::{context_mw, Ctx, QueryParse, SERVER_HEADER_VALUE},
 };
 
-type Request = http::Request<RequestExt<()>>;
-type Response = http::Response<Once<Bytes>>;
-
 fn main() -> io::Result<()> {
     let service = fn_service(handler)
         .enclosed(context_mw())
@@ -53,47 +44,31 @@ fn main() -> io::Result<()> {
         .wait()
 }
 
-async fn handler(ctx: Ctx<'_, Request>) -> Result<Response, Infallible> {
+async fn handler(ctx: Ctx<'_, Request<()>>) -> Result<Response, Infallible> {
     let (req, state) = ctx.into_parts();
     let mut res = match req.uri().path() {
-        "/plaintext" => {
-            let mut res = req.into_response(const { Bytes::from_static(b"Hello, World!") });
-            res.headers_mut().insert(CONTENT_TYPE, TEXT);
-            res
-        }
-        "/json" => json_response(req, &mut state.write_buf.borrow_mut(), &Message::new()).unwrap(),
+        "/plaintext" => req.text_response().unwrap(),
+        "/json" => req.json_response(state, &Message::new()).unwrap(),
         "/db" => {
             let world = state.client.get_world().await.unwrap();
-            json_response(req, &mut state.write_buf.borrow_mut(), &world).unwrap()
+            req.json_response(state, &world).unwrap()
         }
         "/queries" => {
             let num = req.uri().query().parse_query();
             let worlds = state.client.get_worlds(num).await.unwrap();
-            json_response(req, &mut state.write_buf.borrow_mut(), worlds.as_slice()).unwrap()
+            req.json_response(state, &worlds).unwrap()
         }
         "/updates" => {
             let num = req.uri().query().parse_query();
             let worlds = state.client.update(num).await.unwrap();
-            json_response(req, &mut state.write_buf.borrow_mut(), worlds.as_slice()).unwrap()
+            req.json_response(state, &worlds).unwrap()
         }
         "/fortunes" => {
             use sailfish::TemplateOnce;
-            let fortunes = state
-                .client
-                .tell_fortune()
-                .await
-                .unwrap()
-                .render_once()
-                .unwrap();
-            let mut res = req.into_response(Bytes::from(fortunes));
-            res.headers_mut().append(CONTENT_TYPE, TEXT_HTML_UTF8);
-            res
-        }
-        _ => {
-            let mut res = req.into_response(Bytes::new());
-            *res.status_mut() = StatusCode::NOT_FOUND;
-            res
+            let fortunes = state.client.tell_fortune().await.unwrap().render_once().unwrap();
+            req.html_response(fortunes).unwrap()
         }
+        _ => error_response(StatusCode::NOT_FOUND),
     };
     res.headers_mut().insert(SERVER, SERVER_HEADER_VALUE);
     Ok(res)
@@ -107,7 +82,7 @@ struct Http1IOU<S> {
 // runner for http service.
 impl<S> Service<TcpStream> for Http1IOU<S>
 where
-    S: Service<Request, Response = Response>,
+    S: Service<Request<()>, Response = Response>,
     S::Error: fmt::Debug,
 {
     type Response = ();
@@ -138,10 +113,7 @@ where
                 let (parts, body) = self.service.call(req).await.unwrap().into_parts();
                 let mut encoder = ctx.encode_head(parts, &body, &mut write_buf).unwrap();
                 let mut body = pin!(body);
-                let chunk = poll_fn(|cx| body.as_mut().poll_next(cx))
-                    .await
-                    .unwrap()
-                    .unwrap();
+                let chunk = poll_fn(|cx| body.as_mut().poll_next(cx)).await.unwrap().unwrap();
                 encoder.encode(chunk, &mut write_buf);
                 encoder.encode_eof(&mut write_buf);
             }

+ 2 - 8
frameworks/Rust/xitca-web/src/main_sync.rs

@@ -51,17 +51,11 @@ fn fortunes(StateOwn(pool): StateOwn<Pool>) -> HandleResult<Html<String>> {
 }
 
 #[route("/queries", method = get)]
-fn queries(
-    Query(Num(num)): Query<Num>,
-    StateOwn(pool): StateOwn<Pool>,
-) -> HandleResult<Json<impl Serialize>> {
+fn queries(Query(Num(num)): Query<Num>, StateOwn(pool): StateOwn<Pool>) -> HandleResult<Json<impl Serialize>> {
     pool.get_worlds(num).map(Json)
 }
 
 #[route("/updates", method = get)]
-fn updates(
-    Query(Num(num)): Query<Num>,
-    StateOwn(pool): StateOwn<Pool>,
-) -> HandleResult<Json<impl Serialize>> {
+fn updates(Query(Num(num)): Query<Num>, StateOwn(pool): StateOwn<Pool>) -> HandleResult<Json<impl Serialize>> {
     pool.update(num).map(Json)
 }

+ 53 - 20
frameworks/Rust/xitca-web/src/ser.rs

@@ -5,11 +5,19 @@ use std::borrow::Cow;
 use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
 use xitca_http::{
     body::Once,
-    bytes::{BufMutWriter, Bytes, BytesMut},
-    http::{const_header_value::JSON, header::CONTENT_TYPE, IntoResponse, Request, Response},
+    bytes::{BufMutWriter, Bytes},
+    http::{
+        self,
+        const_header_value::{JSON, TEXT, TEXT_HTML_UTF8},
+        header::CONTENT_TYPE,
+        IntoResponse as _, RequestExt, StatusCode,
+    },
 };
 
-use crate::util::Error;
+use crate::util::{Error, State};
+
+const HELLO: &str = "Hello, World!";
+const HELLO_BYTES: &[u8] = HELLO.as_bytes();
 
 #[derive(Clone)]
 pub struct Message {
@@ -19,9 +27,7 @@ pub struct Message {
 impl Message {
     #[inline]
     pub const fn new() -> Self {
-        Self {
-            message: "Hello, World!",
-        }
+        Self { message: HELLO }
     }
 }
 
@@ -128,8 +134,7 @@ impl<'de> Deserialize<'de> for Num {
             where
                 V: MapAccess<'de>,
             {
-                map.next_key::<Field>()?
-                    .ok_or_else(|| Error::missing_field("q"))?;
+                map.next_key::<Field>()?.ok_or_else(|| Error::missing_field("q"))?;
                 let q = map.next_value::<u16>().unwrap_or(1);
                 let q = cmp::min(500, cmp::max(1, q));
                 Ok(Num(q))
@@ -163,16 +168,44 @@ impl Serialize for World {
     }
 }
 
-pub fn json_response<Ext, S>(
-    req: Request<Ext>,
-    buf: &mut BytesMut,
-    value: &S,
-) -> Result<Response<Once<Bytes>>, Error>
-where
-    S: ?Sized + Serialize,
-{
-    serde_json::to_writer(BufMutWriter(buf), value)?;
-    let mut res = req.into_response(buf.split().freeze());
-    res.headers_mut().insert(CONTENT_TYPE, JSON);
-    Ok(res)
+pub type Request<B> = http::Request<RequestExt<B>>;
+pub type Response = http::Response<Once<Bytes>>;
+
+pub trait IntoResponse: Sized {
+    fn json_response<C>(self, state: &State<C>, val: &impl Serialize) -> Result<Response, Error>;
+
+    fn text_response(self) -> Result<Response, Error>;
+
+    fn html_response(self, val: String) -> Result<Response, Error>;
+}
+
+impl<Ext> IntoResponse for Request<Ext> {
+    fn json_response<C>(self, state: &State<C>, val: &impl Serialize) -> Result<Response, Error> {
+        let buf = &mut *state.write_buf.borrow_mut();
+        serde_json::to_writer(BufMutWriter(buf), val)?;
+        let mut res = self.into_response(buf.split().freeze());
+        res.headers_mut().insert(CONTENT_TYPE, JSON);
+        Ok(res)
+    }
+
+    fn text_response(self) -> Result<Response, Error> {
+        let mut res = self.into_response(const { Bytes::from_static(HELLO_BYTES) });
+        res.headers_mut().insert(CONTENT_TYPE, TEXT);
+        Ok(res)
+    }
+
+    fn html_response(self, val: String) -> Result<Response, Error> {
+        let mut res = self.into_response(Bytes::from(val));
+        res.headers_mut().insert(CONTENT_TYPE, TEXT_HTML_UTF8);
+        Ok(res)
+    }
+}
+
+#[cold]
+#[inline(never)]
+pub fn error_response(status: StatusCode) -> Response {
+    http::Response::builder()
+        .status(status)
+        .body(Once::new(Bytes::new()))
+        .unwrap()
 }

+ 1 - 3
frameworks/Rust/xitca-web/src/util.rs

@@ -65,9 +65,7 @@ mod non_wasm {
 
         pub type Ctx<'a, Req> = Context<'a, Req, State<Client>>;
 
-        pub fn context_mw(
-        ) -> ContextBuilder<impl Fn() -> Pin<Box<dyn Future<Output = HandleResult<State<Client>>>>>>
-        {
+        pub fn context_mw() -> ContextBuilder<impl Fn() -> Pin<Box<dyn Future<Output = HandleResult<State<Client>>>>>> {
             ContextBuilder::new(|| {
                 Box::pin(async {
                     db::create().await.map(|client| State {