diff --git a/README.md b/README.md index fb6cfc8..d980f09 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # curl-http-client This is a wrapper for Easy2 from curl-rust crate for ergonomic use -and is able to perform asynchronously using async-curl crate +and is able to perform synchronously and asynchronously using async-curl crate that uses an actor model (Message passing) to achieve a non-blocking I/O. [![Latest Version](https://img.shields.io/crates/v/curl-http-client.svg)](https://crates.io/crates/curl-http-client) @@ -9,6 +9,8 @@ that uses an actor model (Message passing) to achieve a non-blocking I/O. [![Documentation](https://docs.rs/curl-http-client/badge.svg)](https://docs.rs/curl-http-client) [![Build Status](https://github.com/LorenzoLeonardo/curl-http-client/workflows/Rust/badge.svg)](https://github.com/LorenzoLeonardo/curl-http-client/actions) +# Asynchronous Examples + ## Get Request ```rust use async_curl::actor::CurlActor; @@ -18,7 +20,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::Ram(Vec::new()); let request = HttpRequest { @@ -28,8 +30,9 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request)? + .nonblocking(actor) .perform() .await?; @@ -47,7 +50,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::Ram(Vec::new()); let request = HttpRequest { @@ -57,8 +60,9 @@ async fn main() -> Result<(), Box> { body: Some("test body".as_bytes().to_vec()), }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request)? + .nonblocking(actor) .perform() .await?; @@ -82,7 +86,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(PathBuf::from(""))); @@ -93,8 +97,9 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request)? + .nonblocking(actor) .perform() .await?; @@ -121,7 +126,7 @@ async fn main() -> Result<(), Box> { let file_to_be_uploaded = PathBuf::from(""); let file_size = fs::metadata(file_to_be_uploaded.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(file_to_be_uploaded)); let request = HttpRequest { @@ -131,9 +136,10 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .upload_file_size(FileSize::from(file_size))? .request(request)? + .nonblocking(actor) .perform() .await?; @@ -154,11 +160,11 @@ use url::Url; async fn main() { const NUM_CONCURRENT: usize = 5; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let mut handles = Vec::new(); for _n in 0..NUM_CONCURRENT { - let curl = curl.clone(); + let actor = actor.clone(); let handle = tokio::spawn(async move { let collector = Collector::Ram(Vec::new()); @@ -169,9 +175,10 @@ async fn main() { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -204,7 +211,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let save_to = PathBuf::from(""); let collector = Collector::File(FileInfo::path(save_to.clone())); @@ -216,9 +223,10 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .resume_from(BytesOffset::from(partial_download_file_size))? .request(request)? + .nonblocking(actor) .perform() .await?; println!("Response: {:?}", response); @@ -244,7 +252,7 @@ use url::Url; async fn main() -> Result<(), Box> { let (tx, mut rx) = channel(1); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let file_info = FileInfo::path(PathBuf::from("")).with_transfer_speed_sender(tx); let collector = Collector::File(file_info); @@ -261,8 +269,9 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request)? + .nonblocking(actor) .perform() .await?; @@ -294,7 +303,7 @@ async fn main() -> Result<(), Box> { let file_to_be_uploaded = PathBuf::from(""); let file_size = fs::metadata(file_to_be_uploaded.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let file_info = FileInfo::path(file_to_be_uploaded).with_transfer_speed_sender(tx); let collector = Collector::File(file_info); @@ -311,9 +320,10 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .upload_file_size(FileSize::from(file_size))? .request(request)? + .nonblocking(actor) .perform() .await?; @@ -321,4 +331,126 @@ async fn main() -> Result<(), Box> { handle.abort(); Ok(()) } +``` + +# Synchronous Examples + +## Get Request +```rust +use curl_http_client::{collector::Collector, http_client::HttpClient, request::HttpRequest}; +use http::{HeaderMap, Method}; +use url::Url; + +fn main() -> Result<(), Box> { + let collector = Collector::Ram(Vec::new()); + + let request = HttpRequest { + url: Url::parse("")?, + method: Method::GET, + headers: HeaderMap::new(), + body: None, + }; + + let response = HttpClient::new(collector) + .request(request)? + .blocking() + .perform()?; + + println!("Response: {:?}", response); + Ok(()) +} +``` + +## Post Request +```rust +use curl_http_client::{collector::Collector, http_client::HttpClient, request::HttpRequest}; +use http::{HeaderMap, Method}; +use url::Url; + +fn main() -> Result<(), Box> { + let collector = Collector::Ram(Vec::new()); + + let request = HttpRequest { + url: Url::parse("")?, + method: Method::POST, + headers: HeaderMap::new(), + body: Some("test body".as_bytes().to_vec()), + }; + + let response = HttpClient::new(collector) + .request(request)? + .blocking() + .perform()?; + + println!("Response: {:?}", response); + Ok(()) +} +``` + +## Downloading a File +```rust +use std::path::PathBuf; + +use curl_http_client::{ + collector::{Collector, FileInfo}, + http_client::HttpClient, + request::HttpRequest, +}; +use http::{HeaderMap, Method}; +use url::Url; + +fn main() -> Result<(), Box> { + let collector = Collector::File(FileInfo::path(PathBuf::from(""))); + + let request = HttpRequest { + url: Url::parse("")?, + method: Method::GET, + headers: HeaderMap::new(), + body: None, + }; + + let response = HttpClient::new(collector) + .request(request)? + .blocking(actor) + .perform()?; + + println!("Response: {:?}", response); + Ok(()) +} +``` + +## Uploading a File +```rust +use std::{fs, path::PathBuf}; + +use curl_http_client::{ + collector::{Collector, FileInfo}, + http_client::{FileSize, HttpClient}, + request::HttpRequest, +}; +use http::{HeaderMap, Method}; +use url::Url; + +fn main() -> Result<(), Box> { + let file_to_be_uploaded = PathBuf::from(""); + let file_size = fs::metadata(file_to_be_uploaded.as_path()).unwrap().len() as usize; + + let collector = Collector::File(FileInfo::path(file_to_be_uploaded)); + + let request = HttpRequest { + url: Url::parse("")?, + method: Method::PUT, + headers: HeaderMap::new(), + body: None, + }; + + let response = HttpClient::new(collector) + .upload_file_size(FileSize::from(file_size))? + .request(request)? + .blocking() + .perform()?; + + println!("Response: {:?}", response); + Ok(()) +} ``` \ No newline at end of file diff --git a/examples/asynchronous.rs b/examples/asynchronous.rs index a318734..6244581 100644 --- a/examples/asynchronous.rs +++ b/examples/asynchronous.rs @@ -12,7 +12,7 @@ async fn main() { let mut handles = Vec::new(); for _n in 0..NUM_CONCURRENT { - let curl = curl.clone(); + let actor = curl.clone(); let handle = tokio::spawn(async move { let collector = Collector::Ram(Vec::new()); @@ -23,9 +23,10 @@ async fn main() { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); diff --git a/examples/download.rs b/examples/download.rs index cb33940..5a8727c 100644 --- a/examples/download.rs +++ b/examples/download.rs @@ -11,7 +11,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(PathBuf::from(""))); @@ -22,9 +22,10 @@ async fn main() { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); diff --git a/examples/get.rs b/examples/get.rs index 93b9210..73ceaa1 100644 --- a/examples/get.rs +++ b/examples/get.rs @@ -5,7 +5,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::Ram(Vec::new()); let request = HttpRequest { @@ -15,9 +15,10 @@ async fn main() { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); diff --git a/examples/post.rs b/examples/post.rs index 503768a..cc2b860 100644 --- a/examples/post.rs +++ b/examples/post.rs @@ -5,7 +5,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::Ram(Vec::new()); let request = HttpRequest { @@ -15,9 +15,10 @@ async fn main() -> Result<(), Box> { body: Some("test body".as_bytes().to_vec()), }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); diff --git a/examples/resume_download.rs b/examples/resume_download.rs index 2e545b8..13b9473 100644 --- a/examples/resume_download.rs +++ b/examples/resume_download.rs @@ -12,7 +12,7 @@ use url::Url; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { - let curl = CurlActor::new(); + let actor = CurlActor::new(); let save_to = PathBuf::from(""); let collector = Collector::File(FileInfo::path(save_to.clone())); @@ -24,11 +24,12 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .resume_from(BytesOffset::from(partial_download_file_size)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); diff --git a/examples/upload.rs b/examples/upload.rs index 2966a6e..2702bec 100644 --- a/examples/upload.rs +++ b/examples/upload.rs @@ -14,7 +14,7 @@ async fn main() -> Result<(), Box> { let file_to_be_uploaded = PathBuf::from(""); let file_size = fs::metadata(file_to_be_uploaded.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(file_to_be_uploaded)); let request = HttpRequest { @@ -24,11 +24,12 @@ async fn main() -> Result<(), Box> { body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .upload_file_size(FileSize::from(file_size)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); diff --git a/src/http_client.rs b/src/http_client.rs index b60f2e3..52fb8eb 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -13,10 +13,12 @@ use crate::{ collector::ExtendedHandler, error::Error, request::HttpRequest, response::HttpResponse, }; -/// A type-state struct in building the HttpClient. +/// Proceed to building state pub struct Build; -/// A type-state struct in building the HttpClient. -pub struct Perform; +/// Proceed to perform async state +pub struct PerformAsync; +/// Proceed to perform sync state +pub struct PerformSync; /// The HTTP Client struct that wraps curl Easy2. pub struct HttpClient @@ -26,11 +28,11 @@ where /// This is the the actor handler that can be cloned to be able to handle multiple request sender /// and a single consumer that is spawned in the background upon creation of this object to be able to achieve /// non-blocking I/O during curl perform. - curl: CurlActor, + actor: Option>, /// The `Easy2` is the Easy2 from curl-rust crate wrapped in this struct to be able to do /// asynchronous task during perform operation. easy: Easy2, - /// This is a type-state builder pattern to help programmers not to mis-used when building curl options before perform + /// This is a type-state builder pattern to help programmers not to mis-use when building curl options before perform /// operation. _state: S, } @@ -41,25 +43,43 @@ where { /// Creates a new HTTP Client. /// - /// The [`CurlActor`](https://docs.rs/async-curl/latest/async_curl/actor/struct.CurlActor.html) is the actor handler that can be cloned to be able to handle multiple request sender - /// and a single consumer that is spawned in the background upon creation of this object to be able to achieve - /// non-blocking I/O during curl perform. - /// /// The C is a generic type to be able to implement a custom HTTP response collector whoever uses this crate. /// There is a built-in [`Collector`](https://docs.rs/curl-http-client/latest/curl_http_client/collector/enum.Collector.html) in this crate that can be used store HTTP response body into memory or in a File. - pub fn new(curl: CurlActor, collector: C) -> Self { + pub fn new(collector: C) -> Self { Self { - curl, + actor: None, easy: Easy2::new(collector), _state: Build, } } + /// This marks the end of the curl builder to be able to do asynchronous operation during perform. + /// + /// The [`CurlActor`](https://docs.rs/async-curl/latest/async_curl/actor/struct.CurlActor.html) is the actor handler that can be cloned + /// to be able to handle multiple request sender and a single consumer that is spawned in the background upon creation of this object to be able to achieve + /// non-blocking I/O during curl perform. + pub fn nonblocking(self, actor: CurlActor) -> HttpClient { + HttpClient:: { + actor: Some(actor), + easy: self.easy, + _state: PerformAsync, + } + } + + /// This marks the end of the curl builder to be able to do synchronous operation during perform. + pub fn blocking(self) -> HttpClient { + HttpClient:: { + actor: None, + easy: self.easy, + _state: PerformSync, + } + } + /// Sets the HTTP request. /// /// The HttpRequest can be customized by the caller by setting the Url, Method Type, /// Headers and the Body. - pub fn request(mut self, request: HttpRequest) -> Result, Error> { + pub fn request(mut self, request: HttpRequest) -> Result> { self.easy.url(&request.url.to_string()[..]).map_err(|e| { trace!("{:?}", e); Error::Curl(e) @@ -113,11 +133,7 @@ where unimplemented!(); } } - Ok(HttpClient:: { - curl: self.curl, - easy: self.easy, - _state: Perform, - }) + Ok(self) } /// Set a point to resume transfer from @@ -788,24 +804,29 @@ where } } -impl HttpClient +impl HttpClient where C: ExtendedHandler + std::fmt::Debug + Send, { /// This will send the request asynchronously, /// and return the underlying [`Easy2`](https://docs.rs/curl/latest/curl/easy/struct.Easy2.html) useful if you /// want to decide how to transform the response yourself. + /// + /// This becomes a non-blocking I/O since the actual perform operation is done + /// at the actor side using Curl-Multi. pub async fn send_request(self) -> Result, Error> { - let easy = self.curl.send_request(self.easy).await.map_err(|e| { - trace!("{:?}", e); - Error::Perform(e) - })?; - Ok(easy) + if let Some(actor) = self.actor { + let easy = actor.send_request(self.easy).await.map_err(|e| { + trace!("{:?}", e); + Error::Perform(e) + })?; + Ok(easy) + } else { + panic!("No actor was set!!!"); + } } /// This will perform the curl operation asynchronously. - /// This becomes a non-blocking I/O since the actual perform operation is done - /// at the actor side. pub async fn perform(self) -> Result> { let easy = self.send_request().await?; @@ -865,6 +886,81 @@ where } } +impl HttpClient +where + C: ExtendedHandler + std::fmt::Debug + Send, +{ + /// This will send the request synchronously, + /// and return the underlying [`Easy2`](https://docs.rs/curl/latest/curl/easy/struct.Easy2.html) useful if you + /// want to decide how to transform the response yourself. + pub fn send_request(self) -> Result, Error> { + self.easy.perform().map_err(|e| { + trace!("{:?}", e); + Error::Perform(async_curl::error::Error::Curl(e)) + })?; + + Ok(self.easy) + } + + /// This will perform the curl operation synchronously. + pub fn perform(self) -> Result> { + let easy = self.send_request()?; + + let (data, headers) = easy.get_ref().get_response_body_and_headers(); + let status_code = easy.response_code().map_err(|e| { + trace!("{:?}", e); + Error::Curl(e) + })? as u16; + + let response_header = if let Some(response_header) = headers { + response_header + } else { + let mut response_header = easy + .content_type() + .map_err(|e| { + trace!("{:?}", e); + Error::Curl(e) + })? + .map(|content_type| { + Ok(vec![( + CONTENT_TYPE, + HeaderValue::from_str(content_type).map_err(|err| { + trace!("{:?}", err); + Error::Http(err.to_string()) + })?, + )] + .into_iter() + .collect::()) + }) + .transpose()? + .unwrap_or_else(HeaderMap::new); + + let content_length = easy.content_length_download().map_err(|e| { + trace!("{:?}", e); + Error::Curl(e) + })?; + + response_header.insert( + CONTENT_LENGTH, + HeaderValue::from_str(content_length.to_string().as_str()).map_err(|err| { + trace!("{:?}", err); + Error::Http(err.to_string()) + })?, + ); + + response_header + }; + + Ok(HttpResponse { + status_code: StatusCode::from_u16(status_code).map_err(|err| { + trace!("{:?}", err); + Error::Http(err.to_string()) + })?, + headers: response_header, + body: data, + }) + } +} /// A strong type unit when setting download speed and upload speed /// in bytes per second. #[derive(Deref)] diff --git a/src/lib.rs b/src/lib.rs index 8f77443..de8471d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ //! curl-http-client: This is a wrapper for [Easy2](https://docs.rs/curl/latest/curl/easy/struct.Easy2.html) from [curl-rust](https://docs.rs/curl/latest/curl) crate for ergonomic use -//! and is able to perform asynchronously using [async-curl](https://docs.rs/async-curl/latest/async_curl) crate that uses an actor model +//! and is able to perform synchronously and asynchronously using [async-curl](https://docs.rs/async-curl/latest/async_curl) crate that uses an actor model //! (Message passing) to achieve a non-blocking I/O. //! This requires a dependency with the [curl](https://crates.io/crates/curl), [async-curl](https://crates.io/crates/async-curl) //! [http](https://crates.io/crates/http), [url](https://crates.io/crates/url) and [tokio](https://crates.io/crates/tokio) crates //! -//! # Get Request +//! # Asynchronous Examples +//! ## Get Request //! ```rust,no_run //! use async_curl::actor::CurlActor; //! use curl_http_client::{collector::Collector, http_client::HttpClient, request::HttpRequest}; @@ -13,7 +14,7 @@ //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() { -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! let collector = Collector::Ram(Vec::new()); //! //! let request = HttpRequest { @@ -23,8 +24,9 @@ //! body: None, //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .request(request).unwrap() +//! .nonblocking(actor) //! .perform() //! .await.unwrap(); //! @@ -32,7 +34,7 @@ //! } //! ``` //! -//! # Post Request +//! ## Post Request //! ```rust,no_run //! use async_curl::actor::CurlActor; //! use curl_http_client::{collector::Collector, http_client::HttpClient, request::HttpRequest}; @@ -41,7 +43,7 @@ //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() { -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! let collector = Collector::Ram(Vec::new()); //! //! let request = HttpRequest { @@ -51,8 +53,9 @@ //! body: Some("test body".as_bytes().to_vec()), //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .request(request).unwrap() +//! .nonblocking(actor) //! .perform() //! .await.unwrap(); //! @@ -60,7 +63,7 @@ //! } //! ``` //! -//! # Downloading a File +//! ## Downloading a File //! ```rust,no_run //! use std::path::PathBuf; //! @@ -75,7 +78,7 @@ //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() -> Result<(), Box> { -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! //! let collector = Collector::File(FileInfo::path(PathBuf::from(""))); //! @@ -86,9 +89,10 @@ //! body: None, //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .request(request) //! .unwrap() +//! .nonblocking(actor) //! .perform() //! .await.unwrap(); //! @@ -97,7 +101,7 @@ //! } //! ``` //! -//! # Uploading a File +//! ## Uploading a File //! ```rust,no_run //! use std::{fs, path::PathBuf}; //! @@ -115,7 +119,7 @@ //! let file_to_be_uploaded = PathBuf::from(""); //! let file_size = fs::metadata(file_to_be_uploaded.as_path()).unwrap().len() as usize; //! -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! let collector = Collector::File(FileInfo::path(file_to_be_uploaded)); //! //! let request = HttpRequest { @@ -125,9 +129,10 @@ //! body: None, //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .upload_file_size(FileSize::from(file_size)).unwrap() //! .request(request).unwrap() +//! .nonblocking(actor) //! .perform() //! .await.unwrap(); //! @@ -135,7 +140,7 @@ //! } //! ``` //! -//! # Concurrency +//! ## Concurrency //! ```rust,no_run //! use async_curl::actor::CurlActor; //! use curl_http_client::{collector::Collector, http_client::HttpClient, request::HttpRequest}; @@ -147,11 +152,11 @@ //! async fn main() { //! const NUM_CONCURRENT: usize = 5; //! -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! let mut handles = Vec::new(); //! //! for _n in 0..NUM_CONCURRENT { -//! let curl = curl.clone(); +//! let actor = actor.clone(); //! //! let handle = tokio::spawn(async move { //! let collector = Collector::Ram(Vec::new()); @@ -162,9 +167,10 @@ //! body: None, //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .request(request) //! .unwrap() +//! .nonblocking(actor) //! .perform() //! .await //! .unwrap(); @@ -181,7 +187,7 @@ //! } //! ``` //! -//! # Resume Downloading a File +//! ## Resume Downloading a File //! ```rust,no_run //! use std::fs; //! use std::path::PathBuf; @@ -197,7 +203,7 @@ //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() { -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! let save_to = PathBuf::from(""); //! let collector = Collector::File(FileInfo::path(save_to.clone())); //! @@ -209,9 +215,10 @@ //! body: None, //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .resume_from(BytesOffset::from(partial_download_file_size)).unwrap() //! .request(request).unwrap() +//! .nonblocking(actor) //! .perform() //! .await.unwrap(); //! @@ -237,7 +244,7 @@ //! async fn main() { //! let (tx, mut rx) = channel(1); //! -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! let file_info = FileInfo::path(PathBuf::from("")).with_transfer_speed_sender(tx); //! let collector = Collector::File(file_info); //! @@ -254,8 +261,9 @@ //! body: None, //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .request(request).unwrap() +//! .nonblocking(actor) //! .perform() //! .await.unwrap(); //! @@ -286,7 +294,7 @@ //! let file_to_be_uploaded = PathBuf::from(""); //! let file_size = fs::metadata(file_to_be_uploaded.as_path()).unwrap().len() as usize; //! -//! let curl = CurlActor::new(); +//! let actor = CurlActor::new(); //! let file_info = FileInfo::path(file_to_be_uploaded).with_transfer_speed_sender(tx); //! let collector = Collector::File(file_info); //! @@ -303,9 +311,10 @@ //! body: None, //! }; //! -//! let response = HttpClient::new(curl, collector) +//! let response = HttpClient::new(collector) //! .upload_file_size(FileSize::from(file_size)).unwrap() //! .request(request).unwrap() +//! .nonblocking(actor) //! .perform() //! .await.unwrap(); //! @@ -314,6 +323,130 @@ //! } //! ``` //! +//! # Synchronous Examples +//! ## Get Request +//! ```rust,no_run +//! use curl_http_client::{collector::Collector, http_client::HttpClient, request::HttpRequest}; +//! use http::{HeaderMap, Method}; +//! use url::Url; +//! +//! fn main() { +//! let collector = Collector::Ram(Vec::new()); +//! +//! let request = HttpRequest { +//! url: Url::parse("").unwrap(), +//! method: Method::GET, +//! headers: HeaderMap::new(), +//! body: None, +//! }; +//! +//! let response = HttpClient::new(collector) +//! .request(request).unwrap() +//! .blocking() +//! .perform() +//! .unwrap(); +//! +//! println!("Response: {:?}", response); +//! } +//! ``` +//! +//! ## Post Request +//! ```rust,no_run +//! use curl_http_client::{collector::Collector, http_client::HttpClient, request::HttpRequest}; +//! use http::{HeaderMap, Method}; +//! use url::Url; +//! +//! fn main() { +//! let collector = Collector::Ram(Vec::new()); +//! +//! let request = HttpRequest { +//! url: Url::parse("").unwrap(), +//! method: Method::POST, +//! headers: HeaderMap::new(), +//! body: Some("test body".as_bytes().to_vec()), +//! }; +//! +//! let response = HttpClient::new(collector) +//! .request(request).unwrap() +//! .blocking() +//! .perform() +//! .unwrap(); +//! +//! println!("Response: {:?}", response); +//! } +//! ``` +//! +//! ## Downloading a File +//! ```rust,no_run +//! use std::path::PathBuf; +//! +//! use curl_http_client::{ +//! collector::{Collector, FileInfo}, +//! http_client::HttpClient, +//! request::HttpRequest, +//! }; +//! use http::{HeaderMap, Method}; +//! use url::Url; +//! +//! fn main() -> Result<(), Box> { +//! +//! let collector = Collector::File(FileInfo::path(PathBuf::from(""))); +//! +//! let request = HttpRequest { +//! url: Url::parse("").unwrap(), +//! method: Method::GET, +//! headers: HeaderMap::new(), +//! body: None, +//! }; +//! +//! let response = HttpClient::new(collector) +//! .request(request) +//! .unwrap() +//! .blocking() +//! .perform() +//! .unwrap(); +//! +//! println!("Response: {:?}", response); +//! Ok(()) +//! } +//! ``` +//! +//! ## Uploading a File +//! ```rust,no_run +//! use std::{fs, path::PathBuf}; +//! +//! use curl_http_client::{ +//! collector::{Collector, FileInfo}, +//! http_client::{FileSize, HttpClient}, +//! request::HttpRequest, +//! }; +//! use http::{HeaderMap, Method}; +//! use url::Url; +//! +//! fn main() { +//! let file_to_be_uploaded = PathBuf::from(""); +//! let file_size = fs::metadata(file_to_be_uploaded.as_path()).unwrap().len() as usize; +//! +//! let collector = Collector::File(FileInfo::path(file_to_be_uploaded)); +//! +//! let request = HttpRequest { +//! url: Url::parse("").unwrap(), +//! method: Method::PUT, +//! headers: HeaderMap::new(), +//! body: None, +//! }; +//! +//! let response = HttpClient::new(collector) +//! .upload_file_size(FileSize::from(file_size)).unwrap() +//! .request(request).unwrap() +//! .blocking() +//! .perform() +//! .unwrap(); +//! +//! println!("Response: {:?}", response); +//! } +//! ``` +//! pub mod collector; pub mod error; pub mod http_client; diff --git a/src/test/asynchronous.rs b/src/test/asynchronous.rs index e1c906b..b9adaf8 100644 --- a/src/test/asynchronous.rs +++ b/src/test/asynchronous.rs @@ -30,9 +30,10 @@ async fn test_across_multiple_threads() { let collector = collector.clone(); let request = request.clone(); let handle = tokio::spawn(async move { - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(curl) .perform() .await .unwrap(); diff --git a/src/test/download.rs b/src/test/download.rs index 9025b15..cef2b7b 100644 --- a/src/test/download.rs +++ b/src/test/download.rs @@ -18,7 +18,7 @@ async fn test_download() { let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); let save_to = tempdir.path().join("downloaded_file.jpg"); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(save_to.clone())); let request = HttpRequest { url: target_url, @@ -26,9 +26,10 @@ async fn test_download() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -47,7 +48,7 @@ async fn test_download_with_speed_control() { let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); let save_to = tempdir.path().join("downloaded_file.jpg"); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(save_to.clone())); let request = HttpRequest { url: target_url, @@ -55,11 +56,12 @@ async fn test_download_with_speed_control() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .download_speed(BytesPerSec::from(40000000)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -87,7 +89,7 @@ async fn test_resume_download(offset: usize, expected_status_code: StatusCode) { let partial_file_size = fs::metadata(save_to.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(save_to.clone())); let request = HttpRequest { url: target_url, @@ -95,11 +97,12 @@ async fn test_resume_download(offset: usize, expected_status_code: StatusCode) { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .resume_from(BytesOffset::from(partial_file_size)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -119,7 +122,7 @@ async fn test_download_with_transfer_speed_sender() { let save_to = tempdir.path().join("downloaded_file.jpg"); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let (tx, mut rx) = channel(1); @@ -138,11 +141,12 @@ async fn test_download_with_transfer_speed_sender() { } }); - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .download_speed(BytesPerSec::from(40000000)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -163,7 +167,7 @@ async fn test_download_with_headers() { let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); let save_to = tempdir.path().join("downloaded_file.jpg"); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::FileAndHeaders(FileInfo::path(save_to.clone()), Vec::new()); let request = HttpRequest { url: target_url, @@ -171,9 +175,10 @@ async fn test_download_with_headers() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); diff --git a/src/test/get.rs b/src/test/get.rs index 2569e82..cca5adc 100644 --- a/src/test/get.rs +++ b/src/test/get.rs @@ -13,7 +13,7 @@ async fn test_get() { let (server, _tempdir) = setup_test_environment(responder).await; let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::Ram(Vec::new()); let request = HttpRequest { url: target_url, @@ -21,9 +21,10 @@ async fn test_get() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -40,7 +41,7 @@ async fn test_get_with_headers() { let (server, _tempdir) = setup_test_environment(responder).await; let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::RamAndHeaders(Vec::new(), Vec::new()); let request = HttpRequest { url: target_url, @@ -48,9 +49,10 @@ async fn test_get_with_headers() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -60,3 +62,29 @@ async fn test_get_with_headers() { assert_eq!(response.body.unwrap(), "test body".as_bytes().to_vec()); assert!(!response.headers.is_empty()); } + +#[tokio::test] +async fn test_get_sync() { + let responder = MockResponder::new(ResponderType::Body("test body".as_bytes().to_vec())); + let (server, _tempdir) = setup_test_environment(responder).await; + let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); + + let collector = Collector::Ram(Vec::new()); + let request = HttpRequest { + url: target_url, + method: Method::GET, + headers: HeaderMap::new(), + body: None, + }; + let response = HttpClient::new(collector) + .request(request) + .unwrap() + .blocking() + .perform() + .unwrap(); + + println!("Response: {:?}", response); + assert_eq!(response.status_code, StatusCode::OK); + assert_eq!(response.body.unwrap(), "test body".as_bytes().to_vec()); + assert!(!response.headers.is_empty()); +} diff --git a/src/test/headers.rs b/src/test/headers.rs index dcc22b8..e499473 100644 --- a/src/test/headers.rs +++ b/src/test/headers.rs @@ -15,7 +15,7 @@ async fn test_with_complete_headers_ram_and_header() { let (server, _tempdir) = setup_test_environment(responder).await; let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::RamAndHeaders(Vec::new(), Vec::new()); let request = HttpRequest { url: target_url, @@ -23,9 +23,10 @@ async fn test_with_complete_headers_ram_and_header() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .send_request() .await .unwrap(); @@ -48,7 +49,7 @@ async fn test_with_complete_headers_file_and_headers() { let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); let save_to = tempdir.path().join("downloaded_file.jpg"); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::FileAndHeaders(FileInfo::path(save_to.clone()), Vec::new()); let request = HttpRequest { url: target_url, @@ -56,9 +57,10 @@ async fn test_with_complete_headers_file_and_headers() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .send_request() .await .unwrap(); @@ -81,7 +83,7 @@ async fn test_with_complete_headers_ram() { let (server, _tempdir) = setup_test_environment(responder).await; let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::Ram(Vec::new()); let request = HttpRequest { url: target_url, @@ -89,9 +91,10 @@ async fn test_with_complete_headers_ram() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .send_request() .await .unwrap(); @@ -114,7 +117,7 @@ async fn test_with_complete_headers_file() { let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); let save_to = tempdir.path().join("downloaded_file.jpg"); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(save_to.clone())); let request = HttpRequest { url: target_url, @@ -122,9 +125,10 @@ async fn test_with_complete_headers_file() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .send_request() .await .unwrap(); @@ -140,3 +144,34 @@ async fn test_with_complete_headers_file() { assert_eq!(response.response_code().unwrap(), 200); assert_eq!(fs::read(save_to).unwrap(), include_bytes!("sample.jpg")); } + +#[tokio::test] +async fn test_with_complete_headers_ram_and_header_sync() { + let responder = MockResponder::new(ResponderType::Body("test body".as_bytes().to_vec())); + let (server, _tempdir) = setup_test_environment(responder).await; + let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); + + let collector = Collector::RamAndHeaders(Vec::new(), Vec::new()); + let request = HttpRequest { + url: target_url, + method: Method::GET, + headers: HeaderMap::new(), + body: None, + }; + let response = HttpClient::new(collector) + .request(request) + .unwrap() + .blocking() + .send_request() + .unwrap(); + + let (body, headers) = response.get_ref().get_response_body_and_headers(); + + println!("body: {:?}", body); + println!("headers: {:?}", headers); + println!("status: {:?}", response.response_code().unwrap()); + + assert!(headers.is_some()); + assert_eq!(body.unwrap(), "test body".as_bytes().to_vec()); + assert_eq!(response.response_code().unwrap(), 200); +} diff --git a/src/test/post.rs b/src/test/post.rs index dad0758..0c89ccf 100644 --- a/src/test/post.rs +++ b/src/test/post.rs @@ -13,7 +13,7 @@ async fn test_post() { let (server, _tempdir) = setup_test_environment(responder).await; let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::Ram(Vec::new()); let request = HttpRequest { url: target_url, @@ -21,9 +21,10 @@ async fn test_post() { headers: HeaderMap::new(), body: Some("test body".as_bytes().to_vec()), }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -40,7 +41,7 @@ async fn test_post_with_headers() { let (server, _tempdir) = setup_test_environment(responder).await; let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::RamAndHeaders(Vec::new(), Vec::new()); let request = HttpRequest { url: target_url, @@ -48,9 +49,10 @@ async fn test_post_with_headers() { headers: HeaderMap::new(), body: Some("test body".as_bytes().to_vec()), }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -60,3 +62,29 @@ async fn test_post_with_headers() { assert_eq!(response.body.unwrap().len(), 0); assert!(!response.headers.is_empty()); } + +#[tokio::test] +async fn test_post_sync() { + let responder = MockResponder::new(ResponderType::Body("test body".as_bytes().to_vec())); + let (server, _tempdir) = setup_test_environment(responder).await; + let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); + + let collector = Collector::Ram(Vec::new()); + let request = HttpRequest { + url: target_url, + method: Method::POST, + headers: HeaderMap::new(), + body: Some("test body".as_bytes().to_vec()), + }; + let response = HttpClient::new(collector) + .request(request) + .unwrap() + .blocking() + .perform() + .unwrap(); + + println!("Response: {:?}", response); + assert_eq!(response.status_code, StatusCode::OK); + assert_eq!(response.body.unwrap().len(), 0); + assert!(!response.headers.is_empty()); +} diff --git a/src/test/upload.rs b/src/test/upload.rs index 63344b9..49eeea7 100644 --- a/src/test/upload.rs +++ b/src/test/upload.rs @@ -20,7 +20,7 @@ async fn test_upload() { fs::write(to_be_uploaded.as_path(), include_bytes!("sample.jpg")).unwrap(); let file_size = fs::metadata(to_be_uploaded.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(to_be_uploaded)); let request = HttpRequest { url: target_url, @@ -28,11 +28,12 @@ async fn test_upload() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .upload_file_size(FileSize::from(file_size)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -53,7 +54,7 @@ async fn test_upload_with_speed_control() { fs::write(to_be_uploaded.clone(), include_bytes!("sample.jpg")).unwrap(); let file_size = fs::metadata(to_be_uploaded.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::File(FileInfo::path(to_be_uploaded)); let request = HttpRequest { url: target_url, @@ -61,13 +62,14 @@ async fn test_upload_with_speed_control() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .upload_file_size(FileSize::from(file_size)) .unwrap() .upload_speed(BytesPerSec::from(40000000)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -89,7 +91,7 @@ async fn test_upload_with_transfer_speed_sender() { let file_size = fs::metadata(to_be_uploaded.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let (tx, mut rx) = channel(1); @@ -108,13 +110,14 @@ async fn test_upload_with_transfer_speed_sender() { } }); - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .upload_file_size(FileSize::from(file_size)) .unwrap() .upload_speed(BytesPerSec::from(40000000)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -137,7 +140,7 @@ async fn test_upload_with_headers() { fs::write(to_be_uploaded.as_path(), include_bytes!("sample.jpg")).unwrap(); let file_size = fs::metadata(to_be_uploaded.as_path()).unwrap().len() as usize; - let curl = CurlActor::new(); + let actor = CurlActor::new(); let collector = Collector::FileAndHeaders(FileInfo::path(to_be_uploaded), Vec::new()); let request = HttpRequest { url: target_url, @@ -145,11 +148,12 @@ async fn test_upload_with_headers() { headers: HeaderMap::new(), body: None, }; - let response = HttpClient::new(curl, collector) + let response = HttpClient::new(collector) .upload_file_size(FileSize::from(file_size)) .unwrap() .request(request) .unwrap() + .nonblocking(actor) .perform() .await .unwrap(); @@ -159,3 +163,35 @@ async fn test_upload_with_headers() { assert_eq!(response.body, None); assert!(!response.headers.is_empty()); } + +#[tokio::test] +async fn test_upload_sync() { + let responder = MockResponder::new(ResponderType::File); + let (server, tempdir) = setup_test_environment(responder).await; + let target_url = Url::parse(format!("{}/test", server.uri()).as_str()).unwrap(); + + let to_be_uploaded = tempdir.path().join("file_to_be_uploaded.jpg"); + fs::write(to_be_uploaded.as_path(), include_bytes!("sample.jpg")).unwrap(); + let file_size = fs::metadata(to_be_uploaded.as_path()).unwrap().len() as usize; + + let collector = Collector::File(FileInfo::path(to_be_uploaded)); + let request = HttpRequest { + url: target_url, + method: Method::PUT, + headers: HeaderMap::new(), + body: None, + }; + let response = HttpClient::new(collector) + .upload_file_size(FileSize::from(file_size)) + .unwrap() + .request(request) + .unwrap() + .blocking() + .perform() + .unwrap(); + + println!("Response: {:?}", response); + assert_eq!(response.status_code, StatusCode::OK); + assert_eq!(response.body, None); + assert!(!response.headers.is_empty()); +}