From 3cca3f82c99cc6f22be28c93ea1b346e14df2010 Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Sun, 28 Jul 2024 09:03:31 +0200 Subject: [PATCH] RedirectPolicy init --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 11 +- ...builder_get.rs => advanced_request_get.rs} | 4 +- src/lib.rs | 2 +- src/request.rs | 147 +++++++++++------- src/response.rs | 14 +- src/stream.rs | 11 +- src/tls.rs | 8 +- 9 files changed, 125 insertions(+), 76 deletions(-) rename examples/{request_builder_get.rs => advanced_request_get.rs} (94%) diff --git a/Cargo.lock b/Cargo.lock index 46d9c60..b8c6ab2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,7 +218,7 @@ dependencies = [ [[package]] name = "http_req" -version = "0.11.0" +version = "0.12.0" dependencies = [ "native-tls", "rustls", diff --git a/Cargo.toml b/Cargo.toml index bb5d947..b5b37e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "http_req" -version = "0.11.0" +version = "0.12.0" license = "MIT" description = "simple and lightweight HTTP client with built-in HTTPS support" repository = "https://github.com/jayjamesjay/http_req" diff --git a/README.md b/README.md index b5d6db8..f23c88d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # http_req > [!CAUTION] -> v0.11.0 introduces major changes to design of `RequestBuilder` and `Request`. Please review [documentation](https://docs.rs/http_req/0.11.0/http_req/) before migrating from previous versions. +> v0.12.0 replaces `RequestBuilder` with `RequestMessage`. Please review [documentation](https://docs.rs/http_req/0.12.0/http_req/) before migrating from previous versions. [![Rust](https://github.com/jayjamesjay/http_req/actions/workflows/rust.yml/badge.svg)](https://github.com/jayjamesjay/http_req/actions/workflows/rust.yml) -[![Crates.io](https://img.shields.io/badge/crates.io-v0.11.0-orange.svg?longCache=true)](https://crates.io/crates/http_req) -[![Docs.rs](https://docs.rs/http_req/badge.svg)](https://docs.rs/http_req/0.11.0/http_req/) +[![Crates.io](https://img.shields.io/badge/crates.io-v0.12.0-orange.svg?longCache=true)](https://crates.io/crates/http_req) +[![Docs.rs](https://docs.rs/http_req/badge.svg)](https://docs.rs/http_req/0.12.0/http_req/) Simple and lightweight HTTP client with built-in HTTPS support. +- HTTP and HTTPS via [rust-native-tls](https://github.com/sfackler/rust-native-tls) (or optionally [rus-tls](https://crates.io/crates/rustls)) +- Small binary size (less than 0.7 MB for basic GET request) +- Minimal amount of dependencies ## Requirements http_req by default uses [rust-native-tls](https://github.com/sfackler/rust-native-tls), @@ -32,7 +35,7 @@ Take a look at [more examples](https://github.com/jayjamesjay/http_req/tree/mast In order to use `http_req` with `rustls` in your project, add the following lines to `Cargo.toml`: ```toml [dependencies] -http_req = {version="^0.11", default-features = false, features = ["rust-tls"]} +http_req = {version="^0.12", default-features = false, features = ["rust-tls"]} ``` ## License diff --git a/examples/request_builder_get.rs b/examples/advanced_request_get.rs similarity index 94% rename from examples/request_builder_get.rs rename to examples/advanced_request_get.rs index e52be18..abf6788 100644 --- a/examples/request_builder_get.rs +++ b/examples/advanced_request_get.rs @@ -1,5 +1,5 @@ use http_req::{ - request::RequestBuilder, + request::RequestMessage, response::Response, stream::{self, Stream}, uri::Uri, @@ -19,7 +19,7 @@ fn main() { let mut body = Vec::new(); // Prepares a request message. - let request_msg = RequestBuilder::new(&addr) + let request_msg = RequestMessage::new(&addr) .header("Connection", "Close") .parse(); diff --git a/src/lib.rs b/src/lib.rs index 2de2c9e..77ad98e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ //! Simple HTTP client with built-in HTTPS support. -//! +//! //! By default uses [rust-native-tls](https://github.com/sfackler/rust-native-tls), //! which relies on TLS framework provided by OS on Windows and macOS, and OpenSSL //! on all other platforms. But it also supports [rus-tls](https://crates.io/crates/rustls). diff --git a/src/request.rs b/src/request.rs index c079892..17e31b8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -78,21 +78,55 @@ impl fmt::Display for HttpVersion { } } -/// Raw HTTP request that can be sent to any stream +pub struct RequestBuilder {} + +#[deprecated( + since = "0.12.0", + note = "RequestBuilder was replaced with RequestMessage" +)] +impl<'a> RequestBuilder { + pub fn new(uri: &'a Uri<'a>) -> RequestMessage<'a> { + RequestMessage::new(uri) + } +} + +/// Allows to control redirects +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum RedirectPolicy bool> { + /// Follows redirect if limit is greater than 0. + Limit(usize), + /// Runs functions `F` to determine if redirect should be followed. + Custom(F), +} + +impl bool> RedirectPolicy { + /// Checks the policy againt specified conditions. + /// Returns `true` if redirect should be followed. + pub fn follow(&self) -> bool { + use self::RedirectPolicy::*; + + match self { + Limit(limit) => *limit > 0, + Custom(func) => func(), + } + } +} + +/// Raw HTTP request message that can be sent to any stream /// /// # Examples /// ``` /// use std::convert::TryFrom; -/// use http_req::{request::RequestBuilder, uri::Uri}; +/// use http_req::{request::RequestMessage, uri::Uri}; /// /// let addr: Uri = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// -/// let mut request_msg = RequestBuilder::new(&addr) +/// let mut request_msg = RequestMessage::new(&addr) /// .header("Connection", "Close") /// .parse(); /// ``` #[derive(Clone, Debug, PartialEq)] -pub struct RequestBuilder<'a> { +pub struct RequestMessage<'a> { uri: &'a Uri<'a>, method: Method, version: HttpVersion, @@ -100,21 +134,21 @@ pub struct RequestBuilder<'a> { body: Option<&'a [u8]>, } -impl<'a> RequestBuilder<'a> { - /// Creates a new `RequestBuilder` with default parameters +impl<'a> RequestMessage<'a> { + /// Creates a new `RequestMessage` with default parameters /// /// # Examples /// ``` /// use std::convert::TryFrom; - /// use http_req::{request::RequestBuilder, uri::Uri}; + /// use http_req::{request::RequestMessage, uri::Uri}; /// /// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// - /// let request_builder = RequestBuilder::new(&addr) + /// let request_msg = RequestMessage::new(&addr) /// .header("Connection", "Close"); /// ``` - pub fn new(uri: &'a Uri<'a>) -> RequestBuilder<'a> { - RequestBuilder { + pub fn new(uri: &'a Uri<'a>) -> RequestMessage<'a> { + RequestMessage { headers: Headers::default_http(uri), uri, method: Method::GET, @@ -128,11 +162,11 @@ impl<'a> RequestBuilder<'a> { /// # Examples /// ``` /// use std::convert::TryFrom; - /// use http_req::{request::{RequestBuilder, Method}, uri::Uri}; + /// use http_req::{request::{RequestMessage, Method}, uri::Uri}; /// /// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// - /// let request_builder = RequestBuilder::new(&addr) + /// let request_msg = RequestMessage::new(&addr) /// .method(Method::HEAD); /// ``` pub fn method(&mut self, method: T) -> &mut Self @@ -148,11 +182,11 @@ impl<'a> RequestBuilder<'a> { /// # Examples /// ``` /// use std::convert::TryFrom; - /// use http_req::{request::{RequestBuilder, HttpVersion}, uri::Uri}; + /// use http_req::{request::{RequestMessage, HttpVersion}, uri::Uri}; /// /// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// - /// let request_builder = RequestBuilder::new(&addr) + /// let request_msg = RequestMessage::new(&addr) /// .version(HttpVersion::Http10); /// ``` pub fn version(&mut self, version: T) -> &mut Self @@ -168,7 +202,7 @@ impl<'a> RequestBuilder<'a> { /// # Examples /// ``` /// use std::convert::TryFrom; - /// use http_req::{request::RequestBuilder, response::Headers, uri::Uri}; + /// use http_req::{request::RequestMessage, response::Headers, uri::Uri}; /// /// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// @@ -178,7 +212,7 @@ impl<'a> RequestBuilder<'a> { /// headers.insert("Host", "rust-lang.org"); /// headers.insert("Connection", "Close"); /// - /// let request_builder = RequestBuilder::new(&addr) + /// let request_msg = RequestMessage::new(&addr) /// .headers(headers); /// ``` pub fn headers(&mut self, headers: T) -> &mut Self @@ -194,11 +228,11 @@ impl<'a> RequestBuilder<'a> { /// # Examples /// ``` /// use std::convert::TryFrom; - /// use http_req::{request::RequestBuilder, response::Headers, uri::Uri}; + /// use http_req::{request::RequestMessage, response::Headers, uri::Uri}; /// /// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// - /// let request_builder = RequestBuilder::new(&addr) + /// let request_msg = RequestMessage::new(&addr) /// .header("Connection", "Close"); /// ``` pub fn header(&mut self, key: &T, val: &U) -> &mut Self @@ -215,12 +249,12 @@ impl<'a> RequestBuilder<'a> { /// # Examples /// ``` /// use std::convert::TryFrom; - /// use http_req::{request::{RequestBuilder, Method}, response::Headers, uri::Uri}; + /// use http_req::{request::{RequestMessage, Method}, response::Headers, uri::Uri}; /// /// let addr = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// const BODY: &[u8; 27] = b"field1=value1&field2=value2"; /// - /// let request_builder = RequestBuilder::new(&addr) + /// let request_msg = RequestMessage::new(&addr) /// .method(Method::POST) /// .body(BODY) /// .header("Content-Length", &BODY.len()) @@ -231,16 +265,16 @@ impl<'a> RequestBuilder<'a> { self } - /// Parses the request message for this `RequestBuilder` + /// Parses the request message for this `RequestMessage` /// /// # Examples /// ``` /// use std::convert::TryFrom; - /// use http_req::{request::RequestBuilder, uri::Uri}; + /// use http_req::{request::RequestMessage, uri::Uri}; /// /// let addr: Uri = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); /// - /// let mut request_msg = RequestBuilder::new(&addr) + /// let mut request_msg = RequestMessage::new(&addr) /// .header("Connection", "Close") /// .parse(); /// ``` @@ -288,7 +322,7 @@ impl<'a> RequestBuilder<'a> { /// #[derive(Clone, Debug, PartialEq)] pub struct Request<'a> { - inner: RequestBuilder<'a>, + messsage: RequestMessage<'a>, connect_timeout: Option, read_timeout: Option, write_timeout: Option, @@ -309,11 +343,11 @@ impl<'a> Request<'a> { /// let request = Request::new(&uri); /// ``` pub fn new(uri: &'a Uri) -> Request<'a> { - let mut builder = RequestBuilder::new(&uri); - builder.header("Connection", "Close"); + let mut message = RequestMessage::new(&uri); + message.header("Connection", "Close"); Request { - inner: builder, + messsage: message, connect_timeout: Some(Duration::from_secs(60)), read_timeout: Some(Duration::from_secs(60)), write_timeout: Some(Duration::from_secs(60)), @@ -338,7 +372,7 @@ impl<'a> Request<'a> { where Method: From, { - self.inner.method(method); + self.messsage.method(method); self } @@ -359,7 +393,7 @@ impl<'a> Request<'a> { where HttpVersion: From, { - self.inner.version(version); + self.messsage.version(version); self } @@ -385,7 +419,7 @@ impl<'a> Request<'a> { where Headers: From, { - self.inner.headers(headers); + self.messsage.headers(headers); self } @@ -406,7 +440,7 @@ impl<'a> Request<'a> { T: ToString + ?Sized, U: ToString + ?Sized, { - self.inner.header(key, val); + self.messsage.header(key, val); self } @@ -426,7 +460,7 @@ impl<'a> Request<'a> { /// .body(body); /// ``` pub fn body(&mut self, body: &'a [u8]) -> &mut Self { - self.inner.body(body); + self.messsage.body(body); self } @@ -572,13 +606,13 @@ impl<'a> Request<'a> { T: Write, { // Set up a stream. - let mut stream = Stream::new(self.inner.uri, self.connect_timeout)?; + let mut stream = Stream::new(self.messsage.uri, self.connect_timeout)?; stream.set_read_timeout(self.read_timeout)?; stream.set_write_timeout(self.write_timeout)?; - stream = Stream::try_to_https(stream, self.inner.uri, self.root_cert_file_pem)?; + stream = Stream::try_to_https(stream, self.messsage.uri, self.root_cert_file_pem)?; // Send the request message to stream. - let request_msg = self.inner.parse(); + let request_msg = self.messsage.parse(); stream.write_all(&request_msg)?; // Set up variables @@ -608,16 +642,13 @@ impl<'a> Request<'a> { let response = Response::from_head(&raw_response_head)?; let content_len = response.content_len().unwrap_or(1); - let encoding = response.headers().get("Transfer-Encoding"); let mut params = Vec::with_capacity(5); - if let Some(encode) = encoding { - if encode == "chunked" { - params.push("chunked"); - } + if response.is_chunked() { + params.push("chunked"); } - if content_len > 0 && self.inner.method != Method::HEAD { + if content_len > 0 && self.messsage.method != Method::HEAD { params.push("non-empty"); } @@ -715,22 +746,22 @@ mod tests { } #[test] - fn request_b_new() { - RequestBuilder::new(&Uri::try_from(URI).unwrap()); - RequestBuilder::new(&Uri::try_from(URI_S).unwrap()); + fn request_m_new() { + RequestMessage::new(&Uri::try_from(URI).unwrap()); + RequestMessage::new(&Uri::try_from(URI_S).unwrap()); } #[test] - fn request_b_method() { + fn request_m_method() { let uri = Uri::try_from(URI).unwrap(); - let mut req = RequestBuilder::new(&uri); + let mut req = RequestMessage::new(&uri); let req = req.method(Method::HEAD); assert_eq!(req.method, Method::HEAD); } #[test] - fn request_b_headers() { + fn request_m_headers() { let mut headers = Headers::new(); headers.insert("Accept-Charset", "utf-8"); headers.insert("Accept-Language", "en-US"); @@ -738,16 +769,16 @@ mod tests { headers.insert("Connection", "Close"); let uri = Uri::try_from(URI).unwrap(); - let mut req = RequestBuilder::new(&uri); + let mut req = RequestMessage::new(&uri); let req = req.headers(headers.clone()); assert_eq!(req.headers, headers); } #[test] - fn request_b_header() { + fn request_m_header() { let uri = Uri::try_from(URI).unwrap(); - let mut req = RequestBuilder::new(&uri); + let mut req = RequestMessage::new(&uri); let k = "Connection"; let v = "Close"; @@ -761,18 +792,18 @@ mod tests { } #[test] - fn request_b_body() { + fn request_m_body() { let uri = Uri::try_from(URI).unwrap(); - let mut req = RequestBuilder::new(&uri); + let mut req = RequestMessage::new(&uri); let req = req.body(&BODY); assert_eq!(req.body, Some(BODY.as_ref())); } #[test] - fn request_b_parse() { + fn request_m_parse() { let uri = Uri::try_from(URI).unwrap(); - let req = RequestBuilder::new(&uri); + let req = RequestMessage::new(&uri); const DEFAULT_MSG: &str = "GET /std/string/index.html HTTP/1.1\r\n\ Host: doc.rust-lang.org\r\n\r\n"; @@ -800,7 +831,7 @@ mod tests { let mut req = Request::new(&uri); req.method(Method::HEAD); - assert_eq!(req.inner.method, Method::HEAD); + assert_eq!(req.messsage.method, Method::HEAD); } #[test] @@ -815,7 +846,7 @@ mod tests { let mut req = Request::new(&uri); let req = req.headers(headers.clone()); - assert_eq!(req.inner.headers, headers); + assert_eq!(req.messsage.headers, headers); } #[test] @@ -832,7 +863,7 @@ mod tests { let req = req.header(k, v); - assert_eq!(req.inner.headers, expect_headers); + assert_eq!(req.messsage.headers, expect_headers); } #[test] @@ -841,7 +872,7 @@ mod tests { let mut req = Request::new(&uri); let req = req.body(&BODY); - assert_eq!(req.inner.body, Some(BODY.as_ref())); + assert_eq!(req.messsage.body, Some(BODY.as_ref())); } #[test] diff --git a/src/response.rs b/src/response.rs index 895e6d6..6295c0d 100644 --- a/src/response.rs +++ b/src/response.rs @@ -60,7 +60,10 @@ impl Response { /// /// let response = Response::try_from(RESPONSE, &mut body).unwrap(); /// ``` - pub fn try_from(res: &[u8], writer: &mut T) -> Result { + pub fn try_from(res: &[u8], writer: &mut T) -> Result + where + T: Write, + { if res.is_empty() { Err(Error::Parse(ParseErr::Empty)) } else { @@ -152,7 +155,7 @@ impl Response { /// let response = Response::try_from(RESPONSE, &mut body).unwrap(); /// let headers = response.headers(); /// ``` - pub fn headers(&self) -> &Headers { + pub const fn headers(&self) -> &Headers { &self.headers } @@ -178,6 +181,13 @@ impl Response { .get("Content-Length") .and_then(|len| len.parse().ok()) } + + /// Checks if Transfer-Encoding includes "chunked". + pub fn is_chunked(&self) -> bool { + self.headers() + .get("Transfer-Encoding") + .is_some_and(|encodings| encodings.contains("chunked")) + } } /// Status of HTTP response diff --git a/src/stream.rs b/src/stream.rs index 1afe877..27b0e60 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -13,6 +13,7 @@ const BUF_SIZE: usize = 16 * 1000; /// Wrapper around TCP stream for HTTP and HTTPS protocols. /// Allows to perform common operations on underlying stream. +#[derive(Debug)] pub enum Stream { Http(TcpStream), Https(Conn), @@ -226,10 +227,10 @@ where /// Exexcutes a function in a loop until operation is completed or deadline is exceeded. /// /// It checks if a timeout was exceeded every iteration, therefore it limits -/// how many time a specific function can be called before deadline. -/// For the `execute_with_deadline` to meet the deadline, each call -/// to `func` needs finish before the deadline. -/// +/// how many time a specific function can be called before deadline. +/// For the `execute_with_deadline` to meet the deadline, each call +/// to `func` needs finish before the deadline. +/// /// Key information about function `func`: /// - is provided with information about remaining time /// - must ensure that its execution will not take more time than specified in `remaining_time` @@ -250,7 +251,7 @@ where /// Reads the head of HTTP response from `reader`. /// -/// Reads from `reader` (line by line) until a blank line is identified, +/// Reads from `reader` (line by line) until a blank line is identified, /// which indicates that all meta-information has been read, pub fn read_head(reader: &mut B) -> Vec where diff --git a/src/tls.rs b/src/tls.rs index 9cd771e..edb5732 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -18,9 +18,10 @@ use rustls_pki_types::ServerName; #[cfg(not(any(feature = "native-tls", feature = "rust-tls")))] compile_error!("one of the `native-tls` or `rust-tls` features must be enabled"); -/// Wrapper around TLS Stream, depends on selected TLS library (`S: io::Read + io::Write`): +/// Wrapper around TLS Stream, depends on selected TLS library: /// - native_tls: `TlsStream` /// - rustls: `StreamOwned` +#[derive(Debug)] pub struct Conn { #[cfg(feature = "native-tls")] stream: native_tls::TlsStream, @@ -70,7 +71,10 @@ where } } -impl io::Write for Conn { +impl io::Write for Conn +where + S: io::Read + io::Write, +{ fn write(&mut self, buf: &[u8]) -> Result { self.stream.write(buf) }