Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make request methods infallible. #1023

Merged
merged 3 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ serde = { version = "1.0.130", default-features = false, optional = true }
serde_json = { version = "1", optional = true }

# websocket
http-ws = { version = "0.3", features = ["stream"], optional = true }
http-ws = { version = "0.4", features = ["stream"], optional = true }

[dev-dependencies]
futures = "0.3"
Expand Down
52 changes: 32 additions & 20 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ impl Default for Client {
macro_rules! method {
($method: tt, $method2: tt) => {
#[doc = concat!("Start a new [Method::",stringify!($method2),"] request with empty request body.")]
pub fn $method<U>(&self, url: U) -> Result<RequestBuilder<'_>, Error>
pub fn $method<U>(&self, url: U) -> RequestBuilder<'_>
where
uri::Uri: TryFrom<U>,
Error: From<<uri::Uri as TryFrom<U>>::Error>,
{
Ok(self.get(url)?.method(Method::$method2))
self.get(url).method(Method::$method2)
}
};
}
Expand Down Expand Up @@ -84,18 +84,23 @@ impl Client {
}

/// Start a new [Method::GET] request with empty request body.
pub fn get<U>(&self, url: U) -> Result<RequestBuilder<'_>, Error>
pub fn get<U>(&self, url: U) -> RequestBuilder<'_>
where
uri::Uri: TryFrom<U>,
Error: From<<uri::Uri as TryFrom<U>>::Error>,
{
let uri = uri::Uri::try_from(url)?;

let mut req = http::Request::new(BoxBody::default());
*req.uri_mut() = uri;
*req.version_mut() = self.max_http_version;

Ok(self.request(req))
let err = uri::Uri::try_from(url).map(|uri| *req.uri_mut() = uri).err();

let mut builder = self.request(req);

if let Some(e) = err {
builder.push_error(e.into());
}

builder
}

method!(post, POST);
Expand All @@ -114,7 +119,7 @@ impl Client {
/// # async fn _main() -> Result<(), xitca_client::error::Error> {
/// // construct a new client and initialize connect request.
/// let client = Client::new();
/// let mut http_tunnel = client.connect("http://localhost:8080")?.send().await?;
/// let mut http_tunnel = client.connect("http://localhost:8080").send().await?;
///
/// // http_tunnel is tunnel connection type exposing Stream and Sink trait
/// // interfaces for 2 way bytes data communicating.
Expand All @@ -141,7 +146,7 @@ impl Client {
/// // write part can operate with Sink trait implement.
/// write.send(b"996").await?;
///
/// let mut http_tunnel = client.connect("http://localhost:8080")?.send().await?;
/// let mut http_tunnel = client.connect("http://localhost:8080").send().await?;
///
/// // import AsyncIo trait and use http tunnel as io type directly.
/// use xitca_io::io::{Interest, AsyncIo};
Expand Down Expand Up @@ -170,13 +175,12 @@ impl Client {
/// Ok(())
/// # }
/// ```
pub fn connect<U>(&self, url: U) -> Result<HttpTunnelRequest<'_>, Error>
pub fn connect<U>(&self, url: U) -> HttpTunnelRequest<'_>
where
uri::Uri: TryFrom<U>,
Error: From<<uri::Uri as TryFrom<U>>::Error>,
{
self.get(url)
.map(|req| HttpTunnelRequest::new(req.method(Method::CONNECT)))
HttpTunnelRequest::new(self.get(url).method(Method::CONNECT))
}

#[cfg(all(feature = "websocket", feature = "http1"))]
Expand All @@ -189,7 +193,7 @@ impl Client {
/// # async fn _main() -> Result<(), xitca_client::error::Error> {
/// // construct a new client and initialize websocket request.
/// let client = Client::new();
/// let mut ws = client.ws("ws://localhost:8080")?.send().await?;
/// let mut ws = client.ws("ws://localhost:8080").send().await?;
///
/// // ws is websocket connection type exposing Stream and Sink trait
/// // interfaces for 2 way websocket message communicating.
Expand Down Expand Up @@ -219,7 +223,7 @@ impl Client {
/// Ok(())
/// # }
/// ```
pub fn ws<U>(&self, url: U) -> Result<crate::ws::WsRequest<'_>, Error>
pub fn ws<U>(&self, url: U) -> crate::ws::WsRequest<'_>
where
uri::Uri: TryFrom<U>,
Error: From<<uri::Uri as TryFrom<U>>::Error>,
Expand All @@ -229,7 +233,7 @@ impl Client {

#[cfg(all(feature = "websocket", feature = "http2"))]
/// Start a new websocket request with HTTP/2.
pub fn ws2<U>(&self, url: U) -> Result<crate::ws::WsRequest<'_>, Error>
pub fn ws2<U>(&self, url: U) -> crate::ws::WsRequest<'_>
where
uri::Uri: TryFrom<U>,
Error: From<<uri::Uri as TryFrom<U>>::Error>,
Expand All @@ -238,14 +242,23 @@ impl Client {
}

#[cfg(feature = "websocket")]
fn _ws<U>(&self, url: U, version: Version) -> Result<crate::ws::WsRequest<'_>, Error>
fn _ws<U>(&self, url: U, version: Version) -> crate::ws::WsRequest<'_>
where
uri::Uri: TryFrom<U>,
Error: From<<uri::Uri as TryFrom<U>>::Error>,
{
let req = http_ws::client_request_from_uri(url, version)?.map(|_| BoxBody::default());
let req = RequestBuilder::new(req, self);
Ok(crate::ws::WsRequest::new(req))
let builder = match uri::Uri::try_from(url) {
Ok(uri) => {
let req = http_ws::client_request_from_uri(uri, version).map(|_| BoxBody::default());
self.request(req)
}
Err(e) => {
let mut builder = self.request(http::Request::new(BoxBody::default()));
builder.push_error(e.into());
builder
}
};
crate::ws::WsRequest::new(builder)
}
}

Expand Down Expand Up @@ -402,7 +415,6 @@ mod test {
.openssl()
.finish()
.get("https://www.google.com/")
.unwrap()
.send()
.await
.unwrap()
Expand Down
22 changes: 22 additions & 0 deletions client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ impl From<Infallible> for Error {
}
}

/// a collection of multiple errors chained together.
#[derive(Debug)]
pub struct ErrorMultiple(Vec<Error>);

impl fmt::Display for ErrorMultiple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for e in self.0.iter() {
write!(f, "{}", e)?;
}

Ok(())
}
}

impl error::Error for ErrorMultiple {}

impl From<Vec<Error>> for Error {
fn from(err: Vec<Error>) -> Self {
Self::Std(Box::new(ErrorMultiple(err)))
}
}

#[derive(Debug)]
pub enum InvalidUri {
MissingHost,
Expand Down
2 changes: 1 addition & 1 deletion client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! // build client with tls enabled.
//! let client = Client::builder().rustls().finish();
//! // send get request to google and wait for response.
//! let res = client.get("https://www.google.com/")?.send().await?;
//! let res = client.get("https://www.google.com/").send().await?;
//! // parse streaming response body to bytes.
//! let body = res.body().await?;
//! // print the body as lossy string.
Expand Down
32 changes: 22 additions & 10 deletions client/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ use crate::{

/// builder type for [http::Request] with extended functionalities.
pub struct RequestBuilder<'a> {
/// HTTP request type from [http] crate.
req: http::Request<BoxBody>,
/// Reference to Client instance.
err: Vec<Error>,
client: &'a Client,
/// Request level timeout setting. When Some(Duration) would override
/// timeout configuration from Client.
timeout: Duration,
}

Expand All @@ -35,11 +32,16 @@ impl<'a> RequestBuilder<'a> {
{
Self {
req: req.map(BoxBody::new),
err: Vec::new(),
client,
timeout: client.timeout_config.request_timeout,
}
}

pub(crate) fn push_error(&mut self, e: Error) {
self.err.push(e);
}

/// Returns request's headers.
#[inline]
pub fn headers(&self) -> &HeaderMap {
Expand Down Expand Up @@ -124,12 +126,17 @@ impl<'a> RequestBuilder<'a> {

#[cfg(feature = "json")]
/// Use json object as request body.
pub fn json(mut self, body: impl serde::ser::Serialize) -> Result<RequestBuilder<'a>, Error> {
// TODO: handle serialize error.
let body = serde_json::to_vec(&body).unwrap();

self.headers_mut().insert(CONTENT_TYPE, const_header_value::JSON);
Ok(self.body(body))
pub fn json(mut self, body: impl serde::ser::Serialize) -> RequestBuilder<'a> {
match serde_json::to_vec(&body) {
Ok(body) => {
self.headers_mut().insert(CONTENT_TYPE, const_header_value::JSON);
self.body(body)
}
Err(e) => {
self.push_error(e.into());
self
}
}
}

/// Use pre allocated bytes as request body.
Expand Down Expand Up @@ -168,10 +175,15 @@ impl<'a> RequestBuilder<'a> {
pub async fn send(self) -> Result<Response<'a>, Error> {
let Self {
mut req,
err,
client,
timeout,
} = self;

if !err.is_empty() {
return Err(err.into());
}

client
.service
.call(ServiceRequest {
Expand Down
4 changes: 3 additions & 1 deletion http-ws/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# unreleased
# unreleased 0.4.0
## Change
- `client_request_from_uri` becomes infallible by receive `Uri` type without try conversion.

# 0.3.0
## Add
Expand Down
2 changes: 1 addition & 1 deletion http-ws/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "http-ws"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
license = "Apache-2.0"
description = "websocket for http crate type"
Expand Down
9 changes: 2 additions & 7 deletions http-ws/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,7 @@ impl From<HandshakeError> for Builder {
/// Prepare a [Request] with given [Uri] and [Version] for websocket connection.
/// Only [Version::HTTP_11] and [Version::HTTP_2] are supported.
/// After process the request would be ready to be sent to server.
pub fn client_request_from_uri<U, E>(uri: U, version: Version) -> Result<Request<()>, E>
where
Uri: TryFrom<U, Error = E>,
{
let uri = uri.try_into()?;

pub fn client_request_from_uri(uri: Uri, version: Version) -> Request<()> {
let mut req = Request::new(());
*req.uri_mut() = uri;
*req.version_mut() = version;
Expand Down Expand Up @@ -124,7 +119,7 @@ where
req.headers_mut()
.insert(SEC_WEBSOCKET_VERSION, SEC_WEBSOCKET_VERSION_VALUE);

Ok(req)
req
}

/// Verify HTTP/1.1 WebSocket handshake request and create handshake response.
Expand Down
2 changes: 1 addition & 1 deletion test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ xitca-service = "0.1"
xitca-unsafe-collection = "0.1.1"
xitca-web = "0.5"

http-ws = { version = "0.3", features = ["stream"] }
http-ws = { version = "0.4", features = ["stream"] }

async-stream = "0.3"
futures-util = "0.3.17"
Expand Down
16 changes: 8 additions & 8 deletions test/tests/h1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async fn h1_get() -> Result<(), Error> {
let c = Client::new();

for _ in 0..3 {
let mut res = c.get(&server_url)?.version(Version::HTTP_11).send().await?;
let mut res = c.get(&server_url).version(Version::HTTP_11).send().await?;
assert_eq!(res.status().as_u16(), 200);
assert!(!res.can_close_connection());
let body = res.string().await?;
Expand All @@ -50,7 +50,7 @@ async fn h1_head() -> Result<(), Error> {
let c = Client::new();

for _ in 0..3 {
let mut res = c.head(&server_url)?.version(Version::HTTP_11).send().await?;
let mut res = c.head(&server_url).version(Version::HTTP_11).send().await?;
assert_eq!(res.status().as_u16(), 200);
assert!(!res.can_close_connection());
let body = res.string().await?;
Expand Down Expand Up @@ -79,7 +79,7 @@ async fn h1_post() -> Result<(), Error> {
}
let body_len = body.len();

let mut res = c.post(&server_url)?.version(Version::HTTP_11).text(body).send().await?;
let mut res = c.post(&server_url).version(Version::HTTP_11).text(body).send().await?;
assert_eq!(res.status().as_u16(), 200);
assert!(!res.can_close_connection());
let body = res.limit::<{ 12 * 1024 }>().string().await?;
Expand Down Expand Up @@ -107,7 +107,7 @@ async fn h1_drop_body_read() -> Result<(), Error> {
body.extend_from_slice(b"Hello,World!");
}

let mut res = c.post(&server_url)?.version(Version::HTTP_11).text(body).send().await?;
let mut res = c.post(&server_url).version(Version::HTTP_11).text(body).send().await?;
assert_eq!(res.status().as_u16(), 200);
assert!(res.can_close_connection());
}
Expand All @@ -133,7 +133,7 @@ async fn h1_partial_body_read() -> Result<(), Error> {
body.extend_from_slice(b"Hello,World!");
}

let mut res = c.post(&server_url)?.version(Version::HTTP_11).text(body).send().await?;
let mut res = c.post(&server_url).version(Version::HTTP_11).text(body).send().await?;
assert_eq!(res.status().as_u16(), 200);
assert!(res.can_close_connection());
}
Expand All @@ -153,7 +153,7 @@ async fn h1_close_connection() -> Result<(), Error> {

let c = Client::new();

let mut res = c.get(&server_url)?.version(Version::HTTP_11).send().await?;
let mut res = c.get(&server_url).version(Version::HTTP_11).send().await?;
assert_eq!(res.status().as_u16(), 200);
assert!(res.can_close_connection());

Expand All @@ -174,7 +174,7 @@ async fn h1_request_too_large() -> Result<(), Error> {

let c = Client::new();

let mut req = c.get(&server_url)?.version(Version::HTTP_11);
let mut req = c.get(&server_url).version(Version::HTTP_11);

let body = vec![*b"H".first().unwrap(); 512 * 1024];
req.headers_mut()
Expand All @@ -184,7 +184,7 @@ async fn h1_request_too_large() -> Result<(), Error> {
assert_eq!(res.status().as_u16(), 200);
let _ = res.body().await;

let mut req = c.get(&server_url)?.version(Version::HTTP_11);
let mut req = c.get(&server_url).version(Version::HTTP_11);

let body = vec![*b"H".first().unwrap(); 1024 * 1024];
req.headers_mut()
Expand Down
Loading
Loading