diff --git a/Cargo.toml b/Cargo.toml index aa80b395..52095f28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -194,17 +194,19 @@ pretty_env_logger = "0.5" # for benchmarks criterion = "0.8.2" -reqwest = { version = "0.13", default-features = false, features = ["rustls", "http2"] } +reqwest = { version = "0.13", default-features = false, features = ["rustls"] } [[bench]] name = "http1" path = "bench/http1.rs" harness = false +required-features = ["wreq/stream", "reqwest/stream"] [[bench]] name = "http2" path = "bench/http2.rs" harness = false +required-features = ["wreq/stream", "reqwest/stream", "reqwest/http2"] [[test]] name = "cookie" diff --git a/bench/http1.rs b/bench/http1.rs index 0d840f97..3b619fb9 100644 --- a/bench/http1.rs +++ b/bench/http1.rs @@ -3,149 +3,29 @@ mod support; use std::time::Duration; -use support::client::{bench_reqwest, bench_wreq}; -use support::server::{spawn_multi_thread_server, spawn_single_thread_server, with_server}; -use support::{HttpMode, build_current_thread_runtime, build_multi_thread_runtime}; +use support::{HttpMode, bench}; -use criterion::{ - BenchmarkGroup, Criterion, criterion_group, criterion_main, measurement::WallTime, -}; +use criterion::{Criterion, criterion_group, criterion_main}; -const NUM_REQUESTS_TO_SEND: usize = 1000; -const CONCURRENT_LIMIT: usize = 100; const HTTP_MODE: HttpMode = HttpMode::Http1; -const CURRENT_THREAD_LABEL: &str = "current_thread"; -const MULTI_THREAD_LABEL: &str = "multi_thread"; +const ADDR: &str = "127.0.0.1:5928"; -fn run_benches( - group: &mut BenchmarkGroup<'_, WallTime>, - rt: fn() -> tokio::runtime::Runtime, - addr: &'static str, - label_prefix: &str, -) { - let runtime = rt(); - bench_wreq( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - false, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); - - bench_reqwest( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - false, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); - - bench_wreq( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - true, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); - - bench_reqwest( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - true, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); -} - -// Criterion benchmark functions +#[inline] fn bench_server_single_thread(c: &mut Criterion) { - let mut group = c.benchmark_group("server_single_thread"); - group.sample_size(10); - - // single-threaded client - with_server( - "127.0.0.1:5928", - HTTP_MODE, - spawn_single_thread_server, - || { - run_benches( - &mut group, - build_current_thread_runtime, - "127.0.0.1:5928", - CURRENT_THREAD_LABEL, - ); - }, - ); - - // multi-threaded client - with_server( - "127.0.0.1:5930", - HTTP_MODE, - spawn_single_thread_server, - || { - run_benches( - &mut group, - build_multi_thread_runtime, - "127.0.0.1:5930", - MULTI_THREAD_LABEL, - ); - }, - ); - group.finish(); + bench::bench_server_single_thread(c, HTTP_MODE, ADDR) + .expect("Failed to run single-threaded HTTP/1 benchmark server") } +#[inline] fn bench_server_multi_thread(c: &mut Criterion) { - let mut group = c.benchmark_group("server_multi_thread"); - group.sample_size(10); - - // single-threaded client - with_server( - "127.0.0.1:5929", - HTTP_MODE, - spawn_multi_thread_server, - || { - run_benches( - &mut group, - build_current_thread_runtime, - "127.0.0.1:5929", - CURRENT_THREAD_LABEL, - ); - }, - ); - - // multi-threaded client - with_server( - "127.0.0.1:5931", - HTTP_MODE, - spawn_multi_thread_server, - || { - run_benches( - &mut group, - build_multi_thread_runtime, - "127.0.0.1:5931", - MULTI_THREAD_LABEL, - ); - }, - ); - group.finish(); + bench::bench_server_multi_thread(c, HTTP_MODE, ADDR) + .expect("Failed to run multi-threaded HTTP/1 benchmark server") } criterion_group!( name = benches; config = Criterion::default() - .measurement_time(Duration::from_secs(10)) + .sample_size(10) .warm_up_time(Duration::from_secs(3)); targets = bench_server_single_thread, diff --git a/bench/http2.rs b/bench/http2.rs index 6a16b32a..b0cdc645 100644 --- a/bench/http2.rs +++ b/bench/http2.rs @@ -3,149 +3,29 @@ mod support; use std::time::Duration; -use support::client::{bench_reqwest, bench_wreq}; -use support::server::{spawn_multi_thread_server, spawn_single_thread_server, with_server}; -use support::{HttpMode, build_current_thread_runtime, build_multi_thread_runtime}; +use support::{HttpMode, bench}; -use criterion::{ - BenchmarkGroup, Criterion, criterion_group, criterion_main, measurement::WallTime, -}; +use criterion::{Criterion, criterion_group, criterion_main}; -const NUM_REQUESTS_TO_SEND: usize = 1000; -const CONCURRENT_LIMIT: usize = 100; const HTTP_MODE: HttpMode = HttpMode::Http2; -const CURRENT_THREAD_LABEL: &str = "current_thread"; -const MULTI_THREAD_LABEL: &str = "multi_thread"; +const ADDR: &str = "127.0.0.1:6928"; -fn run_benches( - group: &mut BenchmarkGroup<'_, WallTime>, - rt: fn() -> tokio::runtime::Runtime, - addr: &'static str, - label_prefix: &str, -) { - let runtime = rt(); - bench_wreq( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - false, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); - - bench_reqwest( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - false, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); - - bench_wreq( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - true, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); - - bench_reqwest( - group, - &runtime, - addr, - HTTP_MODE, - label_prefix, - true, - NUM_REQUESTS_TO_SEND, - CONCURRENT_LIMIT, - ); -} - -// Criterion benchmark functions +#[inline] fn bench_server_single_thread(c: &mut Criterion) { - let mut group = c.benchmark_group("server_single_thread"); - group.sample_size(10); - - // single-threaded client - with_server( - "127.0.0.1:6928", - HTTP_MODE, - spawn_single_thread_server, - || { - run_benches( - &mut group, - build_current_thread_runtime, - "127.0.0.1:6928", - CURRENT_THREAD_LABEL, - ); - }, - ); - - // multi-threaded client - with_server( - "127.0.0.1:6930", - HTTP_MODE, - spawn_single_thread_server, - || { - run_benches( - &mut group, - build_multi_thread_runtime, - "127.0.0.1:6930", - MULTI_THREAD_LABEL, - ); - }, - ); - group.finish(); + bench::bench_server_single_thread(c, HTTP_MODE, ADDR) + .expect("Failed to run single-threaded HTTP/2 benchmark server") } +#[inline] fn bench_server_multi_thread(c: &mut Criterion) { - let mut group = c.benchmark_group("server_multi_thread"); - group.sample_size(10); - - // single-threaded client - with_server( - "127.0.0.1:6929", - HTTP_MODE, - spawn_multi_thread_server, - || { - run_benches( - &mut group, - build_current_thread_runtime, - "127.0.0.1:6929", - CURRENT_THREAD_LABEL, - ); - }, - ); - - // multi-threaded client - with_server( - "127.0.0.1:6931", - HTTP_MODE, - spawn_multi_thread_server, - || { - run_benches( - &mut group, - build_multi_thread_runtime, - "127.0.0.1:6931", - MULTI_THREAD_LABEL, - ); - }, - ); - group.finish(); + bench::bench_server_multi_thread(c, HTTP_MODE, ADDR) + .expect("Failed to run multi-threaded HTTP/2 benchmark server") } criterion_group!( name = benches; config = Criterion::default() - .measurement_time(Duration::from_secs(10)) + .sample_size(10) .warm_up_time(Duration::from_secs(3)); targets = bench_server_single_thread, diff --git a/bench/support.rs b/bench/support.rs index dd852fc9..d48522e7 100644 --- a/bench/support.rs +++ b/bench/support.rs @@ -1,6 +1,9 @@ +pub mod bench; pub mod client; pub mod server; +use std::fmt; + #[allow(unused)] #[derive(Clone, Copy, Debug)] pub enum HttpMode { @@ -8,6 +11,16 @@ pub enum HttpMode { Http2, } +impl fmt::Display for HttpMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match self { + HttpMode::Http1 => "http1", + HttpMode::Http2 => "http2", + }; + f.write_str(value) + } +} + pub fn build_current_thread_runtime() -> tokio::runtime::Runtime { tokio::runtime::Builder::new_current_thread() .enable_all() diff --git a/bench/support/bench.rs b/bench/support/bench.rs new file mode 100644 index 00000000..4f857579 --- /dev/null +++ b/bench/support/bench.rs @@ -0,0 +1,127 @@ +use std::error::Error; + +use criterion::{BenchmarkGroup, Criterion, measurement::WallTime}; + +use crate::support::client::bench_both_clients; +use crate::support::server::{spawn_multi_thread_server, spawn_single_thread_server, with_server}; +use crate::support::{HttpMode, build_current_thread_runtime, build_multi_thread_runtime}; + +pub const NUM_REQUESTS_TO_SEND: usize = 1000; +pub const CONCURRENT_LIMIT: usize = 100; +pub const CURRENT_THREAD_LABEL: &str = "current_thread"; +pub const MULTI_THREAD_LABEL: &str = "multi_thread"; + +pub static BODY_CASES: &[&'static [u8]] = &[ + &[b'a'; 10 * 1024], // 10 KB + &[b'a'; 64 * 1024], // 64 KB + &[b'a'; 256 * 1024], // 256 KB + &[b'a'; 1 * 1024 * 1024], // 1024 KB + &[b'a'; 2 * 1024 * 1024], // 2048 KB + &[b'a'; 4 * 1024 * 1024], // 4096 KB +]; + +pub fn run_benches( + group: &mut BenchmarkGroup<'_, WallTime>, + rt: fn() -> tokio::runtime::Runtime, + addr: &'static str, + mode: HttpMode, + label_prefix: &str, +) -> Result<(), Box> { + let runtime = rt(); + for body in BODY_CASES { + // Sequential tests + bench_both_clients( + group, + &runtime, + addr, + mode, + label_prefix, + false, + NUM_REQUESTS_TO_SEND, + CONCURRENT_LIMIT, + body, + )?; + + // Concurrent tests + bench_both_clients( + group, + &runtime, + addr, + mode, + label_prefix, + true, + NUM_REQUESTS_TO_SEND, + CONCURRENT_LIMIT, + body, + )?; + } + + Ok(()) +} + +pub fn bench_server_single_thread( + c: &mut Criterion, + mode: HttpMode, + addr: &'static str, +) -> Result<(), Box> { + let mut group = c.benchmark_group("server_single_thread"); + group.sampling_mode(criterion::SamplingMode::Flat); + + with_server(addr, mode, spawn_single_thread_server, || { + // single-threaded client + run_benches( + &mut group, + build_current_thread_runtime, + addr, + mode, + CURRENT_THREAD_LABEL, + )?; + + // multi-threaded client + run_benches( + &mut group, + build_multi_thread_runtime, + addr, + mode, + MULTI_THREAD_LABEL, + ) + }); + + group.finish(); + + Ok(()) +} + +pub fn bench_server_multi_thread( + c: &mut Criterion, + mode: HttpMode, + addr: &'static str, +) -> Result<(), Box> { + let mut group = c.benchmark_group("server_multi_thread"); + group.sampling_mode(criterion::SamplingMode::Flat); + + with_server(addr, mode, spawn_multi_thread_server, || { + // single-threaded client + run_benches( + &mut group, + build_current_thread_runtime, + addr, + mode, + CURRENT_THREAD_LABEL, + )?; + + // multi-threaded client + with_server(addr, mode, spawn_multi_thread_server, || { + run_benches( + &mut group, + build_multi_thread_runtime, + addr, + mode, + MULTI_THREAD_LABEL, + ) + }) + })?; + + group.finish(); + Ok(()) +} diff --git a/bench/support/client.rs b/bench/support/client.rs index 2faaa195..5dd6072d 100644 --- a/bench/support/client.rs +++ b/bench/support/client.rs @@ -1,126 +1,223 @@ -use std::{error::Error, sync::Arc}; +use std::{convert::Infallible, error::Error, sync::Arc}; use super::HttpMode; +use bytes::Bytes; use criterion::{BenchmarkGroup, measurement::WallTime}; +use futures_util::stream; use tokio::{runtime::Runtime, sync::Semaphore}; -async fn wreq_send_requests( +const STREAM_CHUNK_SIZE: usize = 8 * 1024; + +#[inline] +fn box_err(e: E) -> Box { + Box::new(e) +} + +fn create_wreq_client(mode: HttpMode) -> Result> { + let builder = wreq::Client::builder() + .no_proxy() + .redirect(wreq::redirect::Policy::none()); + + let builder = match mode { + HttpMode::Http1 => builder.http1_only(), + HttpMode::Http2 => builder.http2_only(), + }; + + Ok(builder.build()?) +} + +fn create_reqwest_client(mode: HttpMode) -> Result> { + let builder = reqwest::Client::builder() + .no_proxy() + .redirect(reqwest::redirect::Policy::none()); + + let builder = match mode { + HttpMode::Http1 => builder.http1_only(), + HttpMode::Http2 => builder.http2_prior_knowledge(), + }; + + Ok(builder.build()?) +} + +async fn wreq_body_assert(mut response: wreq::Response, expected_body_size: usize) { + let mut body_size = 0; + while let Ok(Some(chunk)) = response.chunk().await { + body_size += chunk.len(); + } + assert!( + body_size == expected_body_size, + "Unexpected response body: got {body_size} bytes, expected {expected_body_size} bytes" + ); +} + +async fn reqwest_body_assert(mut response: reqwest::Response, expected_body_size: usize) { + let mut body_size = 0; + while let Ok(Some(chunk)) = response.chunk().await { + body_size += chunk.len(); + } + assert!( + body_size == expected_body_size, + "Unexpected response body: got {body_size} bytes, expected {expected_body_size} bytes" + ); +} + +#[inline] +fn stream_from_static( + body: &'static [u8], +) -> impl futures_util::stream::TryStream + Send + 'static { + stream::iter( + body.chunks(STREAM_CHUNK_SIZE) + .map(|chunk| Ok::(Bytes::from_static(chunk))), + ) +} + +#[inline] +fn wreq_body(stream: bool, body: &'static [u8]) -> wreq::Body { + if stream { + let stream = stream_from_static(body); + wreq::Body::wrap_stream(stream) + } else { + wreq::Body::from(body) + } +} + +#[inline] +fn reqwest_body(stream: bool, body: &'static [u8]) -> reqwest::Body { + if stream { + let stream = stream_from_static(body); + reqwest::Body::wrap_stream(stream) + } else { + reqwest::Body::from(body) + } +} + +async fn wreq_requests_sequential( client: &wreq::Client, url: &str, num_requests: usize, + body: &'static [u8], + stream: bool, ) -> Result<(), Box> { - for _i in 0..num_requests { - let mut response = client.get(url).send().await?; - while let Ok(Some(_chunk)) = response.chunk().await {} + for _ in 0..num_requests { + let response = client + .post(url) + .body(wreq_body(stream, body)) + .send() + .await?; + + wreq_body_assert(response, body.len()).await; } Ok(()) } -async fn reqwest_send_requests( +async fn reqwest_requests_sequential( client: &reqwest::Client, url: &str, num_requests: usize, + body: &'static [u8], + stream: bool, ) -> Result<(), Box> { - for _i in 0..num_requests { - let mut response = client.get(url).send().await?; - while let Ok(Some(_chunk)) = response.chunk().await {} + for _ in 0..num_requests { + let response = client + .post(url) + .body(reqwest_body(stream, body)) + .send() + .await?; + + reqwest_body_assert(response, body.len()).await; } Ok(()) } -async fn wreq_send_requests_concurrent( +async fn wreq_requests_concurrent( client: &wreq::Client, url: &str, num_requests: usize, concurrent_limit: usize, -) { + body: &'static [u8], + stream: bool, +) -> Result<(), Box> { let semaphore = Arc::new(Semaphore::new(concurrent_limit)); let mut handles = Vec::with_capacity(num_requests); - for _i in 0..num_requests { + for _ in 0..num_requests { let url = url.to_owned(); let client = client.clone(); let semaphore = semaphore.clone(); let task = tokio::spawn(async move { - let _permit = semaphore.acquire().await.unwrap(); - let mut response = client.get(url).send().await.unwrap(); - while let Ok(Some(_chunk)) = response.chunk().await {} + let _permit = semaphore.acquire().await.map_err(box_err)?; + let response = client + .post(url) + .body(wreq_body(stream, body)) + .send() + .await + .map_err(box_err)?; + + wreq_body_assert(response, body.len()).await; + Ok(()) }); + handles.push(task); } - futures_util::future::join_all(handles).await; + futures_util::future::join_all(handles) + .await + .into_iter() + .try_for_each(|res| res.map_err(box_err)?) } -async fn reqwest_send_requests_concurrent( +async fn reqwest_requests_concurrent( client: &reqwest::Client, url: &str, num_requests: usize, concurrent_limit: usize, -) { + body: &'static [u8], + stream: bool, +) -> Result<(), Box> { let semaphore = Arc::new(Semaphore::new(concurrent_limit)); let mut handles = Vec::with_capacity(num_requests); - for _i in 0..num_requests { + for _ in 0..num_requests { let url = url.to_owned(); let client = client.clone(); let semaphore = semaphore.clone(); let task = tokio::spawn(async move { - let _permit = semaphore.acquire().await.unwrap(); - let mut response = client.get(url).send().await.unwrap(); - while let Ok(Some(_chunk)) = response.chunk().await {} + let _permit = semaphore.acquire().await.map_err(box_err)?; + let response = client + .post(url) + .body(reqwest_body(stream, body)) + .send() + .await + .map_err(box_err)?; + + reqwest_body_assert(response, body.len()).await; + Ok(()) }); + handles.push(task); } - futures_util::future::join_all(handles).await; + futures_util::future::join_all(handles) + .await + .into_iter() + .try_for_each(|res| res.map_err(box_err)?) } -#[allow(clippy::too_many_arguments)] -pub fn bench_wreq( - group: &mut BenchmarkGroup<'_, WallTime>, - rt: &Runtime, - addr: &str, - mode: HttpMode, - label_prefix: &str, - concurrent: bool, - num_requests: usize, - concurrent_limit: usize, -) { - let builder = wreq::Client::builder() - .no_proxy() - .redirect(wreq::redirect::Policy::none()); - let builder = match mode { - HttpMode::Http1 => builder.http1_only(), - HttpMode::Http2 => builder.http2_only(), - }; - let client = builder.build().unwrap(); - let url = format!("http://{addr}"); - - if concurrent { - let label = format!("{mode:?}_{label_prefix}_wreq_concurrent"); - group.bench_function(label, |b| { - b.iter(|| { - rt.block_on(wreq_send_requests_concurrent( - &client, - &url, - num_requests, - concurrent_limit, - )) - }); - }); - } else { - let label = format!("{mode:?}_{label_prefix}_wreq_sequential"); - group.bench_function(label, |b| { - b.iter(|| rt.block_on(wreq_send_requests(&client, &url, num_requests))); - }); - } +/// Extract the crate name from a type's module path +/// For example: wreq::Client -> "wreq", reqwest::Client -> "reqwest" +fn crate_name() -> &'static str { + let full_name = std::any::type_name::(); + // Split by "::" and take the first part (the crate name) + full_name + .split("::") + .next() + .expect("Type name should contain at least one segment") } #[allow(clippy::too_many_arguments)] -pub fn bench_reqwest( +pub fn bench_both_clients( group: &mut BenchmarkGroup<'_, WallTime>, rt: &Runtime, addr: &str, @@ -129,33 +226,91 @@ pub fn bench_reqwest( concurrent: bool, num_requests: usize, concurrent_limit: usize, -) { - let builder = reqwest::Client::builder() - .no_proxy() - .redirect(reqwest::redirect::Policy::none()); - let builder = match mode { - HttpMode::Http1 => builder.http1_only(), - HttpMode::Http2 => builder.http2_prior_knowledge(), - }; - let client = builder.build().unwrap(); + body: &'static [u8], +) -> Result<(), Box> { + let wreq_client = create_wreq_client(mode)?; + let reqwest_client = create_reqwest_client(mode)?; + let url = format!("http://{addr}"); + let body_kb = body.len() / 1024; + + let make_benchmark_label = |client: &str, stream: bool| { + let execution_mode = if concurrent { + "concurrent" + } else { + "sequential" + }; + let body_type = if stream { "stream" } else { "full" }; + format!("{client}_{mode}_{label_prefix}_{execution_mode}_body_{body_type}_{body_kb}KB") + }; if concurrent { - let label = format!("{mode:?}_{label_prefix}_reqwest_concurrent"); - group.bench_function(label, |b| { - b.iter(|| { - rt.block_on(reqwest_send_requests_concurrent( - &client, - &url, - num_requests, - concurrent_limit, - )) - }); - }); + for stream in [false, true] { + group.bench_function( + make_benchmark_label(crate_name::(), stream), + |b| { + b.iter(|| { + rt.block_on(wreq_requests_concurrent( + &wreq_client, + &url, + num_requests, + concurrent_limit, + body, + stream, + )) + }) + }, + ); + + group.bench_function( + make_benchmark_label(crate_name::(), stream), + |b| { + b.iter(|| { + rt.block_on(reqwest_requests_concurrent( + &reqwest_client, + &url, + num_requests, + concurrent_limit, + body, + stream, + )) + }) + }, + ); + } } else { - let label = format!("{mode:?}_{label_prefix}_reqwest_sequential"); - group.bench_function(label, |b| { - b.iter(|| rt.block_on(reqwest_send_requests(&client, &url, num_requests))); - }); + for stream in [false, true] { + group.bench_function( + make_benchmark_label(crate_name::(), stream), + |b| { + b.iter(|| { + rt.block_on(wreq_requests_sequential( + &wreq_client, + &url, + num_requests, + body, + stream, + )) + }) + }, + ); + + group.bench_function( + make_benchmark_label(crate_name::(), stream), + |b| { + b.iter(|| { + rt.block_on(reqwest_requests_sequential( + &reqwest_client, + &url, + num_requests, + body, + stream, + )) + }) + }, + ); + } } + + Ok(()) } diff --git a/bench/support/server.rs b/bench/support/server.rs index fc6ea876..8f1d5406 100644 --- a/bench/support/server.rs +++ b/bench/support/server.rs @@ -1,8 +1,6 @@ use std::{convert::Infallible, error::Error, time::Duration}; -use bytes::Bytes; use http::{Request, Response}; -use http_body_util::{BodyExt, Full, combinators::BoxBody}; use hyper::{body::Incoming, service::service_fn}; use hyper_util::{ rt::{TokioExecutor, TokioIo}, @@ -42,12 +40,14 @@ pub fn with_server( mode: HttpMode, spawn: fn(&'static str, HttpMode) -> ServerHandle, f: F, -) where - F: FnOnce(), +) -> Result<(), Box> +where + F: FnOnce() -> Result<(), Box>, { let server = spawn(addr, mode); - f(); + f()?; server.shutdown(); + Ok(()) } fn spawn_server(addr: &'static str, multi_thread: bool, mode: HttpMode) -> ServerHandle { @@ -110,13 +110,8 @@ async fn serve(stream: TcpStream, mode: HttpMode) -> Result<(), Box, -) -> Result>, Infallible> { - let response = Response::builder() - .header(http::header::CONTENT_TYPE, "text/plain") - .body(Full::new(Bytes::from("Hello, world!")).boxed()) - .expect("values provided to the builder should be valid"); - - Ok(response) +#[inline] +async fn handle_request(request: Request) -> Result, Infallible> { + let body = request.into_body(); + Ok(Response::new(body)) }