From eabd13a9518cfd342d4bd3fbc827b958de08d71b Mon Sep 17 00:00:00 2001 From: fewensa Date: Fri, 26 Nov 2021 23:55:29 +0800 Subject: [PATCH 01/19] format and special toolchain --- rust-toolchain.toml | 4 ++++ rustfmt.toml | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 rust-toolchain.toml create mode 100644 rustfmt.toml diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..7f50794 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["cargo", "clippy", "rustc", "rustfmt", "rust-src"] +profile = "minimal" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..c809f4f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +hard_tabs = false +max_width = 100 +newline_style = "Unix" +reorder_imports = true +reorder_modules = true +tab_spaces = 2 +use_field_init_shorthand = true From c9ac3aa05f0eacec6e99ad02755e6f3ef173eed2 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 16:41:08 +0800 Subject: [PATCH 02/19] refactor raw builder --- rttp/src/lib.rs | 9 +- rttp_client/Cargo.toml | 25 +- rttp_client/src/client.rs | 15 +- .../src/connection/block_connection.rs | 66 +- rttp_client/src/connection/connection.rs | 112 +++- .../src/connection/connection_reader.rs | 5 +- rttp_client/src/error.rs | 27 +- .../request/builder/build_body_async_std.rs | 65 ++ .../src/request/builder/build_body_block.rs | 253 ++++++++ .../src/request/builder/build_header.rs | 171 +++++ .../src/request/builder/build_para_and_url.rs | 67 ++ rttp_client/src/request/builder/common.rs | 81 +++ rttp_client/src/request/builder/form_data.rs | 68 ++ rttp_client/src/request/builder/mod.rs | 8 + rttp_client/src/request/mod.rs | 4 +- rttp_client/src/request/raw_builder.rs | 600 ------------------ rttp_client/src/request/raw_request.rs | 33 +- rttp_client/src/request/request.rs | 123 +++- rttp_client/src/types/url.rs | 208 +++--- rttp_client/tests/test_http_basic.rs | 23 +- 20 files changed, 1079 insertions(+), 884 deletions(-) create mode 100644 rttp_client/src/request/builder/build_body_async_std.rs create mode 100644 rttp_client/src/request/builder/build_body_block.rs create mode 100644 rttp_client/src/request/builder/build_header.rs create mode 100644 rttp_client/src/request/builder/build_para_and_url.rs create mode 100644 rttp_client/src/request/builder/common.rs create mode 100644 rttp_client/src/request/builder/form_data.rs create mode 100644 rttp_client/src/request/builder/mod.rs delete mode 100644 rttp_client/src/request/raw_builder.rs diff --git a/rttp/src/lib.rs b/rttp/src/lib.rs index 06ce4a6..90060fe 100644 --- a/rttp/src/lib.rs +++ b/rttp/src/lib.rs @@ -1,12 +1,11 @@ pub struct Http {} - impl Http { #[cfg(any( - feature = "all", - feature = "client", - feature = "client_tls_rustls", - feature = "client_tls_native", + feature = "all", + feature = "client", + feature = "client_tls_rustls", + feature = "client_tls_native", ))] pub fn client() -> rttp_client::HttpClient { rttp_client::HttpClient::new() diff --git a/rttp_client/Cargo.toml b/rttp_client/Cargo.toml index a264c0c..5298545 100644 --- a/rttp_client/Cargo.toml +++ b/rttp_client/Cargo.toml @@ -18,25 +18,26 @@ readme = "README.md" edition = "2018" [dependencies] -url = "2" +tracing = "0.1" + +url = "2" percent-encoding = "2" -mime = "0.3" +mime = "0.3" mime_guess = "2" -rand = "0.7" -socks = "0.3" -base64 = "0.11" -flate2 = "1.0" +rand = "0.7" +socks = "0.3" +base64 = "0.11" +flate2 = "1.0" httpdate = "0.3" -native-tls = { version = "0.2", optional = true } -rustls = { version = "0.16", optional = true } -webpki-roots = { version = "0.18", optional = true } -webpki = { version = "0.21", optional = true } - -async-std = { version = "1", optional = true } +native-tls = { optional = true, version = "0.2" } +rustls = { optional = true, version = "0.16" } +webpki-roots = { optional = true, version = "0.18" } +webpki = { optional = true, version = "0.21" } +async-std = { optional = true, version = "1", features = ["std"] } [features] default = [] diff --git a/rttp_client/src/client.rs b/rttp_client/src/client.rs index 74d0d1a..cb0d8fb 100644 --- a/rttp_client/src/client.rs +++ b/rttp_client/src/client.rs @@ -1,10 +1,10 @@ -use crate::{Config, error}; #[cfg(feature = "async")] use crate::connection::AsyncConnection; use crate::connection::BlockConnection; use crate::request::{RawRequest, Request}; use crate::response::Response; use crate::types::{Header, IntoHeader, IntoPara, Proxy, ToFormData, ToRoUrl}; +use crate::{error, Config}; #[derive(Debug)] pub struct HttpClient { @@ -14,13 +14,12 @@ pub struct HttpClient { impl Default for HttpClient { fn default() -> Self { Self { - request: Request::new() + request: Request::new(), } } } impl HttpClient { - /// Create a `HttpClient` object. /// # Examples /// ```rust @@ -32,14 +31,11 @@ impl HttpClient { } pub(crate) fn with_request(request: Request) -> Self { - Self { - request - } + Self { request } } } impl HttpClient { - /// Set count of this request auto redirect times. pub(crate) fn count(&mut self, count: u32) -> &mut Self { self.request.count_set(count); @@ -130,11 +126,12 @@ impl HttpClient { unimplemented!() } - /// Add request header + /// Add request header pub fn header(&mut self, header: P) -> &mut Self { let mut headers = self.request.headers_mut(); for h in header.into_headers() { - let mut exi = headers.iter_mut() + let mut exi = headers + .iter_mut() .find(|d| d.name().eq_ignore_ascii_case(h.name())); if let Some(eh) = exi { diff --git a/rttp_client/src/connection/block_connection.rs b/rttp_client/src/connection/block_connection.rs index 8ffc19b..3b3bc71 100644 --- a/rttp_client/src/connection/block_connection.rs +++ b/rttp_client/src/connection/block_connection.rs @@ -1,7 +1,7 @@ -use std::{io, time}; use std::io::{Read, Write}; use std::net::TcpStream; use std::sync::Arc; +use std::{io, time}; #[cfg(feature = "tls-native")] use native_tls::TlsConnector; @@ -10,30 +10,32 @@ use rustls::{Session, TLSError}; use socks::{Socks4Stream, Socks5Stream}; use url::Url; -use crate::{error, HttpClient}; use crate::connection::connection::Connection; use crate::request::RawRequest; use crate::response::Response; use crate::types::{Proxy, ProxyType}; +use crate::{error, HttpClient}; pub struct BlockConnection<'a> { - conn: Connection<'a> + conn: Connection<'a>, } impl<'a> BlockConnection<'a> { pub fn new(request: RawRequest<'a>) -> Self { - Self { conn: Connection::new(request) } + Self { + conn: Connection::new(request), + } } pub fn block_call(mut self) -> error::Result { let url = self.conn.url().map_err(error::builder)?; -// let header = self.request.header(); -// let body = self.request.body(); -// println!("{}", header); -// if let Some(b) = body { -// println!("{}", b.string()?); -// } + // let header = self.request.header(); + // let body = self.request.body(); + // println!("{}", header); + // if let Some(b) = body { + // println!("{}", b.string()?); + // } let proxy = self.conn.proxy(); @@ -74,26 +76,13 @@ impl<'a> BlockConnection<'a> { impl<'a> BlockConnection<'a> { fn call_with_proxy(&self, url: &Url, proxy: &Proxy) -> error::Result> { match proxy.type_() { - ProxyType::HTTP => self.call_with_proxy_https(url, proxy), - ProxyType::HTTPS => self.call_with_proxy_https(url, proxy), + ProxyType::HTTP | ProxyType::HTTPS => self.call_with_proxy_https(url, proxy), ProxyType::SOCKS4 => self.call_with_proxy_socks4(url, proxy), ProxyType::SOCKS5 => self.call_with_proxy_socks5(url, proxy), } } -// fn call_with_proxy_http(&self, url: &Url, proxy: &Proxy) -> error::Result> { -// let header = self.request.header(); -// let body = self.request.body(); -// -// let addr = format!("{}:{}", proxy.host(), proxy.port()); -// let mut stream = self.tcp_stream(&addr)?; -// self.call_tcp_stream_http(stream) -// } - fn call_with_proxy_https(&self, url: &Url, proxy: &Proxy) -> error::Result> { - let host = self.conn.host(url)?; - let port = self.conn.port(url)?; - //CONNECT proxy.google.com:443 HTTP/1.1 //Host: www.google.com:443 //Proxy-Connection: keep-alive @@ -102,19 +91,25 @@ impl<'a> BlockConnection<'a> { let addr = format!("{}:{}", proxy.host(), proxy.port()); let mut stream = self.conn.block_tcp_stream(&addr)?; - stream.write(connect_header.as_bytes()).map_err(error::request)?; + stream + .write(connect_header.as_bytes()) + .map_err(error::request)?; stream.flush().map_err(error::request)?; //HTTP/1.1 200 Connection Established let mut res = [0u8; 1024]; stream.read(&mut res).map_err(error::request)?; - let res_s = match String::from_utf8(res.to_vec()) { - Ok(r) => r, - Err(_) => return Err(error::bad_proxy("parse proxy server response error.")) - }; - if !res_s.to_ascii_lowercase().contains("connection established") { - return Err(error::bad_proxy("Proxy server response error.")); + let res_s = String::from_utf8(res.to_vec()) + .map_err(|_| error::bad_proxy("parse proxy server response error."))?; + if !res_s + .to_ascii_lowercase() + .contains("connection established") + { + return Err(error::bad_proxy(format!( + "Proxy server response error: {}", + res_s + ))); } self.conn.block_send_with_stream(url, &mut stream) @@ -123,7 +118,11 @@ impl<'a> BlockConnection<'a> { fn call_with_proxy_socks4(&self, url: &Url, proxy: &Proxy) -> error::Result> { let addr_proxy = format!("{}:{}", proxy.host(), proxy.port()); let addr_target = self.conn.addr(url)?; - let user = if let Some(u) = proxy.username() { u.to_string() } else { "".to_string() }; + let user = if let Some(u) = proxy.username() { + u.to_string() + } else { + "".to_string() + }; let mut stream = Socks4Stream::connect(&addr_proxy[..], &addr_target[..], &user[..]) .map_err(error::request)?; self.conn.block_send_with_stream(url, &mut stream) @@ -140,7 +139,8 @@ impl<'a> BlockConnection<'a> { } } else { Socks5Stream::connect(&addr_proxy[..], &addr_target[..]) - }.map_err(error::request)?; + } + .map_err(error::request)?; self.conn.block_send_with_stream(url, &mut stream) } } diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index c17edb4..10dda81 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -1,15 +1,16 @@ -use std::{io, time}; +use std::net::ToSocketAddrs; use std::sync::Arc; +use std::{io, time}; use url::Url; -use crate::{Config, error}; use crate::connection::connection_reader::ConnectionReader; use crate::request::{RawRequest, RequestBody}; use crate::types::{Proxy, RoUrl, ToUrl}; +use crate::{error, Config}; pub struct Connection<'a> { - request: RawRequest<'a> + request: RawRequest<'a>, } impl<'a> Connection<'a> { @@ -31,6 +32,9 @@ impl<'a> Connection<'a> { pub fn header(&self) -> &String { self.request.header() } + pub fn content_type(&self) -> Option { + self.request.content_type() + } pub fn body(&self) -> &Option { self.request.body() } @@ -57,11 +61,18 @@ impl<'a> Connection<'a> { } pub fn host(&self, url: &Url) -> error::Result { - Ok(url.host_str().ok_or(error::url_bad_host(url.clone()))?.to_string()) + Ok( + url + .host_str() + .ok_or(error::url_bad_host(url.clone()))? + .to_string(), + ) } pub fn port(&self, url: &Url) -> error::Result { - url.port_or_known_default().ok_or(error::url_bad_host(url.clone())) + url + .port_or_known_default() + .ok_or(error::url_bad_host(url.clone())) } pub fn proxy_header(&self, url: &Url, proxy: &Proxy) -> error::Result { @@ -93,15 +104,47 @@ impl<'a> Connection<'a> { impl<'a> Connection<'a> { pub fn block_tcp_stream(&self, addr: &String) -> error::Result { let config = self.config(); + + let server: Vec<_> = addr.to_socket_addrs().map_err(error::request)?.collect(); + println!("{:?}", server); let stream = std::net::TcpStream::connect(addr).map_err(error::request)?; - stream.set_read_timeout(Some(time::Duration::from_millis(config.read_timeout()))).map_err(error::request)?; - stream.set_write_timeout(Some(time::Duration::from_millis(config.write_timeout()))).map_err(error::request)?; + stream + .set_read_timeout(Some(time::Duration::from_millis(config.read_timeout()))) + .map_err(error::request)?; + stream + .set_write_timeout(Some(time::Duration::from_millis(config.write_timeout()))) + .map_err(error::request)?; + println!("Connected to the server!"); Ok(stream) } - pub fn block_write_stream(&self, stream: &mut S) -> error::Result<()> where S: io::Write, { + pub fn block_write_stream(&self, stream: &mut S) -> error::Result<()> + where + S: io::Write, + { let header = self.header(); let body = self.body(); + + println!("{}", header); + if let Some(body) = body { + println!("\n\n"); + let content_type = self + .content_type() + .map(|v| v.to_lowercase()) + .unwrap_or("".to_string()); + let mut raw_types = vec![ + "application/x-www-form-urlencoded", + "application/json", + "text/plain", + ]; + raw_types.retain(|item| content_type.contains(item)); + if raw_types.is_empty() { + } else { + let body_text = String::from_utf8(body.bytes().to_vec()).map_err(error::request)?; + println!("{}", body_text); + } + } + stream.write(header.as_bytes()).map_err(error::request)?; if let Some(body) = body { stream.write(body.bytes()).map_err(error::request)?; @@ -111,7 +154,10 @@ impl<'a> Connection<'a> { Ok(()) } - pub fn block_read_stream(&self, url: &Url, stream: &mut S) -> error::Result> where S: io::Read, { + pub fn block_read_stream(&self, url: &Url, stream: &mut S) -> error::Result> + where + S: io::Read, + { let mut reader = ConnectionReader::new(url, stream); reader.binary() } @@ -119,24 +165,24 @@ impl<'a> Connection<'a> { pub fn block_send(&self, url: &Url) -> error::Result> { let addr = self.addr(url)?; let mut stream = self.block_tcp_stream(&addr)?; -// self.call_tcp_stream_http(stream) + // self.call_tcp_stream_http(stream) self.block_send_with_stream(url, &mut stream) } pub fn block_send_with_stream(&self, url: &Url, stream: &mut S) -> error::Result> - where - S: io::Read + io::Write, + where + S: io::Read + io::Write, { match url.scheme() { "http" => self.block_send_http(url, stream), "https" => self.block_send_https(url, stream), - _ => return Err(error::url_bad_scheme(url.clone())) + _ => return Err(error::url_bad_scheme(url.clone())), } } pub fn block_send_http(&self, url: &Url, stream: &mut S) -> error::Result> - where - S: io::Read + io::Write, + where + S: io::Read + io::Write, { self.block_write_stream(stream)?; self.block_read_stream(url, stream) @@ -144,26 +190,31 @@ impl<'a> Connection<'a> { #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))] pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> - where - S: io::Read + io::Write, + where + S: io::Read + io::Write, { - return Err(error::no_request_features("Not have any tls features, Can't request a https url")); + return Err(error::no_request_features( + "Not have any tls features, Can't request a https url", + )); } #[cfg(feature = "tls-native")] pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> - where - S: io::Read + io::Write, + where + S: io::Read + io::Write, { - let connector = native_tls::TlsConnector::builder().build().map_err(error::request)?; + let connector = native_tls::TlsConnector::builder() + .build() + .map_err(error::request)?; let mut ssl_stream; -// if self.verify { - ssl_stream = connector.connect(&self.host(url)?[..], stream) + // if self.verify { + ssl_stream = connector + .connect(&self.host(url)?[..], stream) .map_err(|_| error::bad_ssl("Native tls error."))?; -// ssl_stream = connector.connect(&self.host(url)?[..], stream).map_err(error::request)?; -// } else { -// ssl_stream = connector.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(stream).map_err(error::request)?; -// } + // ssl_stream = connector.connect(&self.host(url)?[..], stream).map_err(error::request)?; + // } else { + // ssl_stream = connector.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(stream).map_err(error::request)?; + // } self.block_write_stream(&mut ssl_stream)?; self.block_read_stream(url, &mut ssl_stream) @@ -171,8 +222,8 @@ impl<'a> Connection<'a> { #[cfg(feature = "tls-rustls")] pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> - where - S: io::Read + io::Write, + where + S: io::Read + io::Write, { let mut config = rustls::ClientConfig::new(); config @@ -188,6 +239,3 @@ impl<'a> Connection<'a> { self.block_read_stream(url, &mut tls) } } - - - diff --git a/rttp_client/src/connection/connection_reader.rs b/rttp_client/src/connection/connection_reader.rs index 45f1118..b05b1bd 100644 --- a/rttp_client/src/connection/connection_reader.rs +++ b/rttp_client/src/connection/connection_reader.rs @@ -21,7 +21,10 @@ impl<'a> ConnectionReader<'a> { pub fn binary(&mut self) -> error::Result> { let mut binary: Vec = Vec::new(); - let _ = self.reader.read_to_end(&mut binary).map_err(error::request)?; + let _ = self + .reader + .read_to_end(&mut binary) + .map_err(error::request)?; Ok(binary) } diff --git a/rttp_client/src/error.rs b/rttp_client/src/error.rs index faae05e..9658958 100644 --- a/rttp_client/src/error.rs +++ b/rttp_client/src/error.rs @@ -25,8 +25,8 @@ struct Inner { impl Error { pub(crate) fn new(kind: Kind, source: Option) -> Error - where - E: Into, + where + E: Into, { Error { inner: Box::new(Inner { @@ -94,7 +94,7 @@ impl Error { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut builder = f.debug_struct("rhttp_client::Error"); + let mut builder = f.debug_struct("rttp_client::Error"); builder.field("kind", &self.inner.kind); @@ -200,7 +200,7 @@ pub(crate) fn request>(e: E) -> Error { Error::new(Kind::Request, Some(e)) } -pub(crate) fn response>(e: E) -> Error{ +pub(crate) fn response>(e: E) -> Error { Error::new(Kind::Response, Some(e)) } @@ -232,23 +232,23 @@ pub(crate) fn none_url() -> Error { Error::new(Kind::Builder, Some("None request url")) } -pub(crate) fn builder_with_message>(message: S) -> Error { +pub(crate) fn builder_with_message(message: impl AsRef) -> Error { Error::new(Kind::Builder, Some(message.as_ref())) } -pub(crate) fn bad_proxy>(message: S) -> Error { +pub(crate) fn bad_proxy(message: impl AsRef) -> Error { Error::new(Kind::Request, Some(message.as_ref())) } -pub(crate) fn bad_response>(message: S) -> Error { +pub(crate) fn bad_response(message: impl AsRef) -> Error { Error::new(Kind::Response, Some(message.as_ref())) } -pub(crate) fn bad_cookie>(message: S) -> Error { +pub(crate) fn bad_cookie(message: impl AsRef) -> Error { Error::new(Kind::Decode, Some(message.as_ref())) } -pub(crate) fn no_request_features>(message: S) -> Error { +pub(crate) fn no_request_features(message: impl AsRef) -> Error { Error::new(Kind::Request, Some(message.as_ref())) } @@ -256,16 +256,10 @@ pub(crate) fn connection_closed() -> Error { Error::new(Kind::Request, Some("The connection is closed.")) } -pub(crate) fn bad_ssl>(message: S) -> Error { +pub(crate) fn bad_ssl(message: impl AsRef) -> Error { Error::new(Kind::Request, Some(message.as_ref())) } -//if_wasm! { -// pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError { -// format!("{:?}", js_val).into() -// } -//} - // io::Error helpers #[allow(unused)] @@ -308,4 +302,3 @@ impl fmt::Display for BlockingClientInAsyncContext { } impl StdError for BlockingClientInAsyncContext {} - diff --git a/rttp_client/src/request/builder/build_body_async_std.rs b/rttp_client/src/request/builder/build_body_async_std.rs new file mode 100644 index 0000000..b29ea59 --- /dev/null +++ b/rttp_client/src/request/builder/build_body_async_std.rs @@ -0,0 +1,65 @@ +use crate::error; +use crate::request::builder::common::RawBuilder; +use crate::request::RequestBody; +use crate::types::{FormDataType, RoUrl}; + +#[cfg(feature = "async")] +impl<'a> RawBuilder<'a> { + pub async fn build_body_async_std( + &mut self, + rourl: &mut RoUrl, + ) -> error::Result> { + if let Some(body) = self.build_body_common(rourl)? { + return Ok(Some(body)); + } + + let formdatas = self.request().formdatas(); + + // form-data + if !formdatas.is_empty() { + return self.build_body_with_form_data_async_std().await; + } + + return Ok(None); + } + + async fn build_body_with_form_data_async_std(&mut self) -> error::Result> { + let fdw = self.build_body_with_form_data_sync_common()?; + let mut disposition = fdw.disposition; + let mut buffer = fdw.buffer; + + let traditional = self.request().traditional(); + let formdatas = self.request().formdatas(); + for formdata in formdatas { + let field_name = if formdata.array() && !traditional { + format!("{}[]", formdata.name()) + } else { + formdata.name().to_string() + }; + if formdata.type_() == &FormDataType::FILE { + let file = formdata + .file() + .clone() + .ok_or(error::builder_with_message(&format!( + "Missing file path for field: {}", + formdata.name() + )))?; + let guess = mime_guess::from_path(&file); + let file_name = formdata.filename().clone().unwrap_or_default(); + let item = disposition.create_with_filename_and_content_type( + &field_name, + &file_name, + guess.first_or_octet_stream(), + ); + buffer.extend_from_slice(item.as_bytes()); + let file_content = async_std::fs::read(&file).await.map_err(error::builder)?; + buffer.extend(file_content); + } + } + let end = disposition.end(); + buffer.extend_from_slice(end.as_bytes()); + + let body = RequestBody::with_vec(buffer); + Ok(Some(body)) + } +} diff --git a/rttp_client/src/request/builder/build_body_block.rs b/rttp_client/src/request/builder/build_body_block.rs new file mode 100644 index 0000000..9e3cd0e --- /dev/null +++ b/rttp_client/src/request/builder/build_body_block.rs @@ -0,0 +1,253 @@ +use std::str::FromStr; + +use mime::Mime; + +use crate::error; +use crate::request::builder::common::{RawBuilder, DISPOSITION_END}; +use crate::request::builder::form_data::{Disposition, FormDataWrap}; +use crate::request::RequestBody; +use crate::types::{FormDataType, RoUrl}; + +impl<'a> RawBuilder<'a> { + pub fn build_body_common(&mut self, rourl: &mut RoUrl) -> error::Result> { + let method = self.request.method(); + let raw = self.request.raw(); + let paras = self.request.paras(); + let binary = self.request.binary(); + let formdatas = self.request.formdatas(); + + let have_multi_body_type = raw.is_some() && !binary.is_empty() && !formdatas.is_empty(); + if have_multi_body_type { + return Err(error::builder_with_message( + "Bad request body, `raw`, `binary` and `form-data` only support choose one", + )); + } + + // if get request + let is_get = method.eq_ignore_ascii_case("get"); + if is_get { + for para in paras { + rourl.para(para); + } + } + self.content_type = Some(mime::APPLICATION_WWW_FORM_URLENCODED); + + // paras + if !paras.is_empty() && raw.is_none() && binary.is_empty() && formdatas.is_empty() && !is_get { + return self.build_body_with_form_urlencoded(); + } + + // raw + if raw.is_some() { + self.content_type = Some( + Mime::from_str( + &self + .request + .header("content-type") + .map_or(mime::TEXT_PLAIN.to_string(), |v| v)[..], + ) + .map_err(error::builder)?, + ); + + let body = raw.clone().map(|raw| RequestBody::with_text(raw)); + if !paras.is_empty() { + for para in paras { + rourl.para(para); + } + } + return Ok(body); + } + + // binary + if !binary.is_empty() { + self.content_type = Some( + Mime::from_str( + &self + .request + .header("content-type") + .map_or(mime::APPLICATION_OCTET_STREAM.to_string(), |v| v)[..], + ) + .map_err(error::builder)?, + ); + + let body = Some(RequestBody::with_vec(binary.clone())); + if !paras.is_empty() { + for para in paras { + rourl.para(para); + } + } + return Ok(body); + } + + // no body (Not include form data) + Ok(None) + } +} + +// build sync body +impl<'a> RawBuilder<'a> { + pub fn build_body_block(&mut self, rourl: &mut RoUrl) -> error::Result> { + if let Some(body) = self.build_body_common(rourl)? { + return Ok(Some(body)); + } + + let formdatas = self.request.formdatas(); + + // form-data + if !formdatas.is_empty() { + return self.build_body_with_form_data_block(); + } + + return Ok(None); + } + + fn build_body_with_form_data_block(&mut self) -> error::Result> { + let fdw = self.build_body_with_form_data_sync_common()?; + let mut disposition = fdw.disposition; + let mut buffer = fdw.buffer; + + let traditional = self.request.traditional(); + let formdatas = self.request.formdatas(); + for formdata in formdatas { + let field_name = if formdata.array() && !traditional { + format!("{}[]", formdata.name()) + } else { + formdata.name().to_string() + }; + if formdata.type_() == &FormDataType::FILE { + let file = formdata + .file() + .clone() + .ok_or(error::builder_with_message(&format!( + "Missing file path for field: {}", + formdata.name() + )))?; + let guess = mime_guess::from_path(&file); + let file_name = formdata.filename().clone().unwrap_or_default(); + let item = disposition.create_with_filename_and_content_type( + &field_name, + &file_name, + guess.first_or_octet_stream(), + ); + buffer.extend_from_slice(item.as_bytes()); + let file_content = std::fs::read(&file).map_err(error::builder)?; + buffer.extend(file_content); + } + } + let end = disposition.end(); + buffer.extend_from_slice(end.as_bytes()); + + let body = RequestBody::with_vec(buffer); + Ok(Some(body)) + } + + pub fn build_body_with_form_data_sync_common(&mut self) -> error::Result { + let traditional = self.request.traditional(); + let is_get = self.request.method().eq_ignore_ascii_case("get"); + + let disposition = Disposition::new(); + let mut buffer = vec![]; + + let content_type = disposition.content_type(); + self.content_type = Some(Mime::from_str(&content_type[..]).map_err(error::builder)?); + + if !is_get { + let paras = self.request.paras(); + for para in paras { + let field_name = if para.array() && !traditional { + format!("{}[]", para.name()) + } else { + para.name().to_string() + }; + let value = if let Some(v) = para.value() { + v.clone() + } else { + Default::default() + }; + let item = format!("{}{}", disposition.create_with_name(&field_name), value); + buffer.extend_from_slice(item.as_bytes()); + buffer.extend_from_slice(DISPOSITION_END.as_bytes()); + } + } + + let formdatas = self.request.formdatas(); + for formdata in formdatas { + let field_name = if formdata.array() && !traditional { + format!("{}[]", formdata.name()) + } else { + formdata.name().clone() + }; + match formdata.type_() { + FormDataType::TEXT => { + let value = if let Some(v) = formdata.text() { + v.to_string() + } else { + Default::default() + }; + let item = format!("{}{}", disposition.create_with_name(&field_name), value); + buffer.extend_from_slice(item.as_bytes()); + } + FormDataType::BINARY => { + let file_name = formdata.filename().clone().unwrap_or_default(); + let octe_stream = Mime::from_str(&mime::APPLICATION_OCTET_STREAM.to_string()[..]) + .map_err(error::builder)?; + let item = + disposition.create_with_filename_and_content_type(&field_name, &file_name, octe_stream); + buffer.extend_from_slice(item.as_bytes()); + buffer.extend(formdata.binary()); + } + FormDataType::FILE => continue, + } + buffer.extend_from_slice(DISPOSITION_END.as_bytes()); + } + + Ok(FormDataWrap { + disposition, + buffer, + }) + } +} + +// build body common +impl<'a> RawBuilder<'a> { + fn build_body_with_form_urlencoded(&mut self) -> error::Result> { + let encode = self.request.encode(); + let traditional = self.request.traditional(); + let paras = self.request.paras(); + let len = paras.len(); + let mut body = String::new(); + for (i, para) in paras.iter().enumerate() { + let name = if encode { + percent_encoding::percent_encode(para.name().as_bytes(), percent_encoding::NON_ALPHANUMERIC) + .to_string() + } else { + para.name().to_string() + }; + let value = if let Some(text) = para.value() { + if encode { + percent_encoding::percent_encode(text.as_bytes(), percent_encoding::NON_ALPHANUMERIC) + .to_string() + } else { + text.clone() + } + } else { + Default::default() + }; + body.push_str(&format!( + "{}{}={}", + name, + if para.array() && !traditional { + "[]" + } else { + "" + }, + value + )); + if i + 1 < len { + body.push_str("&"); + } + } + let req_body = RequestBody::with_text(body); + Ok(Some(req_body)) + } +} diff --git a/rttp_client/src/request/builder/build_header.rs b/rttp_client/src/request/builder/build_header.rs new file mode 100644 index 0000000..9ec2e37 --- /dev/null +++ b/rttp_client/src/request/builder/build_header.rs @@ -0,0 +1,171 @@ +use url::Url; + +use crate::error; +use crate::request::builder::common::{RawBuilder, DISPOSITION_END}; +use crate::request::RequestBody; +use crate::types::{Header, RoUrl, ToUrl}; + +impl<'a> RawBuilder<'a> { + pub fn build_header( + &mut self, + rourl: &RoUrl, + body: &Option, + ) -> error::Result { + let url = rourl.to_url()?; + + self.auto_add_host(&url)?; + self.auto_add_connection()?; + self.auto_add_ua()?; + self.auto_add_accept()?; + self.auto_add_content_type()?; + self.auto_add_content_length(body)?; + + let mut builder = String::new(); + + // let is_http = url.scheme() == "http"; + let request_url = self.request_url(&url, false); + builder.push_str(&format!( + "{} {} HTTP/1.1{}", + self.request.method().to_uppercase(), + request_url, + DISPOSITION_END + )); + + for header in self.request.headers() { + let name = header.name(); + let value = header.value().replace(DISPOSITION_END, ""); + + builder.push_str(&format!("{}: {}{}", name, value, DISPOSITION_END)); + } + + builder.push_str(DISPOSITION_END); + Ok(builder) + } +} + +impl<'a> RawBuilder<'a> { + fn request_url(&self, url: &Url, full: bool) -> String { + if full { + return url.as_str().to_owned(); + } + + let mut result = format!("{}", url.path()); + if let Some(query) = url.query() { + result.push_str(&format!("?{}", query)); + } + if let Some(fragment) = url.fragment() { + result.push_str(&format!("#{}", fragment)); + } + result + } + + fn found_header(&mut self, name: impl AsRef) -> bool { + self + .request + .headers() + .iter() + .find(|item| item.name().eq_ignore_ascii_case(name.as_ref())) + .is_some() + } +} + +impl<'a> RawBuilder<'a> { + fn auto_add_host(&mut self, url: &Url) -> error::Result<()> { + if self.found_header("host") { + return Ok(()); + } + let host = url.host_str().ok_or(error::url_bad_host(url.clone()))?; + let header = match url.port() { + Some(port) => Header::new("Host", format!("{}:{}", host, port)), + None => Header::new("Host", format!("{}", host)), + }; + + self.request.headers_mut().push(header); + Ok(()) + } + + fn auto_add_connection(&mut self) -> error::Result<()> { + if self.found_header("connection") { + return Ok(()); + } + self + .request + .headers_mut() + .push(Header::new("Connection", "Close")); + Ok(()) + } + + fn auto_add_ua(&mut self) -> error::Result<()> { + if self.found_header("user-agent") { + return Ok(()); + } + let ua = format!("Mozilla/5.0 rttp/{}", env!("CARGO_PKG_VERSION")); + self + .request + .headers_mut() + .push(Header::new("User-Agent", ua)); + Ok(()) + } + + fn auto_add_accept(&mut self) -> error::Result<()> { + if self.found_header("accept") { + return Ok(()); + } + self + .request + .headers_mut() + .push(Header::new("Accept", "*/*")); + Ok(()) + } + + fn auto_add_content_type(&mut self) -> error::Result<()> { + // not form-data request + if self.request.formdatas().is_empty() && !self.found_header("content-type") { + let header = match &self.content_type { + Some(ct) => Header::new("Content-Type", ct.to_string()), + None => Header::new( + "Content-Type", + mime::APPLICATION_WWW_FORM_URLENCODED.to_string(), + ), + }; + + self.request.headers_mut().push(header); + return Ok(()); + } + + // if it's form data request, replace header use generate header + let mut headers = self.request.headers().clone(); + let origin = headers + .iter() + .find(|item| item.name().eq_ignore_ascii_case("content-type")) + .cloned(); + + headers.retain(|item| !item.name().eq_ignore_ascii_case("content-type")); + + let header = match &self.content_type { + Some(ct) => Header::new("Content-Type", ct.to_string()), + None => origin + .unwrap_or_else(|| Header::new("Content-Type", mime::APPLICATION_OCTET_STREAM.to_string())), + }; + headers.push(header); + + self.request.headers_set(headers); + Ok(()) + } + + fn auto_add_content_length(&mut self, body: &Option) -> error::Result<()> { + if self.found_header("content-length") { + return Ok(()); + } + let len = if let Some(body) = body { body.len() } else { 0 }; + if len < 1 { + return Ok(()); + } + + let mut headers = self.request.headers().clone(); + headers.retain(|item| !item.name().eq_ignore_ascii_case("content-length")); + headers.push(Header::new("Content-Length", len.to_string())); + self.request.headers_set(headers); + Ok(()) + } +} diff --git a/rttp_client/src/request/builder/build_para_and_url.rs b/rttp_client/src/request/builder/build_para_and_url.rs new file mode 100644 index 0000000..6cb063c --- /dev/null +++ b/rttp_client/src/request/builder/build_para_and_url.rs @@ -0,0 +1,67 @@ +use crate::request::builder::common::RawBuilder; +use crate::types::RoUrl; + +// rebuild para/url +impl<'a> RawBuilder<'a> { + pub fn rebuild_paras(&mut self, rourl: &mut RoUrl) { + let traditional = self.request.traditional(); + rourl.traditional(traditional); + + let mut formdata_req = self.request.formdatas().clone(); + let mut paras_req = self.request.paras().clone(); + let mut paras_url = rourl.paras_get().clone(); + + let mut para_is_array: Vec<(String, bool)> = + Vec::with_capacity(paras_req.len() + paras_url.len()); + + formdata_req.iter().for_each(|p| { + if let Some(v) = para_is_array.iter_mut().find(|(key, _)| key == p.name()) { + v.1 = true; + } else { + para_is_array.push((p.name().clone(), false)); + } + }); + paras_req.iter().for_each(|p| { + if let Some(v) = para_is_array.iter_mut().find(|(key, _)| key == p.name()) { + v.1 = true; + } else { + para_is_array.push((p.name().clone(), false)); + } + }); + paras_url.iter().for_each(|p| { + if let Some(v) = para_is_array.iter_mut().find(|(key, _)| key == p.name()) { + v.1 = true; + } else { + para_is_array.push((p.name().clone(), false)); + } + }); + + formdata_req.iter_mut().for_each(|para| { + if let Some((_, is_array)) = para_is_array.iter().find(|(key, _)| key == para.name()) { + *para.array_mut() = *is_array; + } + }); + + paras_req.iter_mut().for_each(|para| { + if let Some((_, is_array)) = para_is_array.iter().find(|(key, _)| key == para.name()) { + *para.array_mut() = *is_array; + } + }); + + paras_url.iter_mut().for_each(|para| { + if let Some((_, is_array)) = para_is_array.iter().find(|(key, _)| key == para.name()) { + *para.array_mut() = *is_array; + } + }); + + self.request.formdatas_set(formdata_req); + self.request.paras_set(paras_req); + rourl.paras(paras_url); + } + + pub fn rebuild_url(&mut self, rourl: &mut RoUrl) { + self.request.paths().iter().for_each(|path| { + rourl.path(path); + }); + } +} diff --git a/rttp_client/src/request/builder/common.rs b/rttp_client/src/request/builder/common.rs new file mode 100644 index 0000000..268c697 --- /dev/null +++ b/rttp_client/src/request/builder/common.rs @@ -0,0 +1,81 @@ +use std::str::FromStr; + +use mime::Mime; + +use crate::error; +use crate::request::builder::form_data::{self, Disposition, FormDataWrap}; +use crate::request::{RawRequest, Request, RequestBody}; +use crate::types::{FormDataType, RoUrl}; + +pub static HYPHENS: &'static str = "---------------------------"; +pub static DISPOSITION_PREFIX: &'static str = "--"; +pub static DISPOSITION_END: &'static str = "\r\n"; + +#[derive(Debug)] +pub struct RawBuilder<'a> { + pub content_type: Option, + pub request: &'a mut Request, +} + +impl<'a> RawBuilder<'a> { + pub fn new(request: &'a mut Request) -> Self { + Self { + content_type: None, + request, + } + } +} + +// impl<'a> RawBuilder<'a> { +// pub fn request(&mut self) -> &mut Request { +// self.request +// } +// +// pub fn content_type(&self) -> &Option { +// &self.content_type +// } +// +// pub fn content_type_set(&mut self, content_type: Mime) -> &mut Self { +// self.content_type = Some(content_type); +// self +// } +// } + +impl<'a> RawBuilder<'a> { + pub fn raw_request_block(mut self) -> error::Result> { + let mut rourl = self.request.url().clone().ok_or(error::none_url())?; + if rourl.traditional_get().is_none() { + rourl.traditional(self.request.traditional()); + } + + self.rebuild_paras(&mut rourl); + self.rebuild_url(&mut rourl); + let body = self.build_body_block(&mut rourl)?; + let header = self.build_header(&rourl, &body)?; + Ok(RawRequest { + origin: self.request, + url: rourl, + header, + body, + }) + } + + #[cfg(feature = "async")] + pub async fn raw_request_async_std(mut self) -> error::Result> { + let mut rourl = self.request.url().clone().ok_or(error::none_url())?; + if rourl.traditional_get().is_none() { + rourl.traditional(self.request.traditional()); + } + + self.rebuild_paras(&mut rourl); + self.rebuild_url(&mut rourl); + let body = self.build_body_async(&mut rourl).await?; + let header = self.build_header(&rourl, &body)?; + Ok(RawRequest { + origin: self.request, + url: rourl, + header, + body, + }) + } +} diff --git a/rttp_client/src/request/builder/form_data.rs b/rttp_client/src/request/builder/form_data.rs new file mode 100644 index 0000000..0a17ad6 --- /dev/null +++ b/rttp_client/src/request/builder/form_data.rs @@ -0,0 +1,68 @@ +use crate::request::builder::common::{DISPOSITION_END, DISPOSITION_PREFIX, HYPHENS}; +use mime::Mime; +use rand::Rng; + +pub struct FormDataWrap { + pub disposition: Disposition, + pub buffer: Vec, +} + +pub struct Disposition { + boundary: String, +} + +impl Disposition { + pub fn new() -> Self { + let mut rng = rand::thread_rng(); + let boundary: String = std::iter::repeat(()) + .map(|()| rng.sample(rand::distributions::Alphanumeric)) + .take(20) + .collect(); + Self { boundary } + } + + pub fn boundary(&self) -> &String { + &self.boundary + } + + pub fn content_type(&self) -> String { + format!("multipart/form-data; boundary={}{}", HYPHENS, self.boundary) + } + + pub fn create_with_name(&self, name: &String) -> String { + format!( + "{}{}{}{}Content-Disposition: form-data; name=\"{}\"{}{}", + DISPOSITION_PREFIX, + HYPHENS, + self.boundary, + DISPOSITION_END, + name, + DISPOSITION_END, + DISPOSITION_END + ) + } + + pub fn create_with_filename_and_content_type( + &self, + name: &String, + filename: &String, + mime: Mime, + ) -> String { + let mut disposition = format!( + "{}{}{}{}Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"{}", + DISPOSITION_PREFIX, HYPHENS, self.boundary, DISPOSITION_END, name, filename, DISPOSITION_END + ); + + disposition.push_str(&format!( + "Content-Type: {}{}", + mime.to_string(), + DISPOSITION_END + )); + disposition.push_str(DISPOSITION_END); + disposition + } + + pub fn end(&self) -> String { + format!("{}--{}--{}", HYPHENS, self.boundary, DISPOSITION_END) + } +} diff --git a/rttp_client/src/request/builder/mod.rs b/rttp_client/src/request/builder/mod.rs new file mode 100644 index 0000000..6cc6d44 --- /dev/null +++ b/rttp_client/src/request/builder/mod.rs @@ -0,0 +1,8 @@ +pub use self::common::*; + +mod build_body_async_std; +mod build_body_block; +mod build_header; +mod build_para_and_url; +mod common; +mod form_data; diff --git a/rttp_client/src/request/mod.rs b/rttp_client/src/request/mod.rs index 779e498..f26430f 100644 --- a/rttp_client/src/request/mod.rs +++ b/rttp_client/src/request/mod.rs @@ -1,6 +1,6 @@ pub use self::raw_request::RawRequest; pub use self::request::*; -mod request; +mod builder; mod raw_request; -mod raw_builder; +mod request; diff --git a/rttp_client/src/request/raw_builder.rs b/rttp_client/src/request/raw_builder.rs deleted file mode 100644 index ada6fc0..0000000 --- a/rttp_client/src/request/raw_builder.rs +++ /dev/null @@ -1,600 +0,0 @@ -use std::str::FromStr; - -use mime::Mime; -use rand::Rng; -use url::Url; - -use crate::error; -use crate::request::{RawRequest, Request, RequestBody}; -use crate::types::{FormDataType, Header, RoUrl, ToUrl}; - -pub const HYPHENS: &'static str = "---------------------------"; -pub const DISPOSITION_PREFIX: &'static str = "--"; -pub const DISPOSITION_END: &'static str = "\r\n"; - - -#[derive(Debug)] -pub struct RawBuilder<'a> { - content_type: Option, - request: &'a mut Request, -} - - -// create -impl<'a> RawBuilder<'a> { - pub fn new(request: &'a mut Request) -> Self { - Self { - content_type: None, - request, - } - } - - pub fn block_raw_request(mut self) -> error::Result> { - let mut rourl = self.request.url() - .clone() - .ok_or(error::none_url())?; - - self.rebuild_paras(&mut rourl); - self.rebuild_url(&mut rourl); - let body = self.build_body_block(&mut rourl)?; - let header = self.build_header(&rourl, &body)?; - Ok(RawRequest { - origin: self.request, - url: rourl, - header, - body, - }) - } - - #[cfg(feature = "async")] - pub async fn async_raw_request(mut self) -> error::Result> { - let mut rourl = self.request.url() - .clone() - .ok_or(error::none_url())?; - - self.rebuild_paras(&mut rourl); - self.rebuild_url(&mut rourl); - let body = self.build_body_async(&mut rourl).await?; - let header = self.build_header(&rourl, &body)?; - Ok(RawRequest { - origin: self.request, - url: rourl, - header, - body, - }) - } -} - - -// build header -impl<'a> RawBuilder<'a> { - fn request_url(&self, url: &Url, full: bool) -> String { - if full { - return url.as_str().to_owned(); - } - - let mut result = format!("{}", url.path()); - if let Some(query) = url.query() { - result.push_str(&format!("?{}", query)); - } - if let Some(fragment) = url.fragment() { - result.push_str(&format!("#{}", fragment)); - } - result - } - - fn build_header(&mut self, rourl: &RoUrl, body: &Option) -> error::Result { - let url = rourl.to_url()?; - - let mut builder = String::new(); - let is_http = url.scheme() == "http"; - let request_url = self.request_url(&url, is_http); - - builder.push_str(&format!("{} {} HTTP/1.1{}", self.request.method().to_uppercase(), request_url, DISPOSITION_END)); - - let mut found_host = false; - let mut found_connection = false; - let mut found_ua = false; - let mut found_content_type = false; - let mut found_content_length = false; - - for header in self.request.headers() { - let name = header.name(); - let value = header.value().replace(DISPOSITION_END, ""); - - if name.eq_ignore_ascii_case("host") { found_host = true; } - if name.eq_ignore_ascii_case("connection") { found_connection = true; } - if name.eq_ignore_ascii_case("user-agent") { found_ua = true; } - - if name.eq_ignore_ascii_case("content-type") { - found_content_type = true; - if !self.request.formdatas().is_empty() { - continue; - } - } - if name.eq_ignore_ascii_case("content-length") { - found_content_length = true; - continue; - } - - builder.push_str(&format!("{}: {}{}", name, value, DISPOSITION_END)); - } - - // auto add host header - if !found_host { - let host = url.host_str().ok_or(error::url_bad_host(url.clone()))?; - let port: u16 = url.port().map_or_else(|| { - match url.scheme() { - "https" => Ok(443), - "http" => Ok(80), - _ => Err(error::url_bad_scheme(url.clone())) - } - }, |v| Ok(v))?; - builder.push_str(&format!("Host: {}:{}{}", host, port, DISPOSITION_END)); - self.request.headers_mut().push(Header::new("Host", format!("{}:{}", host, port))); - } - - // auto add connection header - if !found_connection { - let conn = format!("Connection: Close{}", DISPOSITION_END); - builder.push_str(&conn); - self.request.headers_mut().push(Header::new("Connection", "Close")); - } - - // auto add user agent header - if !found_ua { - let ua = format!("Mozilla/5.0 rttp/{}", env!("CARGO_PKG_VERSION")); - builder.push_str(&format!("User-Agent: {}{}", ua, DISPOSITION_END)); - self.request.headers_mut().push(Header::new("User-Agent", ua)); - } - - // auto add content type header - // if it's form data request, replace header use this class generate header - if self.request.formdatas().is_empty() { - if !found_content_type { - match &self.content_type { - Some(ct) => { - let ctstr = ct.to_string(); - builder.push_str(&format!("Content-Type: {}{}", ctstr, DISPOSITION_END)); - self.request.headers_mut().push(Header::new("Content-Type", ctstr)); - } - None => { - builder.push_str(&format!("Content-Type: {}{}", mime::APPLICATION_OCTET_STREAM.to_string(), DISPOSITION_END)); - self.request.headers_mut().push(Header::new("Content-Type", mime::APPLICATION_OCTET_STREAM.to_string())); - } - } - } - } else { - let mut headers = self.request.headers().iter() - .filter(|h| !h.name().eq_ignore_ascii_case("content-type")) - .map(|v| v.clone()) - .collect::>(); - if let Some(ct) = &self.content_type { - let cts = ct.to_string(); - builder.push_str(&format!("Content-Type: {}{}", cts, DISPOSITION_END)); - headers.push(Header::new("Content-Type", cts)); - self.request.headers_set(headers); - } - } - - // auto add content length header - let len = if let Some(body) = body { - body.len() - } else { - 0 - }; - builder.push_str(&format!("Content-Length: {}{}", len, DISPOSITION_END)); - if found_content_length { - let mut headers = self.request.headers().iter() - .filter(|h| h.name().eq_ignore_ascii_case("content-length")) - .map(|v| v.clone()) - .collect::>(); - headers.push(Header::new("Content-Length", len.to_string())); - self.request.headers_set(headers); - } - - builder.push_str(DISPOSITION_END); - Ok(builder) - } -} - -// rebuild para/url -impl<'a> RawBuilder<'a> { - fn rebuild_paras(&mut self, rourl: &mut RoUrl) { - let traditional = self.request.traditional(); - rourl.traditional(traditional); - - - let mut formdata_req = self.request.formdatas().clone(); - let mut paras_req = self.request.paras().clone(); - let mut paras_url = rourl.paras_get().clone(); - - let mut names: Vec<(String, bool)> = Vec::with_capacity(paras_req.len() + paras_url.len()); - - formdata_req.iter().for_each(|p| { - if let Some(v) = names.iter_mut().find(|(key, _)| key == p.name()) { - v.1 = true; - } else { - names.push((p.name().clone(), false)); - } - }); - paras_req.iter().for_each(|p| { - if let Some(v) = names.iter_mut().find(|(key, _)| key == p.name()) { - v.1 = true; - } else { - names.push((p.name().clone(), false)); - } - }); - paras_url.iter().for_each(|p| { - if let Some(v) = names.iter_mut().find(|(key, _)| key == p.name()) { - v.1 = true; - } else { - names.push((p.name().clone(), false)); - } - }); - - formdata_req.iter_mut().for_each(|para| { - if let Some((_, is_array)) = names.iter().find(|(key, _)| key == para.name()) { - *para.array_mut() = *is_array; - } - }); - - paras_req.iter_mut().for_each(|para| { - if let Some((_, is_array)) = names.iter().find(|(key, _)| key == para.name()) { - *para.array_mut() = *is_array; - } - }); - - paras_url.iter_mut().for_each(|para| { - if let Some((_, is_array)) = names.iter().find(|(key, _)| key == para.name()) { - *para.array_mut() = *is_array; - } - }); - - self.request.formdatas_set(formdata_req); - self.request.paras_set(paras_req); - rourl.paras_set(paras_url); - } - - fn rebuild_url(&mut self, rourl: &mut RoUrl) { - self.request.paths().iter().for_each(|path| { - rourl.path(path); - }); - } -} - -// build body common -impl<'a> RawBuilder<'a> { - fn build_body_with_form_urlencoded(&mut self, rourl: &mut RoUrl) -> error::Result> { - let encode = self.request.encode(); - let traditional = self.request.traditional(); - let paras = self.request.paras(); - let len = paras.len(); - let mut body = String::new(); - for (i, para) in paras.iter().enumerate() { - let name = if encode { - percent_encoding::percent_encode(para.name().as_bytes(), percent_encoding::NON_ALPHANUMERIC).to_string() - } else { - para.name().to_string() - }; - let value = if let Some(text) = para.value() { - if encode { - percent_encoding::percent_encode(text.as_bytes(), percent_encoding::NON_ALPHANUMERIC).to_string() - } else { - text.clone() - } - } else { - Default::default() - }; - body.push_str(&format!("{}{}={}", name, - if para.array() && !traditional { "[]" } else { "" }, - value)); - if i + 1 < len { - body.push_str("&"); - } - } - let req_body = RequestBody::with_text(body); - Ok(Some(req_body)) - } -} - -// build sync body -impl<'a> RawBuilder<'a> { - fn build_body_block(&mut self, rourl: &mut RoUrl) -> error::Result> { - let method = self.request.method(); - let raw = self.request.raw(); - let paras = self.request.paras(); - let binary = self.request.binary(); - let formdatas = self.request.formdatas(); - - let has_multi_body_type = vec![raw.is_some(), !binary.is_empty(), !formdatas.is_empty()] - .iter() - .filter(|v| **v) - .collect::>() - .len() > 1; - if has_multi_body_type { - return Err(error::builder_with_message("Bad request body, raw binary and form-data only support choose one")); - } - - self.content_type = Some(mime::APPLICATION_WWW_FORM_URLENCODED); - - // if get request - let is_get = method.eq_ignore_ascii_case("get"); - if is_get { - for para in paras { rourl.para(para); } - } - - // paras - if !paras.is_empty() && raw.is_none() && binary.is_empty() && formdatas.is_empty() && !is_get { - return self.build_body_with_form_urlencoded(rourl); - } - - // form-data - if !formdatas.is_empty() { - return self.build_body_with_form_data_block(rourl); - } - - // raw - if raw.is_some() { - self.content_type = Some(Mime::from_str(&self.request.header("content-type").map_or(mime::TEXT_PLAIN.to_string(), |v| v)[..]) - .map_err(error::builder)?); - - let body = raw.clone().map(|raw| RequestBody::with_text(raw)); - if !is_get && !paras.is_empty() { - for para in paras { rourl.para(para); } - } - return Ok(body); - } - - // binary - if !binary.is_empty() { - self.content_type = Some(Mime::from_str(&self.request.header("content-type").map_or(mime::APPLICATION_OCTET_STREAM.to_string(), |v| v)[..]) - .map_err(error::builder)?); - - let body = Some(RequestBody::with_vec(binary.clone())); - if !is_get && !paras.is_empty() { - for para in paras { rourl.para(para); } - } - return Ok(body); - } - - // no body - Ok(None) - } - - fn build_body_with_form_data_block(&mut self, rourl: &mut RoUrl) -> error::Result> { - let fdw = self.build_body_with_form_data_sync_common(rourl)?; - let mut disposition = fdw.disposition; - let mut buffer = fdw.buffer; - - let traditional = self.request.traditional(); - let formdatas = self.request.formdatas(); - for formdata in formdatas { - let field_name = if formdata.array() && !traditional { format!("{}[]", formdata.name()) } else { formdata.name().to_string() }; - if formdata.type_() == &FormDataType::FILE { - let file = formdata.file().clone().ok_or(error::builder_with_message(&format!("{} not have file", formdata.name())))?; - let guess = mime_guess::from_path(&file); - let filename = if let Some(fname) = formdata.filename() { fname.to_string() } else { "".to_string() }; - let item = disposition.create_with_filename_and_content_type(&field_name, &filename, guess.first_or_octet_stream()); - buffer.extend_from_slice(item.as_bytes()); - let file_content = std::fs::read(&file).map_err(error::builder)?; - buffer.extend(file_content); - } - } - let end = disposition.end(); - buffer.extend_from_slice(end.as_bytes()); - - let body = RequestBody::with_vec(buffer); - Ok(Some(body)) - } - - fn build_body_with_form_data_sync_common(&mut self, rourl: &mut RoUrl) -> error::Result { - let traditional = self.request.traditional(); - let paras = self.request.paras(); - let formdatas = self.request.formdatas(); - let is_get = self.request.method().eq_ignore_ascii_case("get"); - - let disposition = Disposition::new(); - let mut buffer = vec![]; - - let content_type = disposition.content_type(); - self.content_type = Some(Mime::from_str(&content_type[..]).map_err(error::builder)?); - - if !is_get { - for para in paras { - let field_name = if para.array() && !traditional { format!("{}[]", para.name()) } else { para.name().to_string() }; - let value = if let Some(v) = para.value() { v.to_string() } else { "".to_string() }; - let item = format!("{}{}", disposition.create_with_name(&field_name), value); - buffer.extend_from_slice(item.as_bytes()); - buffer.extend_from_slice(DISPOSITION_END.as_bytes()); - } - } - for formdata in formdatas { - let field_name = if formdata.array() && !traditional { format!("{}[]", formdata.name()) } else { formdata.name().to_string() }; - match formdata.type_() { - FormDataType::TEXT => { - let value = if let Some(v) = formdata.text() { v.to_string() } else { "".to_string() }; - let item = format!("{}{}", disposition.create_with_name(&field_name), value); - buffer.extend_from_slice(item.as_bytes()); - } - FormDataType::BINARY => { - let filename = if let Some(fname) = formdata.filename() { fname.to_string() } else { "".to_string() }; - let octe_stream = Mime::from_str(&mime::APPLICATION_OCTET_STREAM.to_string()[..]).map_err(error::builder)?; - let item = disposition.create_with_filename_and_content_type(&field_name, &filename, octe_stream); - buffer.extend_from_slice(item.as_bytes()); - buffer.extend(formdata.binary()); - } - FormDataType::FILE => continue - } - buffer.extend_from_slice(DISPOSITION_END.as_bytes()); - } - -// println!("{}", String::from_utf8_lossy(buffer.clone().as_slice())); -// let body = RequestBody::with_vec(buffer); - Ok(FormDataWrap { disposition, buffer }) - } -} - -// build async body -impl<'a> RawBuilder<'a> { - - #[cfg(feature = "async")] - async fn build_body_async(&mut self, rourl: &mut RoUrl) -> error::Result> { - let method = self.request.method(); - let raw = self.request.raw(); - let paras = self.request.paras(); - let binary = self.request.binary(); - let formdatas = self.request.formdatas(); - - let has_multi_body_type = vec![raw.is_some(), !binary.is_empty(), !formdatas.is_empty()] - .iter() - .filter(|v| **v) - .collect::>() - .len() > 1; - if has_multi_body_type { - return Err(error::builder_with_message("Bad request body, raw binary and form-data only support choose one")); - } - - self.content_type = Some(mime::APPLICATION_WWW_FORM_URLENCODED); - - // if get request - let is_get = method.eq_ignore_ascii_case("get"); - if is_get { - for para in paras { rourl.para(para); } - } - - // paras - if !paras.is_empty() && raw.is_none() && binary.is_empty() && formdatas.is_empty() && !is_get { - return self.build_body_with_form_urlencoded(rourl); - } - - // form-data - if !formdatas.is_empty() { - return self.build_body_with_form_data_async(rourl).await.map_err(error::builder); - } - - // raw - if raw.is_some() { - self.content_type = Some(Mime::from_str(&self.request.header("content-type").map_or(mime::TEXT_PLAIN.to_string(), |v| v)[..]) - .map_err(error::builder)?); - - let body = raw.clone().map(|raw| RequestBody::with_text(raw)); - if !is_get && !paras.is_empty() { - for para in paras { rourl.para(para); } - } - return Ok(body); - } - - // binary - if !binary.is_empty() { - self.content_type = Some(Mime::from_str(&self.request.header("content-type").map_or(mime::APPLICATION_OCTET_STREAM.to_string(), |v| v)[..]) - .map_err(error::builder)?); - - let body = Some(RequestBody::with_vec(binary.clone())); - if !is_get && !paras.is_empty() { - for para in paras { rourl.para(para); } - } - return Ok(body); - } - - // no body - Ok(None) - } - - #[cfg(feature = "async")] - async fn build_body_with_form_data_async(&mut self, rourl: &mut RoUrl) -> error::Result> { - let fdw = self.build_body_with_form_data_sync_common(rourl)?; - let mut disposition = fdw.disposition; - let mut buffer = fdw.buffer; - - let traditional = self.request.traditional(); - let formdatas = self.request.formdatas(); - for formdata in formdatas { - let field_name = if formdata.array() && !traditional { format!("{}[]", formdata.name()) } else { formdata.name().to_string() }; - if formdata.type_() == &FormDataType::FILE { - let file = formdata.file().clone().ok_or(error::builder_with_message(&format!("{} not have file", formdata.name())))?; - let guess = mime_guess::from_path(&file); - let filename = if let Some(fname) = formdata.filename() { fname.to_string() } else { "".to_string() }; - let item = disposition.create_with_filename_and_content_type(&field_name, &filename, guess.first_or_octet_stream()); - buffer.extend_from_slice(item.as_bytes()); - let file_content = async_std::fs::read(&file).await.map_err(error::builder)?; - buffer.extend(file_content); - } - } - let end = disposition.end(); - buffer.extend_from_slice(end.as_bytes()); - - let body = RequestBody::with_vec(buffer); - Ok(Some(body)) - } -} - - -struct FormDataWrap { - disposition: Disposition, - buffer: Vec, -} - -struct Disposition { - boundary: String -} - -impl Disposition { - pub fn new() -> Self { - let mut rng = rand::thread_rng(); - let boundary: String = std::iter::repeat(()) - .map(|()| rng.sample(rand::distributions::Alphanumeric)) - .take(20) - .collect(); - Self { boundary } - } - - pub fn boundary(&self) -> &String { &self.boundary } - - pub fn content_type(&self) -> String { - format!("multipart/form-data; boundary={}{}", HYPHENS, self.boundary) - } - - pub fn create_with_name(&self, name: &String) -> String { - format!( - "{}{}{}{}Content-Disposition: form-data; name=\"{}\"{}{}", - DISPOSITION_PREFIX, - HYPHENS, - self.boundary, - DISPOSITION_END, - name, - DISPOSITION_END, - DISPOSITION_END - ) - } - - pub fn create_with_filename_and_content_type(&self, name: &String, filename: &String, mime: Mime) -> String { - let mut disposition = format!( - "{}{}{}{}Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"{}", - DISPOSITION_PREFIX, - HYPHENS, - self.boundary, - DISPOSITION_END, - name, - filename, - DISPOSITION_END - ); - - disposition.push_str(&format!( - "Content-Type: {}{}", - mime.to_string(), - DISPOSITION_END - )); - disposition.push_str(DISPOSITION_END); - disposition - } - - pub fn end(&self) -> String { - format!( - "{}--{}--{}", - HYPHENS, - self.boundary, - DISPOSITION_END - ) - } -} diff --git a/rttp_client/src/request/raw_request.rs b/rttp_client/src/request/raw_request.rs index f58bf9b..6894a73 100644 --- a/rttp_client/src/request/raw_request.rs +++ b/rttp_client/src/request/raw_request.rs @@ -1,6 +1,6 @@ use crate::error; +use crate::request::builder::RawBuilder; use crate::request::{Request, RequestBody}; -use crate::request::raw_builder::RawBuilder; use crate::types::RoUrl; #[derive(Debug)] @@ -13,20 +13,35 @@ pub struct RawRequest<'a> { impl<'a> RawRequest<'a> { pub fn block_new(request: &'a mut Request) -> error::Result> { - RawBuilder::new(request).block_raw_request() + RawBuilder::new(request).raw_request_block() } #[cfg(feature = "async")] pub async fn async_new(request: &'a mut Request) -> error::Result> { - RawBuilder::new(request).async_raw_request().await + RawBuilder::new(request).raw_request_async_std().await } - pub fn origin(&self) -> &Request { &self.origin } - pub fn url(&self) -> &RoUrl { &self.url } - pub fn header(&self) -> &String { &self.header } - pub fn body(&self) -> &Option { &self.body } + pub fn origin(&self) -> &Request { + &self.origin + } - pub(crate) fn origin_mut(&mut self) -> &mut Request { &mut self.origin } -} + pub fn url(&self) -> &RoUrl { + &self.url + } + + pub fn header(&self) -> &String { + &self.header + } + pub fn body(&self) -> &Option { + &self.body + } + + pub fn content_type(&self) -> Option { + self.origin.header("content-type") + } + pub(crate) fn origin_mut(&mut self) -> &mut Request { + &mut self.origin + } +} diff --git a/rttp_client/src/request/request.rs b/rttp_client/src/request/request.rs index 7fc9542..812b1a4 100644 --- a/rttp_client/src/request/request.rs +++ b/rttp_client/src/request/request.rs @@ -1,7 +1,7 @@ use std::fmt; -use crate::{error, Config}; use crate::types::{FormData, Header, Para, Proxy, RoUrl, ToRoUrl}; +use crate::{error, Config}; #[derive(Clone, Debug)] pub struct Request { @@ -41,36 +41,91 @@ impl Request { } } - pub fn closed(&self) -> bool { self.closed } - pub fn config(&self) -> &Config { &self.config } - pub fn count(&self) -> u32 { self.count } - pub fn url(&self) -> &Option { &self.url } - pub fn method(&self) -> &String { &self.method } - pub fn paths(&self) -> &Vec { &self.paths } - pub fn paras(&self) -> &Vec { &self.paras } - pub fn formdatas(&self) -> &Vec { &self.formdatas } - pub fn headers(&self) -> &Vec
{ &self.headers } - pub fn traditional(&self) -> bool { self.traditional } - pub fn encode(&self) -> bool { self.encode } - pub fn raw(&self) -> &Option { &self.raw } - pub fn binary(&self) -> &Vec { &self.binary } - pub fn proxy(&self) -> &Option { &self.proxy } - - pub(crate) fn closed_mut(&mut self) -> &mut bool { &mut self.closed } - pub(crate) fn config_mut(&mut self) -> &mut Config { &mut self.config } - pub(crate) fn count_mut(&mut self) -> &mut u32 { &mut self.count } - pub(crate) fn url_mut(&mut self) -> &mut Option { &mut self.url } - pub(crate) fn method_mut(&mut self) -> &mut String { &mut self.method } - pub(crate) fn paths_mut(&mut self) -> &mut Vec { &mut self.paths } - pub(crate) fn paras_mut(&mut self) -> &mut Vec { &mut self.paras } - pub(crate) fn formdatas_mut(&mut self) -> &mut Vec { &mut self.formdatas } - pub(crate) fn headers_mut(&mut self) -> &mut Vec
{ &mut self.headers } - pub(crate) fn traditional_mut(&mut self) -> &mut bool { &mut self.traditional } - pub(crate) fn encode_mut(&mut self) -> &mut bool { &mut self.encode } - pub(crate) fn raw_mut(&mut self) -> &mut Option { &mut self.raw } - pub(crate) fn binary_mut(&mut self) -> &mut Vec { &mut self.binary } - pub(crate) fn proxy_mut(&mut self) -> &mut Option { &mut self.proxy } + pub fn closed(&self) -> bool { + self.closed + } + pub fn config(&self) -> &Config { + &self.config + } + pub fn count(&self) -> u32 { + self.count + } + pub fn url(&self) -> &Option { + &self.url + } + pub fn method(&self) -> &String { + &self.method + } + pub fn paths(&self) -> &Vec { + &self.paths + } + pub fn paras(&self) -> &Vec { + &self.paras + } + pub fn formdatas(&self) -> &Vec { + &self.formdatas + } + pub fn headers(&self) -> &Vec
{ + &self.headers + } + pub fn traditional(&self) -> bool { + self.traditional + } + pub fn encode(&self) -> bool { + self.encode + } + pub fn raw(&self) -> &Option { + &self.raw + } + pub fn binary(&self) -> &Vec { + &self.binary + } + pub fn proxy(&self) -> &Option { + &self.proxy + } + pub(crate) fn closed_mut(&mut self) -> &mut bool { + &mut self.closed + } + pub(crate) fn config_mut(&mut self) -> &mut Config { + &mut self.config + } + pub(crate) fn count_mut(&mut self) -> &mut u32 { + &mut self.count + } + pub(crate) fn url_mut(&mut self) -> &mut Option { + &mut self.url + } + pub(crate) fn method_mut(&mut self) -> &mut String { + &mut self.method + } + pub(crate) fn paths_mut(&mut self) -> &mut Vec { + &mut self.paths + } + pub(crate) fn paras_mut(&mut self) -> &mut Vec { + &mut self.paras + } + pub(crate) fn formdatas_mut(&mut self) -> &mut Vec { + &mut self.formdatas + } + pub(crate) fn headers_mut(&mut self) -> &mut Vec
{ + &mut self.headers + } + pub(crate) fn traditional_mut(&mut self) -> &mut bool { + &mut self.traditional + } + pub(crate) fn encode_mut(&mut self) -> &mut bool { + &mut self.encode + } + pub(crate) fn raw_mut(&mut self) -> &mut Option { + &mut self.raw + } + pub(crate) fn binary_mut(&mut self) -> &mut Vec { + &mut self.binary + } + pub(crate) fn proxy_mut(&mut self) -> &mut Option { + &mut self.proxy + } pub(crate) fn closed_set(&mut self, closed: bool) -> &mut Self { self.closed = closed; @@ -130,16 +185,17 @@ impl Request { } pub fn header>(&self, name: S) -> Option { - self.headers.iter() + self + .headers + .iter() .find(|h| h.name().eq_ignore_ascii_case(name.as_ref())) .map(|h| h.value().clone()) } } - #[derive(Clone)] pub struct RequestBody { - binary: Vec + binary: Vec, } impl RequestBody { @@ -183,4 +239,3 @@ impl fmt::Debug for RequestBody { fmt::Debug::fmt(&text, formatter) } } - diff --git a/rttp_client/src/types/url.rs b/rttp_client/src/types/url.rs index 514d910..19342c5 100644 --- a/rttp_client/src/types/url.rs +++ b/rttp_client/src/types/url.rs @@ -1,5 +1,6 @@ //use std::{cmp, fmt, hash}; use std::fmt::Debug; +use std::str::FromStr; use url::Url; @@ -28,10 +29,9 @@ pub struct RoUrl { password: Option, paras: Vec, fragment: Option, - traditional: bool, + traditional: Option, } - pub trait ToRoUrl: Debug { fn to_rourl(&self) -> RoUrl; } @@ -42,7 +42,6 @@ pub trait ToUrl: Debug { fn to_url(&self) -> error::Result; } - impl RoUrl { /// Create a rourl /// # Examples @@ -50,14 +49,18 @@ impl RoUrl { /// use rttp_client::types::RoUrl; /// RoUrl::with("http://httpbin.org/get"); /// ``` - pub fn with>(url: S) -> RoUrl { + pub fn with(url: impl AsRef) -> RoUrl { let url = url.as_ref(); let netloc_and_para: Vec<&str> = url.split("?").collect::>(); - let url = netloc_and_para.get(0).map_or("".to_string(), |v| v.to_string()); + let url = netloc_and_para + .get(0) + .map_or("".to_string(), |v| v.to_string()); let mut para_string = String::new(); let mut fragment = None; for (i, nap) in netloc_and_para.iter().enumerate() { - if i == 0 { continue; } + if i == 0 { + continue; + } let para_and_fragment: Vec<&str> = nap.split("#").collect::>(); if para_and_fragment.is_empty() { para_string.push_str(nap); @@ -66,7 +69,8 @@ impl RoUrl { para_string.push_str(last_para); } - let fragment_string = para_and_fragment.iter() + let fragment_string = para_and_fragment + .iter() .enumerate() .filter(|(ix, _)| *ix > 0) .map(|(_, v)| *v) @@ -87,54 +91,53 @@ impl RoUrl { username: Default::default(), password: None, paras, - traditional: true, + traditional: None, fragment, } } - pub(crate) fn url_get(&self) -> &String { &self.url } - pub(crate) fn paths_get(&self) -> &Vec { &self.paths } - pub(crate) fn username_get(&self) -> &String { &self.username } - pub(crate) fn password_get(&self) -> &Option { &self.password } - pub(crate) fn paras_get(&self) -> &Vec { &self.paras } - pub(crate) fn fragment_get(&self) -> &Option { &self.fragment } - pub(crate) fn traditional_get(&self) -> bool { self.traditional } - - pub(crate) fn url_mut(&mut self) -> &mut String { &mut self.url } - pub(crate) fn paths_mut(&mut self) -> &mut Vec { &mut self.paths } - pub(crate) fn username_mut(&mut self) -> &mut String { &mut self.username } - pub(crate) fn password_mut(&mut self) -> &mut Option { &mut self.password } - pub(crate) fn paras_mut(&mut self) -> &mut Vec { &mut self.paras } - pub(crate) fn fragment_mut(&mut self) -> &mut Option { &mut self.fragment } - pub(crate) fn traditional_mut(&mut self) -> &mut bool { &mut self.traditional } + pub(crate) fn url_get(&self) -> &String { + &self.url + } + pub(crate) fn paths_get(&self) -> &Vec { + &self.paths + } + pub(crate) fn username_get(&self) -> &String { + &self.username + } + pub(crate) fn password_get(&self) -> &Option { + &self.password + } + pub(crate) fn paras_get(&self) -> &Vec { + &self.paras + } + pub(crate) fn fragment_get(&self) -> &Option { + &self.fragment + } + pub(crate) fn traditional_get(&self) -> Option { + self.traditional.clone() + } - pub(crate) fn url_set>(&mut self, url: S) -> &mut Self { - self.url = url.as_ref().into(); - self + pub(crate) fn url_mut(&mut self) -> &mut String { + &mut self.url } - pub(crate) fn paths_set(&mut self, paths: Vec) -> &mut Self { - self.paths = paths; - self + pub(crate) fn paths_mut(&mut self) -> &mut Vec { + &mut self.paths } - pub(crate) fn username_set>(&mut self, username: S) -> &mut Self { - self.username = username.as_ref().into(); - self + pub(crate) fn username_mut(&mut self) -> &mut String { + &mut self.username } - pub(crate) fn password_set>(&mut self, password: S) -> &mut Self { - self.password = Some(password.as_ref().into()); - self + pub(crate) fn password_mut(&mut self) -> &mut Option { + &mut self.password } - pub(crate) fn paras_set(&mut self, paras: Vec) -> &mut Self { - self.paras = paras; - self + pub(crate) fn paras_mut(&mut self) -> &mut Vec { + &mut self.paras } - pub(crate) fn fragment_set>(&mut self, fragment: S) -> &mut Self { - self.fragment = Some(fragment.as_ref().into()); - self + pub(crate) fn fragment_mut(&mut self) -> &mut Option { + &mut self.fragment } - pub(crate) fn traditional_set(&mut self, traditional: bool) -> &mut Self { - self.traditional = traditional; - self + pub(crate) fn traditional_mut(&mut self) -> &mut Option { + &mut self.traditional } /// Set fragment to url @@ -179,7 +182,7 @@ impl RoUrl { /// Set is traditional pub fn traditional(&mut self, traditional: bool) -> &mut Self { - self.traditional = traditional; + self.traditional = Some(traditional); self } } @@ -197,8 +200,7 @@ impl RoUrl { fn join_paras(&self, url: &Url) -> Option { let mut all_paras: Vec = vec![]; - let url_paras = url.query() - .map(|v| v.into_paras()); + let url_paras = url.query().map(|v| v.into_paras()); if let Some(uparas) = url_paras { all_paras.extend(uparas); } @@ -206,21 +208,39 @@ impl RoUrl { if all_paras.is_empty() { return None; } - let para_string = all_paras.iter() + let para_string = all_paras + .iter() .filter(|&p| p.is_url() || p.is_form()) .map(|p| { let name = p.name(); - if self.traditional { - return format!("{}={}", name, p.value().clone().map_or("".to_string(), |t| t)); + let traditional = match self.traditional { + Some(v) => v, + None => true, + }; + if traditional { + return format!( + "{}={}", + name, + p.value().clone().map_or("".to_string(), |t| t) + ); } - let is_array = all_paras.iter() + let is_array = all_paras + .iter() .filter(|&item| item.name() == name) .collect::>() - .len() > 1; + .len() + > 1; let ends_with_bracket = name.ends_with("[]"); - return format!("{}{}={}", name, - if !ends_with_bracket && (is_array || p.array()) { "[]" } else { "" }, - p.value().clone().map_or("".to_string(), |t| t)); + return format!( + "{}{}={}", + name, + if !ends_with_bracket && (is_array || p.array()) { + "[]" + } else { + "" + }, + p.value().clone().map_or("".to_string(), |t| t) + ); }) .collect::>() .join("&"); @@ -228,7 +248,6 @@ impl RoUrl { } } - impl ToRoUrl for RoUrl { fn to_rourl(&self) -> RoUrl { Self { @@ -243,7 +262,6 @@ impl ToRoUrl for RoUrl { } } - impl ToRoUrl for &str { fn to_rourl(&self) -> RoUrl { RoUrl::with(self) @@ -256,7 +274,6 @@ impl ToRoUrl for String { } } - impl ToUrl for RoUrl { fn to_url(&self) -> Result { let mut url = Url::parse(&self.url[..]).map_err(error::builder)?; @@ -265,10 +282,14 @@ impl ToUrl for RoUrl { url.set_path(&path[..]); if !self.username.is_empty() { - url.set_username(&self.username[..]).map_err(|_| error::bad_url(url.clone(), "Bad url username"))?; + url + .set_username(&self.username[..]) + .map_err(|_| error::bad_url(url.clone(), "Bad url username"))?; } if let Some(password) = &self.password { - url.set_password(Some(&password[..])).map_err(|_| error::bad_url(url.clone(), "Bad url password"))?; + url + .set_password(Some(&password[..])) + .map_err(|_| error::bad_url(url.clone(), "Bad url password"))?; } if let Some(paras) = &self.join_paras(&url) { url.set_query(Some(¶s[..])); @@ -280,7 +301,6 @@ impl ToUrl for RoUrl { } } - impl From for RoUrl { fn from(url: Url) -> Self { Self::with(url.as_str()) @@ -293,65 +313,6 @@ impl AsRef for RoUrl { } } -///// Display the serialization of this URL. -//impl fmt::Display for RoUrl { -// #[inline] -// fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { -// fmt::Display::fmt(self.to_url().expect("Can't convert RoUrl to Url"), formatter) -// } -//} -// -///// Debug the serialization of this URL. -//impl fmt::Debug for RoUrl { -// #[inline] -// fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { -// fmt::Debug::fmt(self.to_url().expect("Can't convert RoUrl to Url"), formatter) -// } -//} -// -// -///// URLs compare like their serialization. -//impl Eq for RoUrl {} -// -///// URLs compare like their serialization. -//impl PartialEq for RoUrl { -// #[inline] -// fn eq(&self, other: &Self) -> bool { -// self.to_url().expect("Can't convert RoUrl to Url") -// == other.to_url().expect("Can't convert RoUrl to Url") -// } -//} -// -///// URLs compare like their serialization. -//impl Ord for RoUrl { -// #[inline] -// fn cmp(&self, other: &Self) -> cmp::Ordering { -// self.to_url().expect("Can't convert RoUrl to Url") -// .cmp(&other.to_url().expect("Can't convert RoUrl to Url")) -// } -//} -// -///// URLs compare like their serialization. -//impl PartialOrd for RoUrl { -// #[inline] -// fn partial_cmp(&self, other: &Self) -> Option { -// self.to_url().expect("Can't convert RoUrl to Url") -// .partial_cmp(&other.to_url().expect("Can't convert RoUrl to Url")) -// } -//} -// -///// URLs hash like their serialization. -//impl hash::Hash for RoUrl { -// #[inline] -// fn hash(&self, state: &mut H) -// where -// H: hash::Hasher, -// { -// hash::Hash::hash(&self.to_url().expect("Can't convert RoUrl to Url"), state) -// } -//} - - impl<'a, IU: ToRoUrl> ToRoUrl for &'a IU { fn to_rourl(&self) -> RoUrl { (*self).to_rourl() @@ -364,3 +325,10 @@ impl<'a, IU: ToRoUrl> ToRoUrl for &'a mut IU { } } +impl FromStr for RoUrl { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(RoUrl::with(s)) + } +} diff --git a/rttp_client/tests/test_http_basic.rs b/rttp_client/tests/test_http_basic.rs index f818dd3..dc86be9 100644 --- a/rttp_client/tests/test_http_basic.rs +++ b/rttp_client/tests/test_http_basic.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use rttp_client::{Config, HttpClient}; use rttp_client::types::{Para, Proxy, RoUrl}; +use rttp_client::{Config, HttpClient}; fn client() -> HttpClient { HttpClient::new() @@ -9,12 +9,12 @@ fn client() -> HttpClient { #[test] fn test_http() { - let response = client() - .url("http://httpbin.org/get") - .emit(); + // let response = client().url("http://httpbin.org/get").emit(); + let response = client().url("http://debian:1234/get").emit(); + println!("{:?}", response); assert!(response.is_ok()); let response = response.unwrap(); - assert_eq!("httpbin.org", response.host()); + assert_eq!("debian", response.host()); println!("{}", response); } @@ -69,7 +69,6 @@ fn test_upload() { println!("{}", response); } - #[test] fn test_raw_json() { client() @@ -113,7 +112,11 @@ fn test_https() { fn test_http_with_url() { client() .method("get") - .url(RoUrl::with("https://httpbin.org").path("/get").para(("name", "Chico"))) + .url( + RoUrl::with("https://httpbin.org") + .path("/get") + .para(("name", "Chico")), + ) .emit() .expect("REQUEST FAIL"); } @@ -135,12 +138,12 @@ fn test_with_proxy_http() { fn test_with_proxy_socks5() { let response = client() .get() - .url("http://google.com") - .proxy(Proxy::socks5("127.0.0.1", 1080)) + .url("http://httpbin.org/get") + .proxy(Proxy::socks5("127.0.0.1", 2801)) .emit(); assert!(response.is_ok()); let response = response.unwrap(); - assert_eq!("google.com", response.host()); + assert_eq!("httpbin.org", response.host()); println!("{}", response); } From e2f2ac29c196f530a81cbb55a2e91de831370711 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 16:43:28 +0800 Subject: [PATCH 03/19] ci --- .github/workflows/ci.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6625079 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + container: + image: rust:1 + steps: + - uses: actions/checkout@v2 + + - name: Specify toolchain + run: | + rustup default stable + rustup component add clippy + + - name: Cache target + uses: actions/cache@v2 + with: + path: target + key: ${{ runner.os }}-target-${{ hashFiles('Cargo.lock') }} + restore-keys: ${{ runner.os }}-target- + + - name: Lint + run: cargo clippy --all --locked -- -D warnings + + - name: Run tests + run: cargo test From 601871b785cfa01ab6305ed8907d2cdca2150d2a Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 16:43:46 +0800 Subject: [PATCH 04/19] ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6625079..c386c65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: uses: actions/cache@v2 with: path: target - key: ${{ runner.os }}-target-${{ hashFiles('Cargo.lock') }} + key: ${{ runner.os }}-target-${{ hashFiles('Cargo.toml') }} restore-keys: ${{ runner.os }}-target- - name: Lint From 40e1d752ae3698551cd6a343c05058ce59aee894 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 16:43:56 +0800 Subject: [PATCH 05/19] ci --- .github/workflows/ci.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c386c65..d2697ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,13 +20,6 @@ jobs: rustup default stable rustup component add clippy - - name: Cache target - uses: actions/cache@v2 - with: - path: target - key: ${{ runner.os }}-target-${{ hashFiles('Cargo.toml') }} - restore-keys: ${{ runner.os }}-target- - - name: Lint run: cargo clippy --all --locked -- -D warnings From d70ae3d811aa5c43b36ebf04d0a05cc348a24e36 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 16:45:32 +0800 Subject: [PATCH 06/19] ci --- .github/workflows/ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2697ff..4bf58cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,15 +10,14 @@ jobs: build: name: Build and Test runs-on: ubuntu-latest - container: - image: rust:1 steps: - uses: actions/checkout@v2 - - name: Specify toolchain - run: | - rustup default stable - rustup component add clippy + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true - name: Lint run: cargo clippy --all --locked -- -D warnings From a626952a9bd37c3755bff9cdb569361aa89928c2 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 16:45:55 +0800 Subject: [PATCH 07/19] ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4bf58cf..1d62250 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: default: true - name: Lint - run: cargo clippy --all --locked -- -D warnings + run: cargo clippy --all -- -D warnings - name: Run tests run: cargo test From 9c5b96fc9953b867939be63c5a17b61a49360cc0 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 17:16:36 +0800 Subject: [PATCH 08/19] danger config --- rttp_client/src/client.rs | 2 +- rttp_client/src/config.rs | 57 +++++++++++++------ ..._connection.rs => async_std_connection.rs} | 0 .../src/connection/block_connection.rs | 11 +--- rttp_client/src/connection/connection.rs | 57 +++++++++---------- rttp_client/src/connection/mod.rs | 10 ++-- 6 files changed, 73 insertions(+), 64 deletions(-) rename rttp_client/src/connection/{async_connection.rs => async_std_connection.rs} (100%) diff --git a/rttp_client/src/client.rs b/rttp_client/src/client.rs index cb0d8fb..6b9e42b 100644 --- a/rttp_client/src/client.rs +++ b/rttp_client/src/client.rs @@ -201,7 +201,7 @@ impl HttpClient { return Err(error::connection_closed()); } let request = RawRequest::block_new(&mut self.request)?; - BlockConnection::new(request).block_call() + BlockConnection::new(request).call() } /// Async request emit diff --git a/rttp_client/src/config.rs b/rttp_client/src/config.rs index ae952d2..3d233ec 100644 --- a/rttp_client/src/config.rs +++ b/rttp_client/src/config.rs @@ -1,19 +1,13 @@ -//use std::{collections::HashMap, sync::Mutex}; -// -//use once_cell::sync::Lazy; -// -//static DEFAULT_CONFIG: Lazy> = Lazy::new(|| { -// let mut config = ; -// Mutex::new(config) -//}); - - #[derive(Clone, Debug)] pub struct Config { read_timeout: u64, write_timeout: u64, auto_redirect: bool, max_redirect: u32, + #[cfg(feature = "tls-native")] + verify_ssl_hostname: bool, + #[cfg(feature = "tls-native")] + verify_ssl_cert: bool, } impl Default for Config { @@ -34,16 +28,31 @@ impl Config { } impl Config { - pub fn read_timeout(&self) -> u64 { self.read_timeout } - pub fn write_timeout(&self) -> u64 { self.write_timeout } - pub fn auto_redirect(&self) -> bool { self.auto_redirect } - pub fn max_redirect(&self) -> u32 { self.max_redirect } + pub fn read_timeout(&self) -> u64 { + self.read_timeout + } + pub fn write_timeout(&self) -> u64 { + self.write_timeout + } + pub fn auto_redirect(&self) -> bool { + self.auto_redirect + } + pub fn max_redirect(&self) -> u32 { + self.max_redirect + } + #[cfg(feature = "tls-native")] + pub fn verify_ssl_cert(&self) -> bool { + self.verify_ssl_cert + } + #[cfg(feature = "tls-native")] + pub fn verify_ssl_hostname(&self) -> bool { + self.verify_ssl_hostname + } } - #[derive(Clone, Debug)] pub struct ConfigBuilder { - config: Config + config: Config, } impl ConfigBuilder { @@ -54,7 +63,11 @@ impl ConfigBuilder { write_timeout: 5000, auto_redirect: false, max_redirect: 3, - } + #[cfg(feature = "tls-native")] + verify_ssl_hostname: true, + #[cfg(feature = "tls-native")] + verify_ssl_cert: true, + }, } } @@ -78,6 +91,16 @@ impl ConfigBuilder { self.config.max_redirect = max_redirect; self } + #[cfg(feature = "tls-native")] + pub fn verify_ssl_hostname(&mut self, verify_ssl_hostname: bool) -> &mut Self { + self.config.verify_ssl_hostname = verify_ssl_hostname; + self + } + #[cfg(feature = "tls-native")] + pub fn verify_ssl_cert(&mut self, verify_ssl_cert: bool) -> &mut Self { + self.config.verify_ssl_cert = verify_ssl_cert; + self + } } impl AsRef for Config { diff --git a/rttp_client/src/connection/async_connection.rs b/rttp_client/src/connection/async_std_connection.rs similarity index 100% rename from rttp_client/src/connection/async_connection.rs rename to rttp_client/src/connection/async_std_connection.rs diff --git a/rttp_client/src/connection/block_connection.rs b/rttp_client/src/connection/block_connection.rs index 3b3bc71..247e25f 100644 --- a/rttp_client/src/connection/block_connection.rs +++ b/rttp_client/src/connection/block_connection.rs @@ -27,18 +27,9 @@ impl<'a> BlockConnection<'a> { } } - pub fn block_call(mut self) -> error::Result { + pub fn call(mut self) -> error::Result { let url = self.conn.url().map_err(error::builder)?; - - // let header = self.request.header(); - // let body = self.request.body(); - // println!("{}", header); - // if let Some(b) = body { - // println!("{}", b.string()?); - // } - let proxy = self.conn.proxy(); - let binary = if let Some(proxy) = proxy { self.call_with_proxy(&url, proxy)? } else { diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index 10dda81..52a1381 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -105,8 +105,8 @@ impl<'a> Connection<'a> { pub fn block_tcp_stream(&self, addr: &String) -> error::Result { let config = self.config(); - let server: Vec<_> = addr.to_socket_addrs().map_err(error::request)?.collect(); - println!("{:?}", server); + // let server: Vec<_> = addr.to_socket_addrs().map_err(error::request)?.collect(); + // println!("{:?}", server); let stream = std::net::TcpStream::connect(addr).map_err(error::request)?; stream .set_read_timeout(Some(time::Duration::from_millis(config.read_timeout()))) @@ -114,7 +114,6 @@ impl<'a> Connection<'a> { stream .set_write_timeout(Some(time::Duration::from_millis(config.write_timeout()))) .map_err(error::request)?; - println!("Connected to the server!"); Ok(stream) } @@ -125,25 +124,25 @@ impl<'a> Connection<'a> { let header = self.header(); let body = self.body(); - println!("{}", header); - if let Some(body) = body { - println!("\n\n"); - let content_type = self - .content_type() - .map(|v| v.to_lowercase()) - .unwrap_or("".to_string()); - let mut raw_types = vec![ - "application/x-www-form-urlencoded", - "application/json", - "text/plain", - ]; - raw_types.retain(|item| content_type.contains(item)); - if raw_types.is_empty() { - } else { - let body_text = String::from_utf8(body.bytes().to_vec()).map_err(error::request)?; - println!("{}", body_text); - } - } + // println!("{}", header); + // if let Some(body) = body { + // println!("\n\n"); + // let content_type = self + // .content_type() + // .map(|v| v.to_lowercase()) + // .unwrap_or("".to_string()); + // let mut raw_types = vec![ + // "application/x-www-form-urlencoded", + // "application/json", + // "text/plain", + // ]; + // raw_types.retain(|item| content_type.contains(item)); + // if raw_types.is_empty() { + // } else { + // let body_text = String::from_utf8(body.bytes().to_vec()).map_err(error::request)?; + // println!("{}", body_text); + // } + // } stream.write(header.as_bytes()).map_err(error::request)?; if let Some(body) = body { @@ -165,7 +164,6 @@ impl<'a> Connection<'a> { pub fn block_send(&self, url: &Url) -> error::Result> { let addr = self.addr(url)?; let mut stream = self.block_tcp_stream(&addr)?; - // self.call_tcp_stream_http(stream) self.block_send_with_stream(url, &mut stream) } @@ -203,18 +201,15 @@ impl<'a> Connection<'a> { where S: io::Read + io::Write, { + let config = self.config(); let connector = native_tls::TlsConnector::builder() + .danger_accept_invalid_certs(!config.verify_ssl_cert()) + .danger_accept_invalid_hostnames(!config.verify_ssl_host_name()) .build() .map_err(error::request)?; - let mut ssl_stream; - // if self.verify { - ssl_stream = connector + let mut ssl_stream = connector .connect(&self.host(url)?[..], stream) - .map_err(|_| error::bad_ssl("Native tls error."))?; - // ssl_stream = connector.connect(&self.host(url)?[..], stream).map_err(error::request)?; - // } else { - // ssl_stream = connector.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(stream).map_err(error::request)?; - // } + .map_err(|e| error::bad_ssl(format!("Native tls error: {:?}", e)))?; self.block_write_stream(&mut ssl_stream)?; self.block_read_stream(url, &mut ssl_stream) diff --git a/rttp_client/src/connection/mod.rs b/rttp_client/src/connection/mod.rs index ebdbfeb..185c901 100644 --- a/rttp_client/src/connection/mod.rs +++ b/rttp_client/src/connection/mod.rs @@ -1,11 +1,11 @@ #[cfg(feature = "async")] -pub use self::async_connection::*; +pub use self::async_std_connection::*; pub use self::block_connection::*; -mod block_connection; -mod connection_reader; #[cfg(feature = "async")] -mod async_connection; -mod connection; +mod async_std_connection; #[cfg(feature = "async")] mod async_std_io_block; +mod block_connection; +mod connection; +mod connection_reader; From 20b6a5216bf037e0492133c5be8813f9a947493c Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 17:35:21 +0800 Subject: [PATCH 09/19] clippy --- rttp_client/src/client.rs | 15 +-- .../src/connection/block_connection.rs | 3 - rttp_client/src/connection/connection.rs | 5 +- .../src/connection/connection_reader.rs | 2 + rttp_client/src/error.rs | 3 + rttp_client/src/lib.rs | 9 +- .../request/builder/build_body_async_std.rs | 4 + .../src/request/builder/build_body_block.rs | 2 +- rttp_client/src/request/builder/common.rs | 6 +- rttp_client/src/request/builder/form_data.rs | 1 + rttp_client/src/request/request.rs | 1 + rttp_client/src/response/raw_response.rs | 105 ++++++++++------ rttp_client/src/types/form_data.rs | 119 +++++++++++++----- rttp_client/src/types/para.rs | 85 +++++++------ rttp_client/src/types/status.rs | 109 ++++++---------- rttp_client/src/types/url.rs | 25 +--- rttp_client/tests/test_http_basic.rs | 4 +- 17 files changed, 263 insertions(+), 235 deletions(-) diff --git a/rttp_client/src/client.rs b/rttp_client/src/client.rs index 6b9e42b..eebbba2 100644 --- a/rttp_client/src/client.rs +++ b/rttp_client/src/client.rs @@ -104,8 +104,7 @@ impl HttpClient { /// Add url path pub fn path>(&mut self, path: S) -> &mut Self { - let mut paths = self.request.paths_mut(); - paths.push(path.as_ref().into()); + self.request.paths_mut().push(path.as_ref().into()); self } @@ -128,13 +127,13 @@ impl HttpClient { /// Add request header pub fn header(&mut self, header: P) -> &mut Self { - let mut headers = self.request.headers_mut(); + let headers = self.request.headers_mut(); for h in header.into_headers() { - let mut exi = headers + let exit = headers .iter_mut() .find(|d| d.name().eq_ignore_ascii_case(h.name())); - if let Some(eh) = exi { + if let Some(eh) = exit { if h.name().eq_ignore_ascii_case("cookie") { let new_cookie_value = format!("{};{}", eh.value(), h.value()); eh.replace(Header::new("Cookie", new_cookie_value)); @@ -162,16 +161,14 @@ impl HttpClient { /// Add request para pub fn para(&mut self, para: P) -> &mut Self { let paras = para.into_paras(); - let mut req_paras = self.request.paras_mut(); - req_paras.extend(paras); + self.request.paras_mut().extend(paras); self } /// Add request form data. include file pub fn form(&mut self, formdata: S) -> &mut Self { let formdatas = formdata.to_formdatas(); - let mut req_formdatas = self.request.formdatas_mut(); - req_formdatas.extend(formdatas); + self.request.formdatas_mut().extend(formdatas); self } diff --git a/rttp_client/src/connection/block_connection.rs b/rttp_client/src/connection/block_connection.rs index 247e25f..6441916 100644 --- a/rttp_client/src/connection/block_connection.rs +++ b/rttp_client/src/connection/block_connection.rs @@ -1,7 +1,4 @@ use std::io::{Read, Write}; -use std::net::TcpStream; -use std::sync::Arc; -use std::{io, time}; #[cfg(feature = "tls-native")] use native_tls::TlsConnector; diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index 52a1381..1f67dad 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -1,5 +1,3 @@ -use std::net::ToSocketAddrs; -use std::sync::Arc; use std::{io, time}; use url::Url; @@ -19,6 +17,7 @@ impl<'a> Connection<'a> { } } +#[allow(dead_code)] impl<'a> Connection<'a> { pub fn request(&self) -> &RawRequest { &self.request @@ -187,7 +186,7 @@ impl<'a> Connection<'a> { } #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))] - pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> + pub fn block_send_https(&self, _url: &Url, _stream: &mut S) -> error::Result> where S: io::Read + io::Write, { diff --git a/rttp_client/src/connection/connection_reader.rs b/rttp_client/src/connection/connection_reader.rs index b05b1bd..5df6507 100644 --- a/rttp_client/src/connection/connection_reader.rs +++ b/rttp_client/src/connection/connection_reader.rs @@ -6,6 +6,7 @@ use crate::error; use crate::response::Response; use crate::types::RoUrl; +#[allow(dead_code)] pub struct ConnectionReader<'a> { url: &'a Url, reader: Box<&'a mut dyn io::Read>, @@ -28,6 +29,7 @@ impl<'a> ConnectionReader<'a> { Ok(binary) } + #[allow(dead_code)] pub fn response(&mut self) -> error::Result { Response::new(RoUrl::from(self.url.clone()), self.binary()?) } diff --git a/rttp_client/src/error.rs b/rttp_client/src/error.rs index 9658958..d3ccf9a 100644 --- a/rttp_client/src/error.rs +++ b/rttp_client/src/error.rs @@ -171,6 +171,7 @@ impl From for js_sys::Error { } } +#[allow(dead_code)] #[derive(Debug)] pub(crate) enum Kind { Builder, @@ -212,6 +213,7 @@ pub(crate) fn too_many_redirects(url: Url) -> Error { Error::new(Kind::Redirect, Some("too many redirects")).with_url(url) } +#[allow(dead_code)] pub(crate) fn status_code(url: Url, status: StatusCode) -> Error { Error::new(Kind::Status(status), None::).with_url(url) } @@ -256,6 +258,7 @@ pub(crate) fn connection_closed() -> Error { Error::new(Kind::Request, Some("The connection is closed.")) } +#[allow(dead_code)] pub(crate) fn bad_ssl(message: impl AsRef) -> Error { Error::new(Kind::Request, Some(message.as_ref())) } diff --git a/rttp_client/src/lib.rs b/rttp_client/src/lib.rs index 504bba6..63b689f 100644 --- a/rttp_client/src/lib.rs +++ b/rttp_client/src/lib.rs @@ -101,7 +101,7 @@ //! .url("http://httpbin.org/post") //! .para("name=value&name=value") //! .para(("name", "value", "name=value&name=value")) -//! .para(Para::new("name", "value")) +//! .para(Para::with_form("name", "value")) //! .para(multi_para) //! .emit(); //! ``` @@ -216,11 +216,10 @@ pub use self::client::*; pub use self::config::*; mod client; -mod request; -mod connection; mod config; +mod connection; +mod request; -pub mod types; pub mod error; pub mod response; - +pub mod types; diff --git a/rttp_client/src/request/builder/build_body_async_std.rs b/rttp_client/src/request/builder/build_body_async_std.rs index b29ea59..30efa4f 100644 --- a/rttp_client/src/request/builder/build_body_async_std.rs +++ b/rttp_client/src/request/builder/build_body_async_std.rs @@ -1,6 +1,10 @@ +#[cfg(feature = "async")] use crate::error; +#[cfg(feature = "async")] use crate::request::builder::common::RawBuilder; +#[cfg(feature = "async")] use crate::request::RequestBody; +#[cfg(feature = "async")] use crate::types::{FormDataType, RoUrl}; #[cfg(feature = "async")] diff --git a/rttp_client/src/request/builder/build_body_block.rs b/rttp_client/src/request/builder/build_body_block.rs index 9e3cd0e..d1358a7 100644 --- a/rttp_client/src/request/builder/build_body_block.rs +++ b/rttp_client/src/request/builder/build_body_block.rs @@ -103,7 +103,7 @@ impl<'a> RawBuilder<'a> { fn build_body_with_form_data_block(&mut self) -> error::Result> { let fdw = self.build_body_with_form_data_sync_common()?; - let mut disposition = fdw.disposition; + let disposition = fdw.disposition; let mut buffer = fdw.buffer; let traditional = self.request.traditional(); diff --git a/rttp_client/src/request/builder/common.rs b/rttp_client/src/request/builder/common.rs index 268c697..8f705d4 100644 --- a/rttp_client/src/request/builder/common.rs +++ b/rttp_client/src/request/builder/common.rs @@ -1,11 +1,7 @@ -use std::str::FromStr; - use mime::Mime; use crate::error; -use crate::request::builder::form_data::{self, Disposition, FormDataWrap}; -use crate::request::{RawRequest, Request, RequestBody}; -use crate::types::{FormDataType, RoUrl}; +use crate::request::{RawRequest, Request}; pub static HYPHENS: &'static str = "---------------------------"; pub static DISPOSITION_PREFIX: &'static str = "--"; diff --git a/rttp_client/src/request/builder/form_data.rs b/rttp_client/src/request/builder/form_data.rs index 0a17ad6..cbba2a5 100644 --- a/rttp_client/src/request/builder/form_data.rs +++ b/rttp_client/src/request/builder/form_data.rs @@ -21,6 +21,7 @@ impl Disposition { Self { boundary } } + #[allow(dead_code)] pub fn boundary(&self) -> &String { &self.boundary } diff --git a/rttp_client/src/request/request.rs b/rttp_client/src/request/request.rs index 812b1a4..d07a416 100644 --- a/rttp_client/src/request/request.rs +++ b/rttp_client/src/request/request.rs @@ -21,6 +21,7 @@ pub struct Request { proxy: Option, } +#[allow(dead_code)] impl Request { pub fn new() -> Self { Self { diff --git a/rttp_client/src/response/raw_response.rs b/rttp_client/src/response/raw_response.rs index 5c1822f..a7cee33 100644 --- a/rttp_client/src/response/raw_response.rs +++ b/rttp_client/src/response/raw_response.rs @@ -3,7 +3,7 @@ use std::io::Read; use crate::error; use crate::response::ResponseBody; -use crate::types::{Header, IntoHeader, Cookie, RoUrl, ToUrl}; +use crate::types::{Cookie, Header, IntoHeader, RoUrl, ToUrl}; use url::Url; const CR: u8 = b'\r'; @@ -41,6 +41,7 @@ impl RawResponse { Ok(response) } + #[allow(dead_code)] pub fn binary(&mut self, binary: Vec) -> &mut Self { self.binary = binary; self @@ -70,19 +71,37 @@ impl RawResponse { self } - - pub(crate) fn url_get(&self) -> &Url { &self._url } - pub fn binary_get(&self) -> &[u8] { self.binary.as_slice() } - pub fn code_get(&self) -> u32 { self.code } - pub fn version_get(&self) -> &String { &self.version } - pub fn reason_get(&self) -> &String { &self.reason } - pub fn headers_get(&self) -> &Vec
{ &self.headers } - pub fn body_get(&self) -> &ResponseBody { &self.body } - pub fn cookies_get(&self) -> &Vec { &self.cookies } + pub(crate) fn url_get(&self) -> &Url { + &self._url + } + pub fn binary_get(&self) -> &[u8] { + self.binary.as_slice() + } + pub fn code_get(&self) -> u32 { + self.code + } + pub fn version_get(&self) -> &String { + &self.version + } + pub fn reason_get(&self) -> &String { + &self.reason + } + pub fn headers_get(&self) -> &Vec
{ + &self.headers + } + pub fn body_get(&self) -> &ResponseBody { + &self.body + } + pub fn cookies_get(&self) -> &Vec { + &self.cookies + } pub fn string(&self) -> error::Result { let mut text = String::new(); - text.push_str(&format!("{} {} {}\r\n", self.version, self.code, self.reason)); + text.push_str(&format!( + "{} {} {}\r\n", + self.version, self.code, self.reason + )); self.headers.iter().for_each(|h| { text.push_str(&format!("{}: {}\r\n", h.name(), h.value())); }); @@ -92,7 +111,6 @@ impl RawResponse { } } - impl fmt::Debug for RawResponse { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -113,7 +131,6 @@ impl fmt::Display for RawResponse { } } - struct Parser { binary: Vec, } @@ -125,22 +142,23 @@ impl Parser { pub fn parse(self, response: &mut RawResponse) -> error::Result<()> { if self.binary.is_empty() { - return Ok(()) + return Ok(()); } // find \r\n\r\n position let mut position: usize = 0; for i in 0..self.binary.len() - 1 { - if self.binary.get(i) == Some(&CR) && - self.binary.get(i + 1) == Some(&LF) && - self.binary.get(i + 2) == Some(&CR) && - self.binary.get(i + 3) == Some(&LF) { + if self.binary.get(i) == Some(&CR) + && self.binary.get(i + 1) == Some(&LF) + && self.binary.get(i + 2) == Some(&CR) + && self.binary.get(i + 3) == Some(&LF) + { position = i + 3; break; } -// if self.binary[i] == CR && self.binary[i + 1] == LF && self.binary[i + 2] == CR && self.binary[i + 3] == LF { -// position = i + 3; -// break; -// } + // if self.binary[i] == CR && self.binary[i + 1] == LF && self.binary[i + 2] == CR && self.binary[i + 3] == LF { + // position = i + 3; + // break; + // } } if position == 0 { return Err(error::bad_response("No http response")); @@ -158,37 +176,46 @@ impl Parser { } fn parse_header(&self, response: &mut RawResponse, text: String) -> error::Result<()> { - let parts: Vec<&str> = text.split(CRLF).collect(); - let status_line = parts.get(0).ok_or(error::bad_response("Response not have status line"))?; + let status_line = parts + .get(0) + .ok_or(error::bad_response("Response not have status line"))?; let status_parts: Vec<&str> = status_line.splitn(3, " ").collect(); - let http_version = status_parts.get(0).ok_or(error::bad_response("Response status not have http version"))?; - let status_code: u32 = match status_parts.get(1).ok_or(error::bad_response("Response status not have code"))?.parse() { + let http_version = status_parts + .get(0) + .ok_or(error::bad_response("Response status not have http version"))?; + let status_code: u32 = match status_parts + .get(1) + .ok_or(error::bad_response("Response status not have code"))? + .parse() + { Ok(c) => c, Err(_) => return Err(error::bad_response("Response status code is not a number")), }; let reason = status_parts.get(2).unwrap_or(&""); - response.version(http_version) + response + .version(http_version) .code(status_code) .reason(reason); - let headers = parts.iter().enumerate() + let headers = parts + .iter() + .enumerate() .filter(|(ix, _)| *ix > 0) .filter(|(_, v)| !v.is_empty()) .map(|(_, v)| v.into_headers()) .filter(|hs| !hs.is_empty()) - .map(|hs| { - match hs.get(0) { - Some(h) => Some(h.clone()), - None => None - } + .map(|hs| match hs.get(0) { + Some(h) => Some(h.clone()), + None => None, }) .filter(Option::is_some) .map(|h| h.unwrap()) .collect::>(); - let cookies: Vec = headers.iter() + let cookies: Vec = headers + .iter() .filter(|header| header.name().eq_ignore_ascii_case("set-cookie")) .map(|header| Cookie::parse(header.value()).ok()) .filter(|ck| ck.is_some()) @@ -201,9 +228,13 @@ impl Parser { } fn parse_body(&self, response: &mut RawResponse, binary: Vec) -> error::Result<()> { - if binary.is_empty() { return Ok(()); } + if binary.is_empty() { + return Ok(()); + } - let content_encoding = response.headers_get().iter() + let content_encoding = response + .headers_get() + .iter() .find(|header| header.name().eq_ignore_ascii_case("Content-Encoding")); if let Some(header) = content_encoding { @@ -222,5 +253,3 @@ impl Parser { Ok(()) } } - - diff --git a/rttp_client/src/types/form_data.rs b/rttp_client/src/types/form_data.rs index 4bb44b6..cffd244 100644 --- a/rttp_client/src/types/form_data.rs +++ b/rttp_client/src/types/form_data.rs @@ -1,5 +1,5 @@ -use std::path::{Path, PathBuf}; use std::collections::HashMap; +use std::path::{Path, PathBuf}; #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum FormDataType { @@ -23,6 +23,7 @@ pub struct FormData { array: bool, } +#[allow(dead_code)] impl FormData { pub fn with_text, T: AsRef>(name: S, text: T) -> Self { Self { @@ -38,14 +39,23 @@ impl FormData { pub fn with_file, P: AsRef>(name: S, file: P) -> Self { let file = file.as_ref(); - let filename = file.file_name() + let filename = file + .file_name() .map_or("".to_string(), |v| v.to_string_lossy().to_string()); Self::with_file_and_name(name, file, filename) } - pub fn with_file_and_name, N: AsRef, P: AsRef>(name: S, file: P, filename: N) -> Self { + pub fn with_file_and_name, N: AsRef, P: AsRef>( + name: S, + file: P, + filename: N, + ) -> Self { let filename = filename.as_ref(); - let filename = if filename.is_empty() { None } else { Some(filename.to_string()) }; + let filename = if filename.is_empty() { + None + } else { + Some(filename.to_string()) + }; Self { name: name.as_ref().into(), text: None, @@ -69,25 +79,59 @@ impl FormData { } } - pub fn name(&self) -> &String { &self.name } - pub fn text(&self) -> &Option { &self.text } - pub fn file(&self) -> &Option { &self.file } - pub fn filename(&self) -> &Option { &self.filename } - pub fn binary(&self) -> &Vec { &self.binary } - pub fn type_(&self) -> &FormDataType { &self.type_ } - pub fn array(&self) -> bool { self.array } + pub fn name(&self) -> &String { + &self.name + } + pub fn text(&self) -> &Option { + &self.text + } + pub fn file(&self) -> &Option { + &self.file + } + pub fn filename(&self) -> &Option { + &self.filename + } + pub fn binary(&self) -> &Vec { + &self.binary + } + pub fn type_(&self) -> &FormDataType { + &self.type_ + } + pub fn array(&self) -> bool { + self.array + } - pub fn is_text(&self) -> bool { self.type_ == FormDataType::TEXT } - pub fn is_file(&self) -> bool { self.type_ == FormDataType::FILE } - pub fn is_binary(&self) -> bool { self.type_ == FormDataType::BINARY } + pub fn is_text(&self) -> bool { + self.type_ == FormDataType::TEXT + } + pub fn is_file(&self) -> bool { + self.type_ == FormDataType::FILE + } + pub fn is_binary(&self) -> bool { + self.type_ == FormDataType::BINARY + } - pub(crate) fn name_mut(&mut self) -> &mut String { &mut self.name } - pub(crate) fn text_mut(&mut self) -> &mut Option { &mut self.text } - pub(crate) fn file_mut(&mut self) -> &mut Option { &mut self.file } - pub(crate) fn filename_mut(&mut self) -> &mut Option { &mut self.filename } - pub(crate) fn binary_mut(&mut self) -> &mut Vec { &mut self.binary } - pub(crate) fn type_mut(&mut self) -> &mut FormDataType { &mut self.type_ } - pub(crate) fn array_mut(&mut self) -> &mut bool { &mut self.array } + pub(crate) fn name_mut(&mut self) -> &mut String { + &mut self.name + } + pub(crate) fn text_mut(&mut self) -> &mut Option { + &mut self.text + } + pub(crate) fn file_mut(&mut self) -> &mut Option { + &mut self.file + } + pub(crate) fn filename_mut(&mut self) -> &mut Option { + &mut self.filename + } + pub(crate) fn binary_mut(&mut self) -> &mut Vec { + &mut self.binary + } + pub(crate) fn type_mut(&mut self) -> &mut FormDataType { + &mut self.type_ + } + pub(crate) fn array_mut(&mut self) -> &mut bool { + &mut self.array + } } impl ToFormData for FormData { @@ -103,7 +147,9 @@ impl<'a> ToFormData for &'a str { /// name=Nick&file=@/path/to/file&file_and_filename=@filename#/path/to/file /// ``` fn to_formdatas(&self) -> Vec { - self.split("&").collect::>() + self + .split("&") + .collect::>() .iter() .map(|part: &&str| { let pvs: Vec<&str> = part.split("=").collect::>(); @@ -118,11 +164,16 @@ impl<'a> ToFormData for &'a str { } let hasps: Vec<&str> = (&value[1..]).split("#").collect::>(); let len = hasps.len(); - let filename = hasps.iter().enumerate().filter(|(ix, _)| ix + 1 < len) + let filename = hasps + .iter() + .enumerate() + .filter(|(ix, _)| ix + 1 < len) .map(|(_, &v)| v) .collect::>() .join("#"); - let path = hasps.get(len - 1).map_or("".to_string(), |v| v.trim().to_string()); + let path = hasps + .get(len - 1) + .map_or("".to_string(), |v| v.trim().to_string()); let path = Path::new(&path); FormData::with_file_and_name(name, path, filename) }) @@ -137,7 +188,6 @@ impl ToFormData for String { } } - impl + Eq + std::hash::Hash, V: AsRef> ToFormData for HashMap { fn to_formdatas(&self) -> Vec { let mut rets = Vec::with_capacity(self.len()); @@ -154,11 +204,16 @@ impl + Eq + std::hash::Hash, V: AsRef> ToFormData for HashMap } let hasps: Vec<&str> = (&value[1..]).split("#").collect::>(); let len = hasps.len(); - let filename = hasps.iter().enumerate().filter(|(ix, _)| ix + 1 < len) + let filename = hasps + .iter() + .enumerate() + .filter(|(ix, _)| ix + 1 < len) .map(|(_, &v)| v) .collect::>() .join("#"); - let path = hasps.get(len - 1).map_or("".to_string(), |v| v.trim().to_string()); + let path = hasps + .get(len - 1) + .map_or("".to_string(), |v| v.trim().to_string()); let path = Path::new(&path); rets.push(FormData::with_file_and_name(&name, path, filename)); } @@ -167,7 +222,6 @@ impl + Eq + std::hash::Hash, V: AsRef> ToFormData for HashMap } } - impl<'a, IU: ToFormData> ToFormData for &'a IU { fn to_formdatas(&self) -> Vec { (*self).to_formdatas() @@ -180,11 +234,10 @@ impl<'a, IU: ToFormData> ToFormData for &'a mut IU { } } - - - macro_rules! replace_expr { - ($_t:tt $sub:ty) => {$sub}; + ($_t:tt $sub:ty) => { + $sub + }; } macro_rules! tuple_to_formdata { @@ -259,7 +312,6 @@ macro_rules! tuple_to_formdata { }; } - tuple_to_formdata! { a } tuple_to_formdata! { a b } tuple_to_formdata! { a b c } @@ -286,4 +338,3 @@ tuple_to_formdata! { a b c d e f g h i j k l m n o p q r s t u v w } tuple_to_formdata! { a b c d e f g h i j k l m n o p q r s t u v w x } tuple_to_formdata! { a b c d e f g h i j k l m n o p q r s t u v w x y } tuple_to_formdata! { a b c d e f g h i j k l m n o p q r s t u v w x y z } - diff --git a/rttp_client/src/types/para.rs b/rttp_client/src/types/para.rs index 2c463f8..35f3a0f 100644 --- a/rttp_client/src/types/para.rs +++ b/rttp_client/src/types/para.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use crate::types::FormData; -use crate::types::ParaType::FORM; #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ParaType { @@ -14,7 +13,7 @@ pub struct Para { name: String, value: Option, type_: ParaType, - array: bool + array: bool, } pub trait IntoPara { @@ -24,36 +23,58 @@ pub trait IntoPara { } impl Para { - pub(crate) fn with_url, V: AsRef>(name: N, value: V) -> Self { + pub fn with_url(name: impl AsRef, value: impl AsRef) -> Self { Self { name: name.as_ref().into(), value: Some(value.as_ref().into()), type_: ParaType::URL, - array: false + array: false, } } - pub fn new, V: AsRef>(name: N, value: V) -> Self { + pub fn with_form(name: impl AsRef, value: impl AsRef) -> Self { Self { name: name.as_ref().into(), value: Some(value.as_ref().into()), type_: ParaType::FORM, - array: false + array: false, } } - pub fn name(&self) -> &String { &self.name } - pub fn type_(&self) -> &ParaType { &self.type_ } - pub fn value(&self) -> &Option { &self.value } - pub fn array(&self) -> bool { self.array } + pub fn name(&self) -> &String { + &self.name + } + pub fn type_(&self) -> &ParaType { + &self.type_ + } + pub fn value(&self) -> &Option { + &self.value + } + pub fn array(&self) -> bool { + self.array + } - pub fn is_url(&self) -> bool { self.type_ == ParaType::URL } - pub fn is_form(&self) -> bool { self.type_ == ParaType::FORM } + pub fn is_url(&self) -> bool { + self.type_ == ParaType::URL + } + pub fn is_form(&self) -> bool { + self.type_ == ParaType::FORM + } - pub(crate) fn name_mut(&mut self) -> &mut String { &mut self.name } - pub(crate) fn type_mut(&mut self) -> &mut ParaType { &mut self.type_ } - pub(crate) fn value_mut(&mut self) -> &mut Option { &mut self.value } - pub(crate) fn array_mut(&mut self) -> &mut bool { &mut self.array } + #[allow(dead_code)] + pub(crate) fn name_mut(&mut self) -> &mut String { + &mut self.name + } + pub(crate) fn type_mut(&mut self) -> &mut ParaType { + &mut self.type_ + } + #[allow(dead_code)] + pub(crate) fn value_mut(&mut self) -> &mut Option { + &mut self.value + } + pub(crate) fn array_mut(&mut self) -> &mut bool { + &mut self.array + } } impl Para { @@ -74,11 +95,13 @@ impl IntoPara for Para { impl<'a> IntoPara for &'a str { fn into_paras(&self) -> Vec { - self.split("&").collect::>() + self + .split("&") + .collect::>() .iter() .map(|part: &&str| { let pvs: Vec<&str> = part.split("=").collect::>(); - Para::new( + Para::with_form( pvs.get(0).map_or("".to_string(), |v| v.to_string()).trim(), pvs.get(1).map_or("".to_string(), |v| v.to_string()).trim(), ) @@ -99,15 +122,13 @@ impl + Eq + std::hash::Hash, V: AsRef> IntoPara for HashMap IntoPara for &'a IU { fn into_paras(&self) -> Vec { (*self).into_paras() @@ -120,22 +141,10 @@ impl<'a, IU: IntoPara> IntoPara for &'a mut IU { } } -//impl IntoPara for (&str, &str) { -// fn into_paras(self) -> Vec { -// let para = Para::with_form(self.0, self.1); -// vec![para] -// } -//} - -//impl> IntoPara for (T, T) { -// fn into_paras(self) -> Vec { -// let para = Para::with_form(self.0.as_ref(), self.1.as_ref()); -// vec![para] -// } -//} - macro_rules! replace_expr { - ($_t:tt $sub:ty) => {$sub}; + ($_t:tt $sub:ty) => { + $sub + }; } macro_rules! tuple_to_para { @@ -179,7 +188,7 @@ macro_rules! tuple_to_para { _name = para_first.name().clone(); _position = 1; } else { - rets.push(Para::new(&_name, para_first.name())); + rets.push(Para::with_form(&_name, para_first.name())); _position = 0; } } @@ -218,5 +227,3 @@ tuple_to_para! { a b c d e f g h i j k l m n o p q r s t u v w } tuple_to_para! { a b c d e f g h i j k l m n o p q r s t u v w x } tuple_to_para! { a b c d e f g h i j k l m n o p q r s t u v w x y } tuple_to_para! { a b c d e f g h i j k l m n o p q r s t u v w x y z } - - diff --git a/rttp_client/src/types/status.rs b/rttp_client/src/types/status.rs index 3b8fbd9..7112e80 100644 --- a/rttp_client/src/types/status.rs +++ b/rttp_client/src/types/status.rs @@ -14,8 +14,8 @@ //! assert!(StatusCode::OK.is_success()); //! ``` -use std::fmt; use std::error::Error; +use std::fmt; use std::str::FromStr; //use HttpTryFrom; @@ -151,7 +151,6 @@ impl StatusCode { canonical_reason(self.0) } - /// Check if status is within 100-199. #[inline] pub fn is_informational(&self) -> bool { @@ -199,8 +198,12 @@ impl fmt::Debug for StatusCode { /// ``` impl fmt::Display for StatusCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", u16::from(*self), - self.canonical_reason().unwrap_or("")) + write!( + f, + "{} {}", + u16::from(*self), + self.canonical_reason().unwrap_or("") + ) } } @@ -247,42 +250,6 @@ impl<'a> From<&'a StatusCode> for StatusCode { } } -//impl<'a> HttpTryFrom<&'a StatusCode> for StatusCode { -// type Error = ::error::Never; -// -// #[inline] -// fn try_from(t: &'a StatusCode) -> Result { -// Ok(t.clone()) -// } -//} -// -//impl<'a> HttpTryFrom<&'a [u8]> for StatusCode { -// type Error = InvalidStatusCode; -// -// #[inline] -// fn try_from(t: &'a [u8]) -> Result { -// StatusCode::from_bytes(t) -// } -//} -// -//impl<'a> HttpTryFrom<&'a str> for StatusCode { -// type Error = InvalidStatusCode; -// -// #[inline] -// fn try_from(t: &'a str) -> Result { -// t.parse() -// } -//} -// -//impl HttpTryFrom for StatusCode { -// type Error = InvalidStatusCode; -// -// #[inline] -// fn try_from(t: u16) -> Result { -// StatusCode::from_u16(t) -// } -//} - macro_rules! status_codes { ( $( @@ -505,9 +472,7 @@ status_codes! { impl InvalidStatusCode { fn new() -> InvalidStatusCode { - InvalidStatusCode { - _priv: (), - } + InvalidStatusCode { _priv: () } } } @@ -521,7 +486,7 @@ impl fmt::Debug for InvalidStatusCode { impl fmt::Display for InvalidStatusCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.description()) + f.write_str(&self.to_string()) } } @@ -538,33 +503,31 @@ macro_rules! status_code_strs { } status_code_strs!( - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, - - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, - 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, - 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, - - 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, - 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, - 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, - 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, - 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, - - 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, - 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, - 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, - 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, - 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, - - 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, - 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, - 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, - 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, - 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, + 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, + 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, + 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, + 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, + 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, + 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, + 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, + 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, + 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, + 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, + 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, + 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, + 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, + 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, + 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, + 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, + 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, + 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, + 594, 595, 596, 597, 598, 599, ); diff --git a/rttp_client/src/types/url.rs b/rttp_client/src/types/url.rs index 19342c5..5484897 100644 --- a/rttp_client/src/types/url.rs +++ b/rttp_client/src/types/url.rs @@ -19,7 +19,7 @@ use crate::types::{IntoPara, Para, ParaType}; /// .para("name=value") /// .para("name=value&name=value") /// .para(("name", "value", "name=value&name=value")) -/// .para(Para::new("name", "value")); +/// .para(Para::with_form("name", "value")); /// ``` #[derive(Clone, Debug)] pub struct RoUrl { @@ -42,6 +42,7 @@ pub trait ToUrl: Debug { fn to_url(&self) -> error::Result; } +#[allow(dead_code)] impl RoUrl { /// Create a rourl /// # Examples @@ -118,28 +119,6 @@ impl RoUrl { self.traditional.clone() } - pub(crate) fn url_mut(&mut self) -> &mut String { - &mut self.url - } - pub(crate) fn paths_mut(&mut self) -> &mut Vec { - &mut self.paths - } - pub(crate) fn username_mut(&mut self) -> &mut String { - &mut self.username - } - pub(crate) fn password_mut(&mut self) -> &mut Option { - &mut self.password - } - pub(crate) fn paras_mut(&mut self) -> &mut Vec { - &mut self.paras - } - pub(crate) fn fragment_mut(&mut self) -> &mut Option { - &mut self.fragment - } - pub(crate) fn traditional_mut(&mut self) -> &mut Option { - &mut self.traditional - } - /// Set fragment to url pub fn fragment>(&mut self, fragment: S) -> &mut Self { self.fragment = Some(fragment.as_ref().into()); diff --git a/rttp_client/tests/test_http_basic.rs b/rttp_client/tests/test_http_basic.rs index dc86be9..8b00673 100644 --- a/rttp_client/tests/test_http_basic.rs +++ b/rttp_client/tests/test_http_basic.rs @@ -86,7 +86,7 @@ fn test_raw_form_urlencoded() { client() .method("post") .url("http://httpbin.org/post") - .para(Para::new("name", "Chico")) + .para(Para::with_form("name", "Chico")) .raw("name=Nick&name=Wendy") .content_type("application/x-www-form-urlencoded") .emit() @@ -99,7 +99,7 @@ fn test_https() { let response = client() .get() .url("https://bing.com") - .para(Para::new("q", "News")) + .para(Para::with_form("q", "News")) .emit(); assert!(response.is_ok()); let response = response.unwrap(); From 78d81a860db401cdf7c78d62ff03b28ed6969d12 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 17:41:34 +0800 Subject: [PATCH 10/19] lint --- rttp_client/src/request/builder/common.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rttp_client/src/request/builder/common.rs b/rttp_client/src/request/builder/common.rs index 8f705d4..c41a63d 100644 --- a/rttp_client/src/request/builder/common.rs +++ b/rttp_client/src/request/builder/common.rs @@ -3,9 +3,9 @@ use mime::Mime; use crate::error; use crate::request::{RawRequest, Request}; -pub static HYPHENS: &'static str = "---------------------------"; -pub static DISPOSITION_PREFIX: &'static str = "--"; -pub static DISPOSITION_END: &'static str = "\r\n"; +pub static HYPHENS: &str = "---------------------------"; +pub static DISPOSITION_PREFIX: &str = "--"; +pub static DISPOSITION_END: &str = "\r\n"; #[derive(Debug)] pub struct RawBuilder<'a> { From d5cdfb8af55f4f8eab2e590e4f936b693bbae01e Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 28 Nov 2021 23:18:39 +0800 Subject: [PATCH 11/19] refactor features --- rttp/Cargo.toml | 2 +- rttp_client/Cargo.toml | 30 ++++++++++++++----- rttp_client/README.md | 2 +- rttp_client/src/client.rs | 6 ++-- rttp_client/src/connection/block_stream.rs | 0 rttp_client/src/connection/mod.rs | 6 ++-- rttp_client/src/lib.rs | 2 +- .../request/builder/build_body_async_std.rs | 10 +++---- rttp_client/src/request/builder/common.rs | 2 +- rttp_client/src/request/raw_request.rs | 2 +- rttp_client/src/response/raw_response.rs | 6 ++-- rttp_client/tests/test_http_async.rs | 14 +++++---- 12 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 rttp_client/src/connection/block_stream.rs diff --git a/rttp/Cargo.toml b/rttp/Cargo.toml index 599ccbe..adc241e 100644 --- a/rttp/Cargo.toml +++ b/rttp/Cargo.toml @@ -19,7 +19,7 @@ edition = "2018" [dependencies] -rttp_client = { version = "=0.1.0", optional = true, path = "../rttp_client", features = [ "tls-native", "async" ] } +rttp_client = { optional = true, path = "../rttp_client", features = [ "tls-native", "async-std" ] } [dev-dependencies] diff --git a/rttp_client/Cargo.toml b/rttp_client/Cargo.toml index 5298545..a96194a 100644 --- a/rttp_client/Cargo.toml +++ b/rttp_client/Cargo.toml @@ -33,15 +33,31 @@ flate2 = "1.0" httpdate = "0.3" -native-tls = { optional = true, version = "0.2" } -rustls = { optional = true, version = "0.16" } -webpki-roots = { optional = true, version = "0.18" } -webpki = { optional = true, version = "0.21" } +native-tls = { optional = true, version = "0.2" } +async-native-tls = { optional = true, version = "0.4" } + +rustls = { optional = true, version = "0.16" } +webpki-roots = { optional = true, version = "0.18" } +webpki = { optional = true, version = "0.21" } +async-rustls = { optional = true, version = "0.2" } + async-std = { optional = true, version = "1", features = ["std"] } +tokio = { optional = true, version = "1", features = ["io-std"] } [features] default = [] -tls-native = ["native-tls"] -tls-rustls = ["rustls", "webpki", "webpki-roots"] -async = ["async-std"] +async-tokio = ["tokio"] + +tls-native = ["native-tls", "async-native-tls"] +tls-rustls = ["rustls", "webpki", "webpki-roots", "async-rustls"] + +## +# warning: /data/rttp/rttp_client/Cargo.toml: Found `feature = ...` in `target.'cfg(...)'.dependencies`. +# This key is not supported for selecting dependencies and will not work as expected. +# Use the [features] section instead: https://doc.rust-lang.org/cargo/reference/features.html +## + +#[target.'cfg(any(feature = "async-std", feature = "async-tokio"))'.features] +#tls-native = ["async-native-tls"] +#tls-rustls = ["async-rustls", "webpki", "webpki-roots"] diff --git a/rttp_client/README.md b/rttp_client/README.md index 8436d8c..4e4b7ac 100644 --- a/rttp_client/README.md +++ b/rttp_client/README.md @@ -199,7 +199,7 @@ assert_ne!("bing.com", response.host()); ```rust # use rttp_client::HttpClient; -# #[cfg(feature = "async")] +# #[cfg(feature = "async-std")] let response = HttpClient::new().post() .url("http://httpbin.org/post") .rasync() diff --git a/rttp_client/src/client.rs b/rttp_client/src/client.rs index eebbba2..bc108a4 100644 --- a/rttp_client/src/client.rs +++ b/rttp_client/src/client.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] use crate::connection::AsyncConnection; use crate::connection::BlockConnection; use crate::request::{RawRequest, Request}; @@ -207,7 +207,7 @@ impl HttpClient { /// /// ```rust /// # use rttp_client::HttpClient; - /// # #[cfg(feature = "async")] + /// # #[cfg(feature = "async-std")] /// # async fn test_async() { /// HttpClient::new() /// .url("http://httpbin.org.get") @@ -215,7 +215,7 @@ impl HttpClient { /// .await; /// # } /// ``` - #[cfg(feature = "async")] + #[cfg(feature = "async-std")] pub async fn rasync(&mut self) -> error::Result { if self.request.closed() { return Err(error::connection_closed()); diff --git a/rttp_client/src/connection/block_stream.rs b/rttp_client/src/connection/block_stream.rs new file mode 100644 index 0000000..e69de29 diff --git a/rttp_client/src/connection/mod.rs b/rttp_client/src/connection/mod.rs index 185c901..02e90cb 100644 --- a/rttp_client/src/connection/mod.rs +++ b/rttp_client/src/connection/mod.rs @@ -1,10 +1,10 @@ -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] pub use self::async_std_connection::*; pub use self::block_connection::*; -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] mod async_std_connection; -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] mod async_std_io_block; mod block_connection; mod connection; diff --git a/rttp_client/src/lib.rs b/rttp_client/src/lib.rs index 63b689f..5146169 100644 --- a/rttp_client/src/lib.rs +++ b/rttp_client/src/lib.rs @@ -199,7 +199,7 @@ //! //! ```rust //! # use rttp_client::HttpClient; -//! # #[cfg(feature = "async")] +//! # #[cfg(feature = "async-std")] //! # async fn test_async() { //! let response = HttpClient::new().post() //! .url("http://httpbin.org/post") diff --git a/rttp_client/src/request/builder/build_body_async_std.rs b/rttp_client/src/request/builder/build_body_async_std.rs index 30efa4f..8ee64da 100644 --- a/rttp_client/src/request/builder/build_body_async_std.rs +++ b/rttp_client/src/request/builder/build_body_async_std.rs @@ -1,13 +1,13 @@ -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] use crate::error; -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] use crate::request::builder::common::RawBuilder; -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] use crate::request::RequestBody; -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] use crate::types::{FormDataType, RoUrl}; -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] impl<'a> RawBuilder<'a> { pub async fn build_body_async_std( &mut self, diff --git a/rttp_client/src/request/builder/common.rs b/rttp_client/src/request/builder/common.rs index c41a63d..531a9d7 100644 --- a/rttp_client/src/request/builder/common.rs +++ b/rttp_client/src/request/builder/common.rs @@ -56,7 +56,7 @@ impl<'a> RawBuilder<'a> { }) } - #[cfg(feature = "async")] + #[cfg(feature = "async-std")] pub async fn raw_request_async_std(mut self) -> error::Result> { let mut rourl = self.request.url().clone().ok_or(error::none_url())?; if rourl.traditional_get().is_none() { diff --git a/rttp_client/src/request/raw_request.rs b/rttp_client/src/request/raw_request.rs index 6894a73..98de8ec 100644 --- a/rttp_client/src/request/raw_request.rs +++ b/rttp_client/src/request/raw_request.rs @@ -16,7 +16,7 @@ impl<'a> RawRequest<'a> { RawBuilder::new(request).raw_request_block() } - #[cfg(feature = "async")] + #[cfg(feature = "async-std")] pub async fn async_new(request: &'a mut Request) -> error::Result> { RawBuilder::new(request).raw_request_async_std().await } diff --git a/rttp_client/src/response/raw_response.rs b/rttp_client/src/response/raw_response.rs index a7cee33..6a86abb 100644 --- a/rttp_client/src/response/raw_response.rs +++ b/rttp_client/src/response/raw_response.rs @@ -6,9 +6,9 @@ use crate::response::ResponseBody; use crate::types::{Cookie, Header, IntoHeader, RoUrl, ToUrl}; use url::Url; -const CR: u8 = b'\r'; -const LF: u8 = b'\n'; -const CRLF: &'static str = "\r\n"; +static CR: u8 = b'\r'; +static LF: u8 = b'\n'; +static CRLF: &str = "\r\n"; #[derive(Clone)] pub struct RawResponse { diff --git a/rttp_client/tests/test_http_async.rs b/rttp_client/tests/test_http_async.rs index 1a23a21..174f10f 100644 --- a/rttp_client/tests/test_http_async.rs +++ b/rttp_client/tests/test_http_async.rs @@ -1,15 +1,15 @@ -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] use async_std::task; -use rttp_client::HttpClient; use rttp_client::types::Proxy; +use rttp_client::HttpClient; fn client() -> HttpClient { HttpClient::new() } #[test] -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] fn test_async_http() { task::block_on(async { let response = client() @@ -26,7 +26,10 @@ fn test_async_http() { } #[test] -#[cfg(all(feature = "async", any(feature = "tls-rustls", feature = "tls-native")))] +#[cfg(all( + feature = "async-std", + any(feature = "tls-rustls", feature = "tls-native") +))] fn test_async_https() { task::block_on(async { let response = client() @@ -43,7 +46,7 @@ fn test_async_https() { #[test] #[ignore] -#[cfg(feature = "async")] +#[cfg(feature = "async-std")] fn test_async_proxy_socks5() { task::block_on(async { let response = client() @@ -58,4 +61,3 @@ fn test_async_proxy_socks5() { println!("{}", response); }); } - From 398a20e236c298a962705550f83ff480300d6230 Mon Sep 17 00:00:00 2001 From: fewensa Date: Mon, 29 Nov 2021 10:01:30 +0800 Subject: [PATCH 12/19] refactor features --- rttp_client/Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rttp_client/Cargo.toml b/rttp_client/Cargo.toml index a96194a..fa396de 100644 --- a/rttp_client/Cargo.toml +++ b/rttp_client/Cargo.toml @@ -57,7 +57,11 @@ tls-rustls = ["rustls", "webpki", "webpki-roots", "async-rustls"] # This key is not supported for selecting dependencies and will not work as expected. # Use the [features] section instead: https://doc.rust-lang.org/cargo/reference/features.html ## - #[target.'cfg(any(feature = "async-std", feature = "async-tokio"))'.features] #tls-native = ["async-native-tls"] #tls-rustls = ["async-rustls", "webpki", "webpki-roots"] + +## Not good way +#[target.'cfg(any(feature = "async-std", feature = "async-tokio"))'.dependencies] +#tls-native = { optional = true, package = "async-native-tls", version = "0.4" } +#tls-rustls = { optional = true, package = "async-rustls", version = "0.2" } From eefa7516f374303ddc3c1605992d1bf9c1838c53 Mon Sep 17 00:00:00 2001 From: fewensa Date: Sun, 17 Apr 2022 11:58:52 +0800 Subject: [PATCH 13/19] Revert test host --- rttp_client/tests/test_http_basic.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rttp_client/tests/test_http_basic.rs b/rttp_client/tests/test_http_basic.rs index 8b00673..289a602 100644 --- a/rttp_client/tests/test_http_basic.rs +++ b/rttp_client/tests/test_http_basic.rs @@ -9,12 +9,12 @@ fn client() -> HttpClient { #[test] fn test_http() { - // let response = client().url("http://httpbin.org/get").emit(); - let response = client().url("http://debian:1234/get").emit(); + let response = client().url("http://httpbin.org/get").emit(); + // let response = client().url("http://debian:1234/get").emit(); println!("{:?}", response); assert!(response.is_ok()); let response = response.unwrap(); - assert_eq!("debian", response.host()); + assert_eq!("httpbin.org", response.host()); println!("{}", response); } From 99676a0d7101abe3d3d115a3d533441cd7363537 Mon Sep 17 00:00:00 2001 From: fewensa <37804932+fewensa@users.noreply.github.com> Date: Sat, 7 Feb 2026 13:08:28 +0800 Subject: [PATCH 14/19] Migrate async client to futures and socket2 (#2) --- .github/workflows/ci.yml | 5 +- rttp/Cargo.toml | 4 +- rttp_client/Cargo.toml | 11 +- rttp_client/README.md | 2 +- rttp_client/src/client.rs | 6 +- rttp_client/src/config.rs | 16 +- .../src/connection/async_connection.rs | 180 ++++++++++++++ .../src/connection/async_std_connection.rs | 224 ------------------ .../src/connection/async_std_io_block.rs | 91 ------- rttp_client/src/connection/connection.rs | 90 +++++-- rttp_client/src/connection/mod.rs | 11 +- rttp_client/src/lib.rs | 2 +- ..._body_async_std.rs => build_body_async.rs} | 23 +- rttp_client/src/request/builder/common.rs | 4 +- rttp_client/src/request/builder/mod.rs | 2 +- rttp_client/src/request/raw_request.rs | 4 +- rttp_client/tests/support/mod.rs | 79 ++++++ rttp_client/tests/test_http_async.rs | 36 +-- rttp_client/tests/test_http_basic.rs | 79 +++--- rttp_client/tests/test_rustls.rs | 59 +++-- 20 files changed, 472 insertions(+), 456 deletions(-) create mode 100644 rttp_client/src/connection/async_connection.rs delete mode 100644 rttp_client/src/connection/async_std_connection.rs delete mode 100644 rttp_client/src/connection/async_std_io_block.rs rename rttp_client/src/request/builder/{build_body_async_std.rs => build_body_async.rs} (74%) create mode 100644 rttp_client/tests/support/mod.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d62250..d34770d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,8 +19,11 @@ jobs: toolchain: stable default: true + - name: Format + run: cargo fmt --all -- --check + - name: Lint run: cargo clippy --all -- -D warnings - name: Run tests - run: cargo test + run: cargo test --all-features diff --git a/rttp/Cargo.toml b/rttp/Cargo.toml index adc241e..2ee2cb3 100644 --- a/rttp/Cargo.toml +++ b/rttp/Cargo.toml @@ -19,13 +19,11 @@ edition = "2018" [dependencies] -rttp_client = { optional = true, path = "../rttp_client", features = [ "tls-native", "async-std" ] } +rttp_client = { optional = true, path = "../rttp_client", features = [ "tls-native", "async" ] } [dev-dependencies] -async-std = { version = "1" } - [features] default = [] diff --git a/rttp_client/Cargo.toml b/rttp_client/Cargo.toml index fa396de..06e2093 100644 --- a/rttp_client/Cargo.toml +++ b/rttp_client/Cargo.toml @@ -19,6 +19,8 @@ edition = "2018" [dependencies] tracing = "0.1" +socket2 = "0.5" +futures = "0.3" url = "2" percent-encoding = "2" @@ -41,14 +43,10 @@ webpki-roots = { optional = true, version = "0.18" } webpki = { optional = true, version = "0.21" } async-rustls = { optional = true, version = "0.2" } -async-std = { optional = true, version = "1", features = ["std"] } -tokio = { optional = true, version = "1", features = ["io-std"] } [features] default = [] - -async-tokio = ["tokio"] - +async = [] tls-native = ["native-tls", "async-native-tls"] tls-rustls = ["rustls", "webpki", "webpki-roots", "async-rustls"] @@ -65,3 +63,6 @@ tls-rustls = ["rustls", "webpki", "webpki-roots", "async-rustls"] #[target.'cfg(any(feature = "async-std", feature = "async-tokio"))'.dependencies] #tls-native = { optional = true, package = "async-native-tls", version = "0.4" } #tls-rustls = { optional = true, package = "async-rustls", version = "0.2" } + +[dev-dependencies] +rcgen = "0.11" diff --git a/rttp_client/README.md b/rttp_client/README.md index 4e4b7ac..8436d8c 100644 --- a/rttp_client/README.md +++ b/rttp_client/README.md @@ -199,7 +199,7 @@ assert_ne!("bing.com", response.host()); ```rust # use rttp_client::HttpClient; -# #[cfg(feature = "async-std")] +# #[cfg(feature = "async")] let response = HttpClient::new().post() .url("http://httpbin.org/post") .rasync() diff --git a/rttp_client/src/client.rs b/rttp_client/src/client.rs index bc108a4..eebbba2 100644 --- a/rttp_client/src/client.rs +++ b/rttp_client/src/client.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] use crate::connection::AsyncConnection; use crate::connection::BlockConnection; use crate::request::{RawRequest, Request}; @@ -207,7 +207,7 @@ impl HttpClient { /// /// ```rust /// # use rttp_client::HttpClient; - /// # #[cfg(feature = "async-std")] + /// # #[cfg(feature = "async")] /// # async fn test_async() { /// HttpClient::new() /// .url("http://httpbin.org.get") @@ -215,7 +215,7 @@ impl HttpClient { /// .await; /// # } /// ``` - #[cfg(feature = "async-std")] + #[cfg(feature = "async")] pub async fn rasync(&mut self) -> error::Result { if self.request.closed() { return Err(error::connection_closed()); diff --git a/rttp_client/src/config.rs b/rttp_client/src/config.rs index 3d233ec..c33c494 100644 --- a/rttp_client/src/config.rs +++ b/rttp_client/src/config.rs @@ -4,9 +4,7 @@ pub struct Config { write_timeout: u64, auto_redirect: bool, max_redirect: u32, - #[cfg(feature = "tls-native")] - verify_ssl_hostname: bool, - #[cfg(feature = "tls-native")] + verify_ssl_hostname: bool, verify_ssl_cert: bool, } @@ -40,11 +38,9 @@ impl Config { pub fn max_redirect(&self) -> u32 { self.max_redirect } - #[cfg(feature = "tls-native")] - pub fn verify_ssl_cert(&self) -> bool { + pub fn verify_ssl_cert(&self) -> bool { self.verify_ssl_cert } - #[cfg(feature = "tls-native")] pub fn verify_ssl_hostname(&self) -> bool { self.verify_ssl_hostname } @@ -63,9 +59,7 @@ impl ConfigBuilder { write_timeout: 5000, auto_redirect: false, max_redirect: 3, - #[cfg(feature = "tls-native")] - verify_ssl_hostname: true, - #[cfg(feature = "tls-native")] + verify_ssl_hostname: true, verify_ssl_cert: true, }, } @@ -91,12 +85,10 @@ impl ConfigBuilder { self.config.max_redirect = max_redirect; self } - #[cfg(feature = "tls-native")] - pub fn verify_ssl_hostname(&mut self, verify_ssl_hostname: bool) -> &mut Self { + pub fn verify_ssl_hostname(&mut self, verify_ssl_hostname: bool) -> &mut Self { self.config.verify_ssl_hostname = verify_ssl_hostname; self } - #[cfg(feature = "tls-native")] pub fn verify_ssl_cert(&mut self, verify_ssl_cert: bool) -> &mut Self { self.config.verify_ssl_cert = verify_ssl_cert; self diff --git a/rttp_client/src/connection/async_connection.rs b/rttp_client/src/connection/async_connection.rs new file mode 100644 index 0000000..471f89f --- /dev/null +++ b/rttp_client/src/connection/async_connection.rs @@ -0,0 +1,180 @@ +use std::net::TcpStream; + +use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, AllowStdIo}; +use std::io::{Read, Write}; +use socks::{Socks4Stream, Socks5Stream}; +use url::Url; + +use crate::connection::connection::Connection; +use crate::error; +use crate::request::RawRequest; +use crate::response::Response; +use crate::types::{Proxy, ProxyType, ToUrl}; + +pub struct AsyncConnection<'a> { + conn: Connection<'a>, +} + +impl<'a> AsyncConnection<'a> { + pub fn new(request: RawRequest<'a>) -> AsyncConnection<'a> { + Self { + conn: Connection::new(request), + } + } + + pub async fn async_call(mut self) -> error::Result { + let url = self.conn.url().map_err(error::builder)?; + let proxy = self.conn.proxy(); + let binary = if let Some(proxy) = proxy { + self.call_with_proxy(&url, proxy).await? + } else { + self.async_send(&url).await? + }; + + let response = Response::new(self.conn.rourl().clone(), binary)?; + self.conn.closed_set(true); + Ok(response) + } +} + +impl<'a> AsyncConnection<'a> { + async fn async_tcp_stream(&self, addr: &String) -> error::Result { + self.conn.block_tcp_stream(addr) + } + + async fn async_write_stream(&self, stream: &mut S) -> error::Result<()> + where + S: AsyncWrite + Unpin, + { + let header = self.conn.header(); + let body = self.conn.body(); + + stream.write_all(header.as_bytes()).await.map_err(error::request)?; + if let Some(body) = body { + stream.write_all(body.bytes()).await.map_err(error::request)?; + } + stream.flush().await.map_err(error::request)?; + + Ok(()) + } + + async fn async_read_stream(&self, _url: &Url, stream: &mut S) -> error::Result> + where + S: AsyncRead + Unpin, + { + let mut buffer = Vec::new(); + stream.read_to_end(&mut buffer).await.map_err(error::request)?; + Ok(buffer) + } +} + +// connection send +impl<'a> AsyncConnection<'a> { + async fn async_send(&self, url: &Url) -> error::Result> { + let addr = self.conn.addr(url)?; + let stream = self.async_tcp_stream(&addr).await?; + + self.async_send_with_stream(url, stream).await + } + + async fn async_send_with_stream(&self, url: &Url, stream: TcpStream) -> error::Result> { + match url.scheme() { + "http" => { + let mut stream = AllowStdIo::new(stream); + self.async_send_http(url, &mut stream).await + } + "https" => self.async_send_https(url, stream).await, + _ => Err(error::url_bad_scheme(url.clone())), + } + } + + async fn async_send_http(&self, url: &Url, stream: &mut S) -> error::Result> + where + S: AsyncRead + AsyncWrite + Unpin, + { + self.async_write_stream(stream).await?; + self.async_read_stream(url, stream).await + } + + #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))] + async fn async_send_https(&self, _url: &Url, _stream: TcpStream) -> error::Result> { + Err(error::no_request_features( + "Not have any tls features, Can't request a https url", + )) + } + + #[cfg(feature = "tls-native")] + async fn async_send_https(&self, url: &Url, mut stream: TcpStream) -> error::Result> { + self.conn.block_send_https(url, &mut stream) + } + + #[cfg(feature = "tls-rustls")] + async fn async_send_https(&self, url: &Url, mut stream: TcpStream) -> error::Result> { + self.conn.block_send_https(url, &mut stream) + } +} + +// proxy connection +impl<'a> AsyncConnection<'a> { + async fn call_with_proxy(&self, url: &Url, proxy: &Proxy) -> error::Result> { + match proxy.type_() { + ProxyType::HTTP => self.call_with_proxy_https(url, proxy).await, + ProxyType::HTTPS => self.call_with_proxy_https(url, proxy).await, + ProxyType::SOCKS4 => self.call_with_proxy_socks4(url, proxy).await, + ProxyType::SOCKS5 => self.call_with_proxy_socks5(url, proxy).await, + } + } + + async fn call_with_proxy_https(&self, url: &Url, proxy: &Proxy) -> error::Result> { + let connect_header = self.conn.proxy_header(url, proxy)?; + + let addr = format!("{}:{}", proxy.host(), proxy.port()); + let mut stream = self.async_tcp_stream(&addr).await?; + + stream.write_all(connect_header.as_bytes()).map_err(error::request)?; + stream.flush().map_err(error::request)?; + + // HTTP/1.1 200 Connection Established + let mut res = vec![0u8; 1024]; + let bytes = stream.read(&mut res).map_err(error::request)?; + + let res_s = match String::from_utf8(res[..bytes].to_vec()) { + Ok(r) => r, + Err(_) => return Err(error::bad_proxy("parse proxy server response error.")), + }; + if !res_s.to_ascii_lowercase().contains("connection established") { + return Err(error::bad_proxy("Proxy server response error.")); + } + + self.async_send_with_stream(url, stream).await + } + + async fn call_with_proxy_socks4(&self, url: &Url, proxy: &Proxy) -> error::Result> { + let addr_proxy = format!("{}:{}", proxy.host(), proxy.port()); + let addr_target = self.conn.addr(url)?; + let user = if let Some(u) = proxy.username() { + u.to_string() + } else { + "".to_string() + }; + let mut stream = Socks4Stream::connect(&addr_proxy[..], &addr_target[..], &user[..]) + .map_err(error::request)?; + self.conn.block_send_with_stream(url, &mut stream) + } + + async fn call_with_proxy_socks5(&self, url: &Url, proxy: &Proxy) -> error::Result> { + let addr_proxy = format!("{}:{}", proxy.host(), proxy.port()); + let addr_target = self.conn.addr(url)?; + let mut stream = if let Some(u) = proxy.username() { + if let Some(p) = proxy.password() { + Socks5Stream::connect_with_password(&addr_proxy[..], &addr_target[..], &u[..], &p[..]) + } else { + Socks5Stream::connect_with_password(&addr_proxy[..], &addr_target[..], &u[..], "") + } + } else { + Socks5Stream::connect(&addr_proxy[..], &addr_target[..]) + } + .map_err(error::request)?; + self.conn.block_send_with_stream(url, &mut stream) + } +} diff --git a/rttp_client/src/connection/async_std_connection.rs b/rttp_client/src/connection/async_std_connection.rs deleted file mode 100644 index e7e48f2..0000000 --- a/rttp_client/src/connection/async_std_connection.rs +++ /dev/null @@ -1,224 +0,0 @@ -use std::sync::Arc; - -use async_std::prelude::*; -use socks::{Socks4Stream, Socks5Stream}; -use url::Url; - -use crate::connection::connection::Connection; -use crate::connection::connection_reader::ConnectionReader; -use crate::error; -use crate::request::RawRequest; -use crate::response::Response; -use crate::types::{Proxy, ProxyType, ToUrl}; -use crate::connection::async_std_io_block::AsyncToBlockStream; - -pub struct AsyncConnection<'a> { - conn: Connection<'a> -} - -impl<'a> AsyncConnection<'a> { - pub fn new(request: RawRequest<'a>) -> AsyncConnection<'a> { - Self { conn: Connection::new(request) } - } - - pub async fn async_call(mut self) -> error::Result { - let url = self.conn.url().map_err(error::builder)?; - let proxy = self.conn.proxy(); - let binary = if let Some(proxy) = proxy { - self.call_with_proxy(&url, proxy).await? - } else { - self.async_send(&url).await? - }; -// let binary = self.async_send(&url).await?; - - let response = Response::new(self.conn.rourl().clone(), binary)?; - self.conn.closed_set(true); - Ok(response) - } -} - -impl<'a> AsyncConnection<'a> { - async fn async_tcp_stream(&self, addr: &String) -> error::Result { -// let async_stream = self.async_tcp_stream(addr)?; -// Ok(async_std::net::TcpStream::from(async_stream)) - - let stream = async_std::net::TcpStream::connect(addr).await.map_err(error::request)?; - // todo: async_std tcp stream set timeout? - Ok(stream) - } - - async fn async_write_stream(&self, stream: &mut S) -> error::Result<()> - where - S: async_std::io::Write + std::marker::Unpin, - { - let header = self.conn.header(); - let body = self.conn.body(); - - stream.write(header.as_bytes()).await.map_err(error::request)?; - if let Some(body) = body { - stream.write(body.bytes()).await.map_err(error::request)?; - } - stream.flush().await.map_err(error::request)?; - - Ok(()) - } - - async fn async_read_stream(&self, url: &Url, stream: &mut S) -> error::Result> - where - S: async_std::io::Read + std::marker::Unpin, - { -// let mut reader = ConnectionReader::new(url, stream); -// reader.binary() - - let mut buffer = vec![0u8; 1024]; - let _ = stream.read(&mut buffer).await.map_err(error::request)?; - Ok(buffer) - } -} - -// connection send -impl<'a> AsyncConnection<'a> { - async fn async_send(&self, url: &Url) -> error::Result> { - let addr = self.conn.addr(url)?; - let mut stream = self.async_tcp_stream(&addr).await?; - - self.async_send_with_stream(url, stream).await - } - - async fn async_send_with_stream(&self, url: &Url, mut stream: async_std::net::TcpStream) - -> error::Result> { - match url.scheme() { - "http" => self.async_send_http(url, stream).await, - "https" => self.async_send_https(url, stream).await, - _ => return Err(error::url_bad_scheme(url.clone())) - } - } - - async fn async_send_http(&self, url: &Url, mut stream: async_std::net::TcpStream) - -> error::Result> { - self.async_write_stream(&mut stream).await?; - self.async_read_stream(url, &mut stream).await - } - - #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))] - async fn async_send_https(&self, url: &Url, mut stream: async_std::net::TcpStream) - -> error::Result> { - return Err(error::no_request_features("Not have any tls features, Can't request a https url")); - } - - #[cfg(feature = "tls-native")] - async fn async_send_https(&self, url: &Url, mut stream: async_std::net::TcpStream) -> error::Result> { - let mut stream = AsyncToBlockStream::new(stream); - let connector = native_tls::TlsConnector::builder().build().map_err(error::request)?; - let mut ssl_stream; -// if self.verify { - ssl_stream = connector.connect(&self.conn.host(url)?[..], stream).map_err(error::request)?; -// } else { -// ssl_stream = connector.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(stream).map_err(error::request)?; -// } - - - // fixme: block to async -// self.async_write_stream(&mut ssl_stream).await?; -// self.async_read_stream(url, &mut ssl_stream).await - self.conn.block_write_stream(&mut ssl_stream)?; - self.conn.block_read_stream(url, &mut ssl_stream) - } - - #[cfg(feature = "tls-rustls")] - async fn async_send_https(&self, url: &Url, mut stream: async_std::net::TcpStream) - -> error::Result> { - let mut stream = AsyncToBlockStream::new(stream); - let mut config = rustls::ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - let rc_config = Arc::new(config); - let host = self.conn.host(url)?; - let dns_name = webpki::DNSNameRef::try_from_ascii_str(&host[..]).unwrap(); - let mut client = rustls::ClientSession::new(&rc_config, dns_name); - let mut tls = rustls::Stream::new(&mut client, &mut stream); - - // fixme: block to async -// self.async_write_stream(&mut tls).await?; -// self.async_read_stream(url, &mut tls).await - self.conn.block_write_stream(&mut tls)?; - self.conn.block_read_stream(url, &mut tls) - } -} - -// proxy connection -impl<'a> AsyncConnection<'a> { - async fn call_with_proxy(&self, url: &Url, proxy: &Proxy) -> error::Result> { - match proxy.type_() { - ProxyType::HTTP => self.call_with_proxy_https(url, proxy).await, - ProxyType::HTTPS => self.call_with_proxy_https(url, proxy).await, - ProxyType::SOCKS4 => self.call_with_proxy_socks4(url, proxy).await, - ProxyType::SOCKS5 => self.call_with_proxy_socks5(url, proxy).await, - } - } - -// fn call_with_proxy_http(&self, url: &Url, proxy: &Proxy) -> error::Result> { -// let header = self.request.header(); -// let body = self.request.body(); -// -// let addr = format!("{}:{}", proxy.host(), proxy.port()); -// let mut stream = self.tcp_stream(&addr)?; -// self.call_tcp_stream_http(stream) -// } - - async fn call_with_proxy_https(&self, url: &Url, proxy: &Proxy) -> error::Result> { - let connect_header = self.conn.proxy_header(url, proxy)?; - - let addr = format!("{}:{}", proxy.host(), proxy.port()); - let mut stream = self.async_tcp_stream(&addr).await?; - - stream.write(connect_header.as_bytes()).await.map_err(error::request)?; - stream.flush().await.map_err(error::request)?; - - //HTTP/1.1 200 Connection Established - let mut res = [0u8; 1024]; - stream.read(&mut res).await.map_err(error::request)?; - - let res_s = match String::from_utf8(res.to_vec()) { - Ok(r) => r, - Err(_) => return Err(error::bad_proxy("parse proxy server response error.")) - }; - if !res_s.to_ascii_lowercase().contains("connection established") { - return Err(error::bad_proxy("Proxy server response error.")); - } - - self.async_send_with_stream(url, stream).await - } - - async fn call_with_proxy_socks4(&self, url: &Url, proxy: &Proxy) -> error::Result> { - let addr_proxy = format!("{}:{}", proxy.host(), proxy.port()); - let addr_target = self.conn.addr(url)?; - let user = if let Some(u) = proxy.username() { u.to_string() } else { "".to_string() }; - let mut stream = Socks4Stream::connect(&addr_proxy[..], &addr_target[..], &user[..]) - .map_err(error::request)?; - // fixme: block to async -// let mut stream = BlockToAsyncStream::new(&mut stream); -// self.async_send_with_stream(url, &mut stream).await - self.conn.block_send_with_stream(url, &mut stream) - } - - async fn call_with_proxy_socks5(&self, url: &Url, proxy: &Proxy) -> error::Result> { - let addr_proxy = format!("{}:{}", proxy.host(), proxy.port()); - let addr_target = self.conn.addr(url)?; - let mut stream = if let Some(u) = proxy.username() { - if let Some(p) = proxy.password() { - Socks5Stream::connect_with_password(&addr_proxy[..], &addr_target[..], &u[..], &p[..]) - } else { - Socks5Stream::connect_with_password(&addr_proxy[..], &addr_target[..], &u[..], "") - } - } else { - Socks5Stream::connect(&addr_proxy[..], &addr_target[..]) - }.map_err(error::request)?; - // fixme: block to async -// let mut stream = BlockToAsyncStream::new(&mut stream); -// self.async_send_with_stream(url, &mut stream).await - self.conn.block_send_with_stream(url, &mut stream) - } -} - diff --git a/rttp_client/src/connection/async_std_io_block.rs b/rttp_client/src/connection/async_std_io_block.rs deleted file mode 100644 index 6f4aa3f..0000000 --- a/rttp_client/src/connection/async_std_io_block.rs +++ /dev/null @@ -1,91 +0,0 @@ -//use std::io::{Error, ErrorKind}; -use async_std::prelude::*; -use std::borrow::BorrowMut; - - -#[derive(Debug)] -pub struct AsyncToBlockStream { - async_stream: async_std::net::TcpStream, -} - -impl AsyncToBlockStream { - pub fn new(async_stream: async_std::net::TcpStream) -> Self { - Self { - async_stream - } - } -} - -impl std::io::Read for AsyncToBlockStream { - fn read(&mut self, buf: &mut [u8]) -> Result { - async_std::task::block_on(async { - self.async_stream.read(buf).await - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) - }) - } -} - -impl std::io::Write for AsyncToBlockStream { - fn write(&mut self, buf: &[u8]) -> Result { - async_std::task::block_on(async { - self.async_stream.write(buf).await - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) - }) - } - - fn flush(&mut self) -> Result<(), std::io::Error> { - async_std::task::block_on(async { - self.async_stream.flush().await - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) - }) - } -} - -// todo: impl blcok to async stream -//pub struct BlockToAsyncStream<'a> { -// block_stream: Box<&'a mut dyn io::Read + io::Write>, -//} -// -//impl<'a> BlockToAsyncStream<'a> { -// pub fn new(block_stream: &'a mut dyn io::Read + io::Write) -> BlockToAsyncStream<'a> { -// Self { -// block_stream: Box::new(block_stream) -// } -// } -//} -// -//impl<'a> async_std::io::Stream for BlockToAsyncStream<'a> { -// fn poll_read( -// self: async_std::pin::Pin<&mut Self>, -// cx: &mut async_std::task::Context, -// buf: &mut [u8], -// ) -> async_std::task::Poll> { -// -// } -// -// fn poll_read_vectored( -// self: async_std::pin::Pin<&mut Self>, -// cx: &mut async_std::task::Context<'_>, -// bufs: &mut [async_std::io::IoSliceMut<'_>], -// ) -> async_std::task::Poll> { -// async_std::task::Poll::new(&mut &*self).poll_read_vectored(cx, bufs) -// } -//} -// -//impl<'a> async_std::io::Write for BlockToAsyncStream<'a> { -// fn poll_write( -// self: async_std::pin::Pin<&mut Self>, -// cx: &mut async_std::task::Context, -// buf: &[u8], -// ) -> async_std::task::Poll> { -// -// } -// fn poll_flush(self: async_std::pin::Pin<&mut Self>, cx: &mut async_std::task::Context) -// -> async_std::task::Poll> { -// -// } -// fn poll_close(self: async_std::pin::Pin<&mut Self>, cx: &mut async_std::task::Context) -// -> async_std::task::Poll> { -// -// } -//} diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index 1f67dad..6551173 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -1,4 +1,9 @@ -use std::{io, time}; +use std::{io, net::ToSocketAddrs, time}; + +use socket2::{Domain, Protocol, Socket, Type}; + +#[cfg(feature = "tls-rustls")] +use std::sync::Arc; use url::Url; @@ -7,6 +12,22 @@ use crate::request::{RawRequest, RequestBody}; use crate::types::{Proxy, RoUrl, ToUrl}; use crate::{error, Config}; +#[cfg(feature = "tls-rustls")] +struct NoCertificateVerification; + +#[cfg(feature = "tls-rustls")] +impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef, + _ocsp_response: &[u8], + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } +} + pub struct Connection<'a> { request: RawRequest<'a>, } @@ -103,17 +124,42 @@ impl<'a> Connection<'a> { impl<'a> Connection<'a> { pub fn block_tcp_stream(&self, addr: &String) -> error::Result { let config = self.config(); + let timeout_read = time::Duration::from_millis(config.read_timeout()); + let timeout_write = time::Duration::from_millis(config.write_timeout()); + let mut last_err = None; - // let server: Vec<_> = addr.to_socket_addrs().map_err(error::request)?.collect(); - // println!("{:?}", server); - let stream = std::net::TcpStream::connect(addr).map_err(error::request)?; - stream - .set_read_timeout(Some(time::Duration::from_millis(config.read_timeout()))) - .map_err(error::request)?; - stream - .set_write_timeout(Some(time::Duration::from_millis(config.write_timeout()))) - .map_err(error::request)?; - Ok(stream) + let addrs = addr.to_socket_addrs().map_err(error::request)?; + for addr in addrs { + let domain = Domain::for_address(addr); + let socket = match Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) { + Ok(socket) => socket, + Err(err) => { + last_err = Some(err); + continue; + } + }; + + if let Err(err) = socket.set_read_timeout(Some(timeout_read)) { + last_err = Some(err); + continue; + } + if let Err(err) = socket.set_write_timeout(Some(timeout_write)) { + last_err = Some(err); + continue; + } + + if let Err(err) = socket.connect(&addr.into()) { + last_err = Some(err); + continue; + } + + let stream = socket.into_tcp_stream(); + return Ok(stream); + } + + Err(error::request(last_err.unwrap_or_else(|| { + io::Error::new(io::ErrorKind::Other, "failed to connect") + }))) } pub fn block_write_stream(&self, stream: &mut S) -> error::Result<()> @@ -203,7 +249,7 @@ impl<'a> Connection<'a> { let config = self.config(); let connector = native_tls::TlsConnector::builder() .danger_accept_invalid_certs(!config.verify_ssl_cert()) - .danger_accept_invalid_hostnames(!config.verify_ssl_host_name()) + .danger_accept_invalid_hostnames(!config.verify_ssl_hostname()) .build() .map_err(error::request)?; let mut ssl_stream = connector @@ -213,17 +259,23 @@ impl<'a> Connection<'a> { self.block_write_stream(&mut ssl_stream)?; self.block_read_stream(url, &mut ssl_stream) } - - #[cfg(feature = "tls-rustls")] +#[cfg(feature = "tls-rustls")] pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> where S: io::Read + io::Write, { - let mut config = rustls::ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - let rc_config = Arc::new(config); + let config = self.config(); + let mut rustls_config = rustls::ClientConfig::new(); + if config.verify_ssl_cert() { + rustls_config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + } else { + rustls_config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification)); + } + let rc_config = Arc::new(rustls_config); let host = self.host(url)?; let dns_name = webpki::DNSNameRef::try_from_ascii_str(&host[..]).unwrap(); let mut client = rustls::ClientSession::new(&rc_config, dns_name); diff --git a/rttp_client/src/connection/mod.rs b/rttp_client/src/connection/mod.rs index 02e90cb..92931fc 100644 --- a/rttp_client/src/connection/mod.rs +++ b/rttp_client/src/connection/mod.rs @@ -1,11 +1,10 @@ -#[cfg(feature = "async-std")] -pub use self::async_std_connection::*; +#[cfg(feature = "async")] +pub use self::async_connection::*; pub use self::block_connection::*; -#[cfg(feature = "async-std")] -mod async_std_connection; -#[cfg(feature = "async-std")] -mod async_std_io_block; +#[cfg(feature = "async")] +mod async_connection; +#[cfg(feature = "async")] mod block_connection; mod connection; mod connection_reader; diff --git a/rttp_client/src/lib.rs b/rttp_client/src/lib.rs index 5146169..63b689f 100644 --- a/rttp_client/src/lib.rs +++ b/rttp_client/src/lib.rs @@ -199,7 +199,7 @@ //! //! ```rust //! # use rttp_client::HttpClient; -//! # #[cfg(feature = "async-std")] +//! # #[cfg(feature = "async")] //! # async fn test_async() { //! let response = HttpClient::new().post() //! .url("http://httpbin.org/post") diff --git a/rttp_client/src/request/builder/build_body_async_std.rs b/rttp_client/src/request/builder/build_body_async.rs similarity index 74% rename from rttp_client/src/request/builder/build_body_async_std.rs rename to rttp_client/src/request/builder/build_body_async.rs index 8ee64da..4344e11 100644 --- a/rttp_client/src/request/builder/build_body_async_std.rs +++ b/rttp_client/src/request/builder/build_body_async.rs @@ -1,18 +1,15 @@ -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] use crate::error; -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] use crate::request::builder::common::RawBuilder; -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] use crate::request::RequestBody; -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] use crate::types::{FormDataType, RoUrl}; -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] impl<'a> RawBuilder<'a> { - pub async fn build_body_async_std( - &mut self, - rourl: &mut RoUrl, - ) -> error::Result> { + pub async fn build_body_async(&mut self, rourl: &mut RoUrl) -> error::Result> { if let Some(body) = self.build_body_common(rourl)? { return Ok(Some(body)); } @@ -21,13 +18,13 @@ impl<'a> RawBuilder<'a> { // form-data if !formdatas.is_empty() { - return self.build_body_with_form_data_async_std().await; + return self.build_body_with_form_data_async().await; } - return Ok(None); + Ok(None) } - async fn build_body_with_form_data_async_std(&mut self) -> error::Result> { + async fn build_body_with_form_data_async(&mut self) -> error::Result> { let fdw = self.build_body_with_form_data_sync_common()?; let mut disposition = fdw.disposition; let mut buffer = fdw.buffer; @@ -56,7 +53,7 @@ impl<'a> RawBuilder<'a> { guess.first_or_octet_stream(), ); buffer.extend_from_slice(item.as_bytes()); - let file_content = async_std::fs::read(&file).await.map_err(error::builder)?; + let file_content = std::fs::read(&file).map_err(error::builder)?; buffer.extend(file_content); } } diff --git a/rttp_client/src/request/builder/common.rs b/rttp_client/src/request/builder/common.rs index 531a9d7..a73b605 100644 --- a/rttp_client/src/request/builder/common.rs +++ b/rttp_client/src/request/builder/common.rs @@ -56,8 +56,8 @@ impl<'a> RawBuilder<'a> { }) } - #[cfg(feature = "async-std")] - pub async fn raw_request_async_std(mut self) -> error::Result> { + #[cfg(feature = "async")] + pub async fn raw_request_async(mut self) -> error::Result> { let mut rourl = self.request.url().clone().ok_or(error::none_url())?; if rourl.traditional_get().is_none() { rourl.traditional(self.request.traditional()); diff --git a/rttp_client/src/request/builder/mod.rs b/rttp_client/src/request/builder/mod.rs index 6cc6d44..3f2928c 100644 --- a/rttp_client/src/request/builder/mod.rs +++ b/rttp_client/src/request/builder/mod.rs @@ -1,6 +1,6 @@ pub use self::common::*; -mod build_body_async_std; +mod build_body_async; mod build_body_block; mod build_header; mod build_para_and_url; diff --git a/rttp_client/src/request/raw_request.rs b/rttp_client/src/request/raw_request.rs index 98de8ec..eccd691 100644 --- a/rttp_client/src/request/raw_request.rs +++ b/rttp_client/src/request/raw_request.rs @@ -16,9 +16,9 @@ impl<'a> RawRequest<'a> { RawBuilder::new(request).raw_request_block() } - #[cfg(feature = "async-std")] + #[cfg(feature = "async")] pub async fn async_new(request: &'a mut Request) -> error::Result> { - RawBuilder::new(request).raw_request_async_std().await + RawBuilder::new(request).raw_request_async().await } pub fn origin(&self) -> &Request { diff --git a/rttp_client/tests/support/mod.rs b/rttp_client/tests/support/mod.rs new file mode 100644 index 0000000..ab811c8 --- /dev/null +++ b/rttp_client/tests/support/mod.rs @@ -0,0 +1,79 @@ +use std::io::{Read, Write}; +use std::net::{SocketAddr, TcpListener}; +use std::thread::{self, JoinHandle}; + +pub fn spawn_http_server() -> (SocketAddr, JoinHandle<()>) { + spawn_http_server_count(1) +} + +pub fn spawn_http_server_count(count: usize) -> (SocketAddr, JoinHandle<()>) { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind http server"); + let addr = listener.local_addr().expect("local addr"); + let handle = thread::spawn(move || { + for _ in 0..count { + if let Ok((mut stream, _)) = listener.accept() { + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK"; + let _ = stream.write_all(response); + } + } + }); + (addr, handle) +} + +pub fn spawn_redirect_server() -> (SocketAddr, JoinHandle<()>) { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind redirect server"); + let addr = listener.local_addr().expect("redirect addr"); + let handle = thread::spawn(move || { + if let Ok((mut stream, _)) = listener.accept() { + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + let response = format!( + "HTTP/1.1 302 Found\r\nLocation: http://{}/final\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", + addr + ); + let _ = stream.write_all(response.as_bytes()); + } + + if let Ok((mut stream, _)) = listener.accept() { + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK"; + let _ = stream.write_all(response); + } + }); + (addr, handle) +} + +#[cfg(feature = "tls-rustls")] +pub fn spawn_tls_server() -> (SocketAddr, JoinHandle<()>) { + use rcgen::generate_simple_self_signed; + use rustls::{NoClientAuth, ServerConfig, ServerSession, Stream}; + use std::sync::Arc; + + let cert = generate_simple_self_signed(vec!["localhost".to_string()]).expect("generate cert"); + let cert_der = cert.serialize_der().expect("cert der"); + let key_der = cert.serialize_private_key_der(); + + let mut config = ServerConfig::new(NoClientAuth::new()); + config + .set_single_cert(vec![rustls::Certificate(cert_der)], rustls::PrivateKey(key_der)) + .expect("set cert"); + let config = Arc::new(config); + + let listener = TcpListener::bind("127.0.0.1:0").expect("bind tls server"); + let addr = listener.local_addr().expect("tls addr"); + let handle = thread::spawn(move || { + if let Ok((mut stream, _)) = listener.accept() { + let mut session = ServerSession::new(&config); + let mut tls = Stream::new(&mut session, &mut stream); + let mut buf = [0u8; 1024]; + let _ = tls.read(&mut buf); + let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK"; + let _ = tls.write_all(response); + let _ = tls.flush(); + } + }); + (addr, handle) +} diff --git a/rttp_client/tests/test_http_async.rs b/rttp_client/tests/test_http_async.rs index 174f10f..996422f 100644 --- a/rttp_client/tests/test_http_async.rs +++ b/rttp_client/tests/test_http_async.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "async-std")] -use async_std::task; +mod support; +use futures::executor::block_on; use rttp_client::types::Proxy; use rttp_client::HttpClient; @@ -9,46 +9,50 @@ fn client() -> HttpClient { } #[test] -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] fn test_async_http() { - task::block_on(async { + let (addr, _handle) = support::spawn_http_server(); + block_on(async { let response = client() .post() - .url("http://httpbin.org/post") - .form(("debug", "true", "name=Form&file=@cargo#../Cargo.toml")) + .url(format!("http://{}/post", addr)) + .form("debug=true") .rasync() .await; assert!(response.is_ok()); let response = response.unwrap(); - assert_eq!("httpbin.org", response.host()); + assert_eq!("127.0.0.1", response.host()); println!("{}", response); }); } #[test] -#[cfg(all( - feature = "async-std", - any(feature = "tls-rustls", feature = "tls-native") -))] +#[cfg(all(feature = "async", feature = "tls-rustls"))] fn test_async_https() { - task::block_on(async { + let (addr, _handle) = support::spawn_tls_server(); + block_on(async { let response = client() .post() - .url("https://httpbin.org/get") + .url(format!("https://{}/get", addr)) + .config( + rttp_client::Config::builder() + .verify_ssl_cert(false) + .verify_ssl_hostname(false), + ) .rasync() .await; assert!(response.is_ok()); let response = response.unwrap(); - assert_eq!("httpbin.org", response.host()); + assert_eq!("127.0.0.1", response.host()); println!("{}", response); }); } #[test] #[ignore] -#[cfg(feature = "async-std")] +#[cfg(feature = "async")] fn test_async_proxy_socks5() { - task::block_on(async { + block_on(async { let response = client() .get() .url("http://google.com") diff --git a/rttp_client/tests/test_http_basic.rs b/rttp_client/tests/test_http_basic.rs index 289a602..027d12e 100644 --- a/rttp_client/tests/test_http_basic.rs +++ b/rttp_client/tests/test_http_basic.rs @@ -1,3 +1,5 @@ +mod support; + use std::collections::HashMap; use rttp_client::types::{Para, Proxy, RoUrl}; @@ -9,30 +11,30 @@ fn client() -> HttpClient { #[test] fn test_http() { - let response = client().url("http://httpbin.org/get").emit(); - // let response = client().url("http://debian:1234/get").emit(); - println!("{:?}", response); + let (addr, _handle) = support::spawn_http_server(); + let response = client().url(format!("http://{}/get", addr)).emit(); assert!(response.is_ok()); let response = response.unwrap(); - assert_eq!("httpbin.org", response.host()); + assert_eq!("127.0.0.1", response.host()); println!("{}", response); } #[test] fn test_multi() { + let (addr, _handle) = support::spawn_http_server(); let mut para_map = HashMap::new(); para_map.insert("id", "1"); para_map.insert("relation", "eq"); let response = client() .method("post") - .url(RoUrl::with("http://httpbin.org?id=1&name=jack#none").para("name=Julia")) + .url(RoUrl::with(format!("http://{}/?id=1&name=jack#none", addr)).para("name=Julia")) .path("post") .header("User-Agent: Mozilla/5.0") - .header(&format!("Host:{}", "httpbin.org")) + .header("Host: localhost") .para("name=Chico") .para(&"name=æ–‡".to_string()) .para(para_map) - .form(("debug", "true", "name=Form&file=@cargo#../Cargo.toml")) + .form(("debug", "true", "name=Form")) .cookie("token=123234") .cookie("uid=abcdef") .content_type("application/x-www-form-urlencoded") @@ -41,39 +43,37 @@ fn test_multi() { .emit(); assert!(response.is_ok()); let response = response.unwrap(); - println!("{}", response); + assert_eq!("127.0.0.1", response.host()); } #[test] fn test_gzip() { + let (addr, _handle) = support::spawn_http_server(); let response = client() .get() - .url("http://httpbin.org/get") + .url(format!("http://{}/get", addr)) .header(("Accept-Encoding", "gzip, deflate")) .emit(); assert!(response.is_ok()); - let response = response.unwrap(); - println!("{}", response); } #[test] fn test_upload() { + let (addr, _handle) = support::spawn_http_server(); let response = client() .method("post") - .url("http://httpbin.org") - .path("post") - .form(("debug", "true", "name=Form&file=@cargo#../Cargo.toml")) + .url(format!("http://{}/post", addr)) + .form(("debug", "true", "name=Form")) .emit(); assert!(response.is_ok()); - let response = response.unwrap(); - println!("{}", response); } #[test] fn test_raw_json() { + let (addr, _handle) = support::spawn_http_server(); client() .method("post") - .url("http://httpbin.org/post?raw=json") + .url(format!("http://{}/post?raw=json", addr)) .para("name=Chico") .content_type("application/json") .raw(r#" {"from": "rttp"} "#) @@ -83,9 +83,10 @@ fn test_raw_json() { #[test] fn test_raw_form_urlencoded() { + let (addr, _handle) = support::spawn_http_server(); client() .method("post") - .url("http://httpbin.org/post") + .url(format!("http://{}/post", addr)) .para(Para::with_form("name", "Chico")) .raw("name=Nick&name=Wendy") .content_type("application/x-www-form-urlencoded") @@ -94,29 +95,28 @@ fn test_raw_form_urlencoded() { } #[test] -#[cfg(any(feature = "tls-rustls", feature = "tls-native"))] +#[cfg(feature = "tls-rustls")] fn test_https() { + let (addr, _handle) = support::spawn_tls_server(); let response = client() .get() - .url("https://bing.com") + .url(format!("https://{}/", addr)) + .config( + Config::builder() + .verify_ssl_cert(false) + .verify_ssl_hostname(false), + ) .para(Para::with_form("q", "News")) .emit(); assert!(response.is_ok()); - let response = response.unwrap(); - println!("{}", response); } #[test] -#[cfg(any(feature = "tls-native"))] -// feature = "tls-rustls", // rustls request httpbin.org will throw exception // "CloseNotify alert received" fn test_http_with_url() { + let (addr, _handle) = support::spawn_http_server(); client() .method("get") - .url( - RoUrl::with("https://httpbin.org") - .path("/get") - .para(("name", "Chico")), - ) + .url(RoUrl::with(format!("http://{}", addr)).path("/get").para(("name", "Chico"))) .emit() .expect("REQUEST FAIL"); } @@ -138,38 +138,39 @@ fn test_with_proxy_http() { fn test_with_proxy_socks5() { let response = client() .get() - .url("http://httpbin.org/get") + .url("http://google.com") .proxy(Proxy::socks5("127.0.0.1", 2801)) .emit(); assert!(response.is_ok()); let response = response.unwrap(); - assert_eq!("httpbin.org", response.host()); - println!("{}", response); + assert_eq!("google.com", response.host()); } #[test] fn test_auto_redirect() { + let (addr, _handle) = support::spawn_redirect_server(); let response = client() .config(Config::builder().auto_redirect(true)) .get() - .url("http://bing.com") + .url(format!("http://{}/", addr)) .emit(); assert!(response.is_ok()); let response = response.unwrap(); - assert_ne!("bing.com", response.host()); + assert!(response.ok()); } #[test] fn test_connection_closed() { + let (addr, _handle) = support::spawn_http_server_count(5); let mut client = client(); - let resp0 = client.url("http://httpbin.org/get").emit(); + let resp0 = client.url(format!("http://{}/get", addr)).emit(); assert!(resp0.is_ok()); - let resp1 = client.post().url("http://httpbin.org/post").emit(); + let resp1 = client.post().url(format!("http://{}/post", addr)).emit(); assert!(resp1.is_err()); - let resp2 = self::client().url("http://httpbin.org/get").emit(); + let resp2 = self::client().url(format!("http://{}/get", addr)).emit(); assert!(resp2.is_ok()); - let resp3 = self::client().post().url("http://httpbin.org/post").emit(); + let resp3 = self::client().post().url(format!("http://{}/post", addr)).emit(); assert!(resp3.is_ok()); - let resp4 = client.reset().post().url("http://httpbin.org/post").emit(); + let resp4 = client.reset().post().url(format!("http://{}/post", addr)).emit(); assert!(resp4.is_ok()); } diff --git a/rttp_client/tests/test_rustls.rs b/rttp_client/tests/test_rustls.rs index c024e3f..8f7a66c 100644 --- a/rttp_client/tests/test_rustls.rs +++ b/rttp_client/tests/test_rustls.rs @@ -1,5 +1,11 @@ -use std::io::{Read, stdout, Write}; +#[cfg(feature = "tls-rustls")] +mod support; + +#[cfg(feature = "tls-rustls")] +use std::io::{Read, Write}; +#[cfg(feature = "tls-rustls")] use std::net::TcpStream; +#[cfg(feature = "tls-rustls")] use std::sync::Arc; #[cfg(feature = "tls-rustls")] @@ -8,31 +14,50 @@ use rustls; use rustls::Session; #[cfg(feature = "tls-rustls")] use webpki; -#[cfg(feature = "tls-rustls")] -use webpki_roots; #[test] #[cfg(feature = "tls-rustls")] fn test_rustls() { + let (addr, _handle) = support::spawn_tls_server(); let mut config = rustls::ClientConfig::new(); - config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification)); - let dns_name = webpki::DNSNameRef::try_from_ascii_str("bing.com").unwrap(); + let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap(); let mut sess = rustls::ClientSession::new(&Arc::new(config), dns_name); - let mut sock = TcpStream::connect("bing.com:443").unwrap(); + let mut sock = TcpStream::connect(addr).unwrap(); let mut tls = rustls::Stream::new(&mut sess, &mut sock); - tls.write(concat!("GET / HTTP/1.1\r\n", - "Host: bing.com\r\n", - "Connection: close\r\n", - "Accept-Encoding: identity\r\n", - "\r\n") - .as_bytes()) - .unwrap(); - let ciphersuite = tls.sess.get_negotiated_ciphersuite().unwrap(); -// writeln!(&mut std::io::stderr(), "Current ciphersuite: {:?}", ciphersuite.suite).unwrap(); + tls.write_all( + concat!( + "GET / HTTP/1.1\r\n", + "Host: localhost\r\n", + "Connection: close\r\n", + "Accept-Encoding: identity\r\n", + "\r\n" + ) + .as_bytes(), + ) + .unwrap(); + let _ciphersuite = tls.sess.get_negotiated_ciphersuite().unwrap(); let mut plaintext = Vec::new(); tls.read_to_end(&mut plaintext).unwrap(); -// stdout().write_all(&plaintext).unwrap(); let text = String::from_utf8(plaintext).unwrap(); - println!("{}", text); + assert!(text.contains("200 OK")); +} + +#[cfg(feature = "tls-rustls")] +struct NoCertificateVerification; + +#[cfg(feature = "tls-rustls")] +impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef, + _ocsp_response: &[u8], + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } } From 5496be9de971a5098cb1baa8147f2da9ec13e644 Mon Sep 17 00:00:00 2001 From: fewensa <37804932+fewensa@users.noreply.github.com> Date: Sat, 7 Feb 2026 13:08:46 +0800 Subject: [PATCH 15/19] Migrate async client to futures and socket2 (#3) From d05861921cffa417bf4444dc1ad75e8049c47dc0 Mon Sep 17 00:00:00 2001 From: fewensa <37804932+fewensa@users.noreply.github.com> Date: Sat, 7 Feb 2026 14:28:48 +0800 Subject: [PATCH 16/19] Migrate to socket2: update features, async TLS handling, config tests, and CI (#7) * Update features and CI for socket2 migration * Fix rustls feature list * Bump checkout action to v6 * Fix CI branch and relax dependency specs --- .github/workflows/ci.yml | 14 ++--- rttp/Cargo.toml | 10 ++-- rttp/src/lib.rs | 7 +-- rttp_client/Cargo.toml | 19 +++---- rttp_client/src/config.rs | 57 ++++++++++++++++--- .../src/connection/async_connection.rs | 25 ++++---- .../src/connection/block_connection.rs | 2 - rttp_client/src/connection/connection.rs | 52 +++++++++++------ 8 files changed, 118 insertions(+), 68 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d34770d..1676678 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,22 +2,22 @@ name: CI on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: build: name: Build and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - default: true + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo build + uses: Swatinem/rust-cache@v2 - name: Format run: cargo fmt --all -- --check diff --git a/rttp/Cargo.toml b/rttp/Cargo.toml index 2ee2cb3..03c1320 100644 --- a/rttp/Cargo.toml +++ b/rttp/Cargo.toml @@ -15,7 +15,7 @@ include = [ ] readme = "README.md" -edition = "2018" +edition = "2021" [dependencies] @@ -27,10 +27,11 @@ rttp_client = { optional = true, path = "../rttp_client", features = [ "tls-nati [features] default = [] -all = ["rttp_client"] client = ["rttp_client"] -#client_tls_native = ["rttp_client"] -#client_tls_rustls = ["rttp_client"] +async = ["client", "rttp_client/async"] +tls-native = ["client", "rttp_client/tls-native"] +tls-rustls = ["client", "rttp_client/tls-rustls"] +all = ["client", "async", "tls-native", "tls-rustls"] #[target.'cfg(feature = "all")'.dependencies] @@ -44,4 +45,3 @@ client = ["rttp_client"] # #[target.'cfg(feature = "client_tls_rustls")'.dependencies] #rttp_client = { version = "=0.1.0", path = "../rttp_client", optional = true, default-features = false, features = [ "tls-rustls" ] } - diff --git a/rttp/src/lib.rs b/rttp/src/lib.rs index 90060fe..61d9050 100644 --- a/rttp/src/lib.rs +++ b/rttp/src/lib.rs @@ -1,12 +1,7 @@ pub struct Http {} impl Http { - #[cfg(any( - feature = "all", - feature = "client", - feature = "client_tls_rustls", - feature = "client_tls_native", - ))] + #[cfg(feature = "client")] pub fn client() -> rttp_client::HttpClient { rttp_client::HttpClient::new() } diff --git a/rttp_client/Cargo.toml b/rttp_client/Cargo.toml index 06e2093..e34c154 100644 --- a/rttp_client/Cargo.toml +++ b/rttp_client/Cargo.toml @@ -15,7 +15,7 @@ include = [ ] readme = "README.md" -edition = "2018" +edition = "2021" [dependencies] tracing = "0.1" @@ -28,27 +28,26 @@ percent-encoding = "2" mime = "0.3" mime_guess = "2" -rand = "0.7" +rand = "0.8" socks = "0.3" -base64 = "0.11" +base64 = "0.22" flate2 = "1.0" -httpdate = "0.3" +httpdate = "1.0" native-tls = { optional = true, version = "0.2" } -async-native-tls = { optional = true, version = "0.4" } +async-native-tls = { optional = true, version = "0.5" } -rustls = { optional = true, version = "0.16" } -webpki-roots = { optional = true, version = "0.18" } -webpki = { optional = true, version = "0.21" } -async-rustls = { optional = true, version = "0.2" } +rustls = { optional = true, version = "0.23" } +webpki-roots = { optional = true, version = "0.26" } +async-rustls = { optional = true, version = "0.4" } [features] default = [] async = [] tls-native = ["native-tls", "async-native-tls"] -tls-rustls = ["rustls", "webpki", "webpki-roots", "async-rustls"] +tls-rustls = ["rustls", "webpki-roots", "async-rustls"] ## # warning: /data/rttp/rttp_client/Cargo.toml: Found `feature = ...` in `target.'cfg(...)'.dependencies`. diff --git a/rttp_client/src/config.rs b/rttp_client/src/config.rs index c33c494..f4a52aa 100644 --- a/rttp_client/src/config.rs +++ b/rttp_client/src/config.rs @@ -4,7 +4,7 @@ pub struct Config { write_timeout: u64, auto_redirect: bool, max_redirect: u32, - verify_ssl_hostname: bool, + verify_ssl_hostname: bool, verify_ssl_cert: bool, } @@ -38,7 +38,7 @@ impl Config { pub fn max_redirect(&self) -> u32 { self.max_redirect } - pub fn verify_ssl_cert(&self) -> bool { + pub fn verify_ssl_cert(&self) -> bool { self.verify_ssl_cert } pub fn verify_ssl_hostname(&self) -> bool { @@ -55,11 +55,11 @@ impl ConfigBuilder { pub fn new() -> Self { Self { config: Config { - read_timeout: 5000, - write_timeout: 5000, + read_timeout: 10000, + write_timeout: 10000, auto_redirect: false, - max_redirect: 3, - verify_ssl_hostname: true, + max_redirect: 0, + verify_ssl_hostname: true, verify_ssl_cert: true, }, } @@ -85,7 +85,7 @@ impl ConfigBuilder { self.config.max_redirect = max_redirect; self } - pub fn verify_ssl_hostname(&mut self, verify_ssl_hostname: bool) -> &mut Self { + pub fn verify_ssl_hostname(&mut self, verify_ssl_hostname: bool) -> &mut Self { self.config.verify_ssl_hostname = verify_ssl_hostname; self } @@ -106,3 +106,46 @@ impl AsRef for ConfigBuilder { &self.config } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn builder_updates_values() { + let config = Config::builder() + .read_timeout(1234) + .write_timeout(4321) + .auto_redirect(true) + .max_redirect(5) + .verify_ssl_hostname(false) + .verify_ssl_cert(false) + .build(); + + assert_eq!(config.read_timeout(), 1234); + assert_eq!(config.write_timeout(), 4321); + assert!(config.auto_redirect()); + assert_eq!(config.max_redirect(), 5); + assert!(!config.verify_ssl_hostname()); + assert!(!config.verify_ssl_cert()); + } + + #[test] + fn default_config_matches_builder_defaults() { + let default_config = Config::default(); + let builder_config = Config::builder().build(); + + assert_eq!(default_config.read_timeout(), builder_config.read_timeout()); + assert_eq!(default_config.write_timeout(), builder_config.write_timeout()); + assert_eq!(default_config.auto_redirect(), builder_config.auto_redirect()); + assert_eq!(default_config.max_redirect(), builder_config.max_redirect()); + assert_eq!( + default_config.verify_ssl_hostname(), + builder_config.verify_ssl_hostname() + ); + assert_eq!( + default_config.verify_ssl_cert(), + builder_config.verify_ssl_cert() + ); + } +} diff --git a/rttp_client/src/connection/async_connection.rs b/rttp_client/src/connection/async_connection.rs index 471f89f..b882e91 100644 --- a/rttp_client/src/connection/async_connection.rs +++ b/rttp_client/src/connection/async_connection.rs @@ -96,21 +96,20 @@ impl<'a> AsyncConnection<'a> { self.async_read_stream(url, stream).await } - #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))] - async fn async_send_https(&self, _url: &Url, _stream: TcpStream) -> error::Result> { - Err(error::no_request_features( - "Not have any tls features, Can't request a https url", - )) - } - - #[cfg(feature = "tls-native")] async fn async_send_https(&self, url: &Url, mut stream: TcpStream) -> error::Result> { - self.conn.block_send_https(url, &mut stream) - } + #[cfg(any(feature = "tls-native", feature = "tls-rustls"))] + { + return self.conn.block_send_https(url, &mut stream); + } - #[cfg(feature = "tls-rustls")] - async fn async_send_https(&self, url: &Url, mut stream: TcpStream) -> error::Result> { - self.conn.block_send_https(url, &mut stream) + #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))] + { + let _ = url; + let _ = stream; + return Err(error::no_request_features( + "Not have any tls features, Can't request a https url", + )); + } } } diff --git a/rttp_client/src/connection/block_connection.rs b/rttp_client/src/connection/block_connection.rs index 6441916..ec3ab39 100644 --- a/rttp_client/src/connection/block_connection.rs +++ b/rttp_client/src/connection/block_connection.rs @@ -2,8 +2,6 @@ use std::io::{Read, Write}; #[cfg(feature = "tls-native")] use native_tls::TlsConnector; -#[cfg(feature = "tls-rustls")] -use rustls::{Session, TLSError}; use socks::{Socks4Stream, Socks5Stream}; use url::Url; diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index 6551173..c1c85e7 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -2,6 +2,8 @@ use std::{io, net::ToSocketAddrs, time}; use socket2::{Domain, Protocol, Socket, Type}; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; #[cfg(feature = "tls-rustls")] use std::sync::Arc; @@ -12,19 +14,27 @@ use crate::request::{RawRequest, RequestBody}; use crate::types::{Proxy, RoUrl, ToUrl}; use crate::{error, Config}; +#[cfg(feature = "tls-rustls")] +use rustls::client::danger::{ServerCertVerified, ServerCertVerifier}; +#[cfg(feature = "tls-rustls")] +use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +#[cfg(feature = "tls-rustls")] +use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned}; + #[cfg(feature = "tls-rustls")] struct NoCertificateVerification; #[cfg(feature = "tls-rustls")] -impl rustls::ServerCertVerifier for NoCertificateVerification { +impl ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, _ocsp_response: &[u8], - ) -> Result { - Ok(rustls::ServerCertVerified::assertion()) + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) } } @@ -112,7 +122,7 @@ impl<'a> Connection<'a> { } else { format!("{}:", username) }; - let auth = base64::encode(&auth); + let auth = STANDARD.encode(auth.as_bytes()); proxy_header.push_str(&format!("Authorization: Basic {}\r\n", auth)); } @@ -259,27 +269,33 @@ impl<'a> Connection<'a> { self.block_write_stream(&mut ssl_stream)?; self.block_read_stream(url, &mut ssl_stream) } -#[cfg(feature = "tls-rustls")] + #[cfg(feature = "tls-rustls")] pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> where S: io::Read + io::Write, { let config = self.config(); - let mut rustls_config = rustls::ClientConfig::new(); + let mut root_store = RootCertStore::empty(); if config.verify_ssl_cert() { - rustls_config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + } + + let builder = ClientConfig::builder(); + let rustls_config = if config.verify_ssl_cert() { + builder.with_root_certificates(root_store).with_no_client_auth() } else { - rustls_config + builder .dangerous() - .set_certificate_verifier(Arc::new(NoCertificateVerification)); - } + .with_custom_certificate_verifier(Arc::new(NoCertificateVerification)) + .with_no_client_auth() + }; let rc_config = Arc::new(rustls_config); let host = self.host(url)?; - let dns_name = webpki::DNSNameRef::try_from_ascii_str(&host[..]).unwrap(); - let mut client = rustls::ClientSession::new(&rc_config, dns_name); - let mut tls = rustls::Stream::new(&mut client, stream); + let server_name = ServerName::try_from(host.as_str()) + .map_err(|_| error::bad_ssl(format!("Invalid server name: {}", host)))?; + let client = + ClientConnection::new(rc_config, server_name).map_err(|e| error::bad_ssl(e.to_string()))?; + let mut tls = StreamOwned::new(client, stream); self.block_write_stream(&mut tls)?; self.block_read_stream(url, &mut tls) From 5234308d73ee259087b8c78d76375e72e99619cd Mon Sep 17 00:00:00 2001 From: fewensa <37804932+fewensa@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:10:49 +0800 Subject: [PATCH 17/19] Restore checkout v6 in CI (#8) --- rttp_client/src/connection/connection.rs | 24 ++++++++++++++++++++++-- rttp_client/src/lib.rs | 3 ++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index c1c85e7..703d24b 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -251,8 +251,27 @@ impl<'a> Connection<'a> { )); } - #[cfg(feature = "tls-native")] + #[cfg(any(feature = "tls-native", feature = "tls-rustls"))] pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> + where + S: io::Read + io::Write, + { + #[cfg(all(feature = "tls-native", feature = "tls-rustls"))] + { + return self.block_send_https_rustls(url, stream); + } + #[cfg(all(feature = "tls-native", not(feature = "tls-rustls")))] + { + return self.block_send_https_native(url, stream); + } + #[cfg(all(feature = "tls-rustls", not(feature = "tls-native")))] + { + return self.block_send_https_rustls(url, stream); + } + } + + #[cfg(feature = "tls-native")] + fn block_send_https_native(&self, url: &Url, stream: &mut S) -> error::Result> where S: io::Read + io::Write, { @@ -269,8 +288,9 @@ impl<'a> Connection<'a> { self.block_write_stream(&mut ssl_stream)?; self.block_read_stream(url, &mut ssl_stream) } + #[cfg(feature = "tls-rustls")] - pub fn block_send_https(&self, url: &Url, stream: &mut S) -> error::Result> + fn block_send_https_rustls(&self, url: &Url, stream: &mut S) -> error::Result> where S: io::Read + io::Write, { diff --git a/rttp_client/src/lib.rs b/rttp_client/src/lib.rs index 63b689f..f857bad 100644 --- a/rttp_client/src/lib.rs +++ b/rttp_client/src/lib.rs @@ -50,7 +50,8 @@ //! ``` //! //! *Important* -//! `tls-native` and `tls-rustls` only support choose on features, do not same to use. +//! `tls-native` and `tls-rustls` can be enabled together; when both are enabled, +//! the client prefers the rustls implementation for HTTPS requests. //! //! ## Examples //! From 1d6f6f06e2e6fa33a880a305a1848fc13a846391 Mon Sep 17 00:00:00 2001 From: fewensa <37804932+fewensa@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:32:18 +0800 Subject: [PATCH 18/19] Fix build errors: socket2 API, rustls trait impl, async field access, Alphanumeric type (#9) - connection/mod.rs: remove incorrect #[cfg(feature = "async")] guard on block_connection - connection/connection.rs: replace socket.into_tcp_stream() with std::net::TcpStream::from(socket) - connection/connection.rs: implement missing ServerCertVerifier methods and #[derive(Debug)] for NoCertificateVerification - connection/connection.rs: call .to_owned() on ServerName to fix lifetime issue - connection/connection.rs: use Display instead of Debug for native-tls HandshakeError - request/builder/build_body_async.rs: use self.request field directly instead of self.request() method - request/builder/form_data.rs: cast u8 to char for Alphanumeric distribution output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- rttp_client/src/connection/connection.rs | 46 +++++++++++++++++-- rttp_client/src/connection/mod.rs | 1 - .../src/request/builder/build_body_async.rs | 6 +-- rttp_client/src/request/builder/form_data.rs | 2 +- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index 703d24b..aa9cd85 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -15,13 +15,14 @@ use crate::types::{Proxy, RoUrl, ToUrl}; use crate::{error, Config}; #[cfg(feature = "tls-rustls")] -use rustls::client::danger::{ServerCertVerified, ServerCertVerifier}; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; #[cfg(feature = "tls-rustls")] use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; #[cfg(feature = "tls-rustls")] -use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned}; +use rustls::{ClientConfig, ClientConnection, DigitallySignedStruct, RootCertStore, SignatureScheme, StreamOwned}; #[cfg(feature = "tls-rustls")] +#[derive(Debug)] struct NoCertificateVerification; #[cfg(feature = "tls-rustls")] @@ -36,6 +37,40 @@ impl ServerCertVerifier for NoCertificateVerification { ) -> Result { Ok(ServerCertVerified::assertion()) } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + SignatureScheme::RSA_PKCS1_SHA1, + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + ] + } } pub struct Connection<'a> { @@ -163,7 +198,7 @@ impl<'a> Connection<'a> { continue; } - let stream = socket.into_tcp_stream(); + let stream = std::net::TcpStream::from(socket); return Ok(stream); } @@ -283,7 +318,7 @@ impl<'a> Connection<'a> { .map_err(error::request)?; let mut ssl_stream = connector .connect(&self.host(url)?[..], stream) - .map_err(|e| error::bad_ssl(format!("Native tls error: {:?}", e)))?; + .map_err(|_| error::bad_ssl("Native tls handshake error"))?; self.block_write_stream(&mut ssl_stream)?; self.block_read_stream(url, &mut ssl_stream) @@ -312,7 +347,8 @@ impl<'a> Connection<'a> { let rc_config = Arc::new(rustls_config); let host = self.host(url)?; let server_name = ServerName::try_from(host.as_str()) - .map_err(|_| error::bad_ssl(format!("Invalid server name: {}", host)))?; + .map_err(|_| error::bad_ssl(format!("Invalid server name: {}", host)))? + .to_owned(); let client = ClientConnection::new(rc_config, server_name).map_err(|e| error::bad_ssl(e.to_string()))?; let mut tls = StreamOwned::new(client, stream); diff --git a/rttp_client/src/connection/mod.rs b/rttp_client/src/connection/mod.rs index 92931fc..9752b40 100644 --- a/rttp_client/src/connection/mod.rs +++ b/rttp_client/src/connection/mod.rs @@ -4,7 +4,6 @@ pub use self::block_connection::*; #[cfg(feature = "async")] mod async_connection; -#[cfg(feature = "async")] mod block_connection; mod connection; mod connection_reader; diff --git a/rttp_client/src/request/builder/build_body_async.rs b/rttp_client/src/request/builder/build_body_async.rs index 4344e11..cb312ea 100644 --- a/rttp_client/src/request/builder/build_body_async.rs +++ b/rttp_client/src/request/builder/build_body_async.rs @@ -14,7 +14,7 @@ impl<'a> RawBuilder<'a> { return Ok(Some(body)); } - let formdatas = self.request().formdatas(); + let formdatas = self.request.formdatas(); // form-data if !formdatas.is_empty() { @@ -29,8 +29,8 @@ impl<'a> RawBuilder<'a> { let mut disposition = fdw.disposition; let mut buffer = fdw.buffer; - let traditional = self.request().traditional(); - let formdatas = self.request().formdatas(); + let traditional = self.request.traditional(); + let formdatas = self.request.formdatas(); for formdata in formdatas { let field_name = if formdata.array() && !traditional { format!("{}[]", formdata.name()) diff --git a/rttp_client/src/request/builder/form_data.rs b/rttp_client/src/request/builder/form_data.rs index cbba2a5..692ca69 100644 --- a/rttp_client/src/request/builder/form_data.rs +++ b/rttp_client/src/request/builder/form_data.rs @@ -15,7 +15,7 @@ impl Disposition { pub fn new() -> Self { let mut rng = rand::thread_rng(); let boundary: String = std::iter::repeat(()) - .map(|()| rng.sample(rand::distributions::Alphanumeric)) + .map(|()| rng.sample(rand::distributions::Alphanumeric) as char) .take(20) .collect(); Self { boundary } From a54f8a359243f8c63c2a13b77e17a4f14db19763 Mon Sep 17 00:00:00 2001 From: fewensa <37804932+fewensa@users.noreply.github.com> Date: Fri, 13 Mar 2026 23:01:17 +0800 Subject: [PATCH 19/19] Fix refactor dependency compatibility (#11) * Fix refactor dependency compatibility\n\nCo-Authored-By: Paperclip * Run CI for refactor PRs\n\nCo-Authored-By: Paperclip * Apply rustfmt for CI\n\nCo-Authored-By: Paperclip * Fix clippy warnings on refactor branch\n\nCo-Authored-By: Paperclip --- .github/workflows/ci.yml | 4 +- rttp/Cargo.toml | 1 + rttp/tests/test_client.rs | 31 ++++--- rttp_client/src/config.rs | 27 +++++- .../src/connection/async_connection.rs | 28 ++++-- rttp_client/src/connection/connection.rs | 32 ++++--- .../src/connection/connection_reader.rs | 7 +- rttp_client/src/connection/mod.rs | 2 + rttp_client/src/error.rs | 28 +----- .../src/request/builder/build_body_async.rs | 5 +- .../src/request/builder/build_body_block.rs | 8 +- .../src/request/builder/build_header.rs | 21 ++--- rttp_client/src/request/builder/form_data.rs | 6 +- rttp_client/src/request/mod.rs | 2 + rttp_client/src/request/raw_request.rs | 4 +- rttp_client/src/response/mod.rs | 3 +- rttp_client/src/response/raw_response.rs | 17 +--- rttp_client/src/response/response.rs | 36 ++++--- rttp_client/src/types/cookie.rs | 85 +++++++++++------ rttp_client/src/types/form_data.rs | 18 ++-- rttp_client/src/types/header.rs | 33 +++---- rttp_client/src/types/mod.rs | 21 ++--- rttp_client/src/types/para.rs | 9 +- rttp_client/src/types/proxy.rs | 62 +++++++++---- rttp_client/src/types/status.rs | 6 +- rttp_client/src/types/type_helper.rs | 7 +- rttp_client/src/types/url.rs | 21 ++--- rttp_client/tests/support/mod.rs | 74 ++++++++++++--- rttp_client/tests/test_http_basic.rs | 17 +++- rttp_client/tests/test_httpdate.rs | 12 ++- rttp_client/tests/test_response.rs | 5 +- rttp_client/tests/test_rustls.rs | 93 +++++++++++++------ rttp_client/tests/test_url.rs | 3 +- 33 files changed, 446 insertions(+), 282 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1676678..e158a52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ main ] + branches: [ main, refactor ] pull_request: - branches: [ main ] + branches: [ main, refactor ] jobs: build: diff --git a/rttp/Cargo.toml b/rttp/Cargo.toml index 03c1320..38140e2 100644 --- a/rttp/Cargo.toml +++ b/rttp/Cargo.toml @@ -22,6 +22,7 @@ edition = "2021" rttp_client = { optional = true, path = "../rttp_client", features = [ "tls-native", "async" ] } [dev-dependencies] +async-std = { version = "1" } [features] diff --git a/rttp/tests/test_client.rs b/rttp/tests/test_client.rs index 55ba381..07c6e5f 100644 --- a/rttp/tests/test_client.rs +++ b/rttp/tests/test_client.rs @@ -1,32 +1,31 @@ -use rttp::Http; - #[test] #[cfg(any(feature = "all", feature = "client"))] fn test_client_http() { - let response = Http::client() - .url("http://httpbin.org/get") - .emit(); + let response = rttp::Http::client().url("http://httpbin.org/get").emit(); assert!(response.is_ok()); let response = response.unwrap(); println!("{}", response); } #[test] -#[cfg(any(feature = "all", feature = "client", feature = "client_tls_native", feature = "client_tls_rustls"))] +#[cfg(any( + feature = "all", + feature = "client", + feature = "tls-native", + feature = "tls-rustls" +))] fn test_client_https() { - let response = Http::client() - .url("https://bing.com") - .emit(); + let response = rttp::Http::client().url("https://httpbin.org/get").emit(); assert!(response.is_ok()); let response = response.unwrap(); println!("{}", response); } #[test] -#[cfg(any(feature = "all", feature = "client"))] +#[cfg(any(feature = "all", feature = "async"))] fn test_client_async_http() { async_std::task::block_on(async { - let response = Http::client() + let response = rttp::Http::client() .post() .url("http://httpbin.org/post") .form(("debug", "true", "name=Form&file=@cargo#../Cargo.toml")) @@ -40,10 +39,15 @@ fn test_client_async_http() { } #[test] -#[cfg(any(feature = "all", feature = "client", feature = "client_tls_native", feature = "client_tls_rustls"))] +#[cfg(any( + feature = "all", + feature = "async", + feature = "tls-native", + feature = "tls-rustls" +))] fn test_client_async_https() { async_std::task::block_on(async { - let response = Http::client() + let response = rttp::Http::client() .post() .url("https://httpbin.org/get") .rasync() @@ -54,4 +58,3 @@ fn test_client_async_https() { println!("{}", response); }); } - diff --git a/rttp_client/src/config.rs b/rttp_client/src/config.rs index f4a52aa..cebc1c6 100644 --- a/rttp_client/src/config.rs +++ b/rttp_client/src/config.rs @@ -51,6 +51,12 @@ pub struct ConfigBuilder { config: Config, } +impl Default for ConfigBuilder { + fn default() -> Self { + Self::new() + } +} + impl ConfigBuilder { pub fn new() -> Self { Self { @@ -79,6 +85,9 @@ impl ConfigBuilder { } pub fn auto_redirect(&mut self, auto_redirect: bool) -> &mut Self { self.config.auto_redirect = auto_redirect; + if auto_redirect && self.config.max_redirect == 0 { + self.config.max_redirect = 5; + } self } pub fn max_redirect(&mut self, max_redirect: u32) -> &mut Self { @@ -136,8 +145,14 @@ mod tests { let builder_config = Config::builder().build(); assert_eq!(default_config.read_timeout(), builder_config.read_timeout()); - assert_eq!(default_config.write_timeout(), builder_config.write_timeout()); - assert_eq!(default_config.auto_redirect(), builder_config.auto_redirect()); + assert_eq!( + default_config.write_timeout(), + builder_config.write_timeout() + ); + assert_eq!( + default_config.auto_redirect(), + builder_config.auto_redirect() + ); assert_eq!(default_config.max_redirect(), builder_config.max_redirect()); assert_eq!( default_config.verify_ssl_hostname(), @@ -148,4 +163,12 @@ mod tests { builder_config.verify_ssl_cert() ); } + + #[test] + fn enabling_redirects_sets_a_reasonable_default_limit() { + let config = Config::builder().auto_redirect(true).build(); + + assert!(config.auto_redirect()); + assert_eq!(config.max_redirect(), 5); + } } diff --git a/rttp_client/src/connection/async_connection.rs b/rttp_client/src/connection/async_connection.rs index b882e91..636a0a2 100644 --- a/rttp_client/src/connection/async_connection.rs +++ b/rttp_client/src/connection/async_connection.rs @@ -1,8 +1,8 @@ use std::net::TcpStream; -use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, AllowStdIo}; -use std::io::{Read, Write}; +use futures::io::{AllowStdIo, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use socks::{Socks4Stream, Socks5Stream}; +use std::io::{Read, Write}; use url::Url; use crate::connection::connection::Connection; @@ -49,9 +49,15 @@ impl<'a> AsyncConnection<'a> { let header = self.conn.header(); let body = self.conn.body(); - stream.write_all(header.as_bytes()).await.map_err(error::request)?; + stream + .write_all(header.as_bytes()) + .await + .map_err(error::request)?; if let Some(body) = body { - stream.write_all(body.bytes()).await.map_err(error::request)?; + stream + .write_all(body.bytes()) + .await + .map_err(error::request)?; } stream.flush().await.map_err(error::request)?; @@ -63,7 +69,10 @@ impl<'a> AsyncConnection<'a> { S: AsyncRead + Unpin, { let mut buffer = Vec::new(); - stream.read_to_end(&mut buffer).await.map_err(error::request)?; + stream + .read_to_end(&mut buffer) + .await + .map_err(error::request)?; Ok(buffer) } } @@ -130,7 +139,9 @@ impl<'a> AsyncConnection<'a> { let addr = format!("{}:{}", proxy.host(), proxy.port()); let mut stream = self.async_tcp_stream(&addr).await?; - stream.write_all(connect_header.as_bytes()).map_err(error::request)?; + stream + .write_all(connect_header.as_bytes()) + .map_err(error::request)?; stream.flush().map_err(error::request)?; // HTTP/1.1 200 Connection Established @@ -141,7 +152,10 @@ impl<'a> AsyncConnection<'a> { Ok(r) => r, Err(_) => return Err(error::bad_proxy("parse proxy server response error.")), }; - if !res_s.to_ascii_lowercase().contains("connection established") { + if !res_s + .to_ascii_lowercase() + .contains("connection established") + { return Err(error::bad_proxy("Proxy server response error.")); } diff --git a/rttp_client/src/connection/connection.rs b/rttp_client/src/connection/connection.rs index aa9cd85..b5d4702 100644 --- a/rttp_client/src/connection/connection.rs +++ b/rttp_client/src/connection/connection.rs @@ -19,7 +19,10 @@ use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, Server #[cfg(feature = "tls-rustls")] use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; #[cfg(feature = "tls-rustls")] -use rustls::{ClientConfig, ClientConnection, DigitallySignedStruct, RootCertStore, SignatureScheme, StreamOwned}; +use rustls::{ + ClientConfig, ClientConnection, DigitallySignedStruct, RootCertStore, SignatureScheme, + StreamOwned, +}; #[cfg(feature = "tls-rustls")] #[derive(Debug)] @@ -85,7 +88,7 @@ impl<'a> Connection<'a> { #[allow(dead_code)] impl<'a> Connection<'a> { - pub fn request(&self) -> &RawRequest { + pub fn request(&self) -> &RawRequest<'_> { &self.request } pub fn rourl(&self) -> &RoUrl { @@ -202,9 +205,9 @@ impl<'a> Connection<'a> { return Ok(stream); } - Err(error::request(last_err.unwrap_or_else(|| { - io::Error::new(io::ErrorKind::Other, "failed to connect") - }))) + Err(error::request( + last_err.unwrap_or_else(|| io::Error::other("failed to connect")), + )) } pub fn block_write_stream(&self, stream: &mut S) -> error::Result<()> @@ -264,7 +267,7 @@ impl<'a> Connection<'a> { match url.scheme() { "http" => self.block_send_http(url, stream), "https" => self.block_send_https(url, stream), - _ => return Err(error::url_bad_scheme(url.clone())), + _ => Err(error::url_bad_scheme(url.clone())), } } @@ -281,9 +284,9 @@ impl<'a> Connection<'a> { where S: io::Read + io::Write, { - return Err(error::no_request_features( + Err(error::no_request_features( "Not have any tls features, Can't request a https url", - )); + )) } #[cfg(any(feature = "tls-native", feature = "tls-rustls"))] @@ -337,7 +340,9 @@ impl<'a> Connection<'a> { let builder = ClientConfig::builder(); let rustls_config = if config.verify_ssl_cert() { - builder.with_root_certificates(root_store).with_no_client_auth() + builder + .with_root_certificates(root_store) + .with_no_client_auth() } else { builder .dangerous() @@ -346,9 +351,12 @@ impl<'a> Connection<'a> { }; let rc_config = Arc::new(rustls_config); let host = self.host(url)?; - let server_name = ServerName::try_from(host.as_str()) - .map_err(|_| error::bad_ssl(format!("Invalid server name: {}", host)))? - .to_owned(); + let server_name = match host.parse::() { + Ok(ip) => ServerName::IpAddress(ip.into()), + Err(_) => ServerName::try_from(host.as_str()) + .map_err(|_| error::bad_ssl(format!("Invalid server name: {}", host)))? + .to_owned(), + }; let client = ClientConnection::new(rc_config, server_name).map_err(|e| error::bad_ssl(e.to_string()))?; let mut tls = StreamOwned::new(client, stream); diff --git a/rttp_client/src/connection/connection_reader.rs b/rttp_client/src/connection/connection_reader.rs index 5df6507..a0efa07 100644 --- a/rttp_client/src/connection/connection_reader.rs +++ b/rttp_client/src/connection/connection_reader.rs @@ -9,15 +9,12 @@ use crate::types::RoUrl; #[allow(dead_code)] pub struct ConnectionReader<'a> { url: &'a Url, - reader: Box<&'a mut dyn io::Read>, + reader: &'a mut dyn io::Read, } impl<'a> ConnectionReader<'a> { pub fn new(url: &'a Url, reader: &'a mut dyn io::Read) -> ConnectionReader<'a> { - Self { - url, - reader: Box::new(reader), - } + Self { url, reader } } pub fn binary(&mut self) -> error::Result> { diff --git a/rttp_client/src/connection/mod.rs b/rttp_client/src/connection/mod.rs index 9752b40..827ebb1 100644 --- a/rttp_client/src/connection/mod.rs +++ b/rttp_client/src/connection/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::module_inception)] + #[cfg(feature = "async")] pub use self::async_connection::*; pub use self::block_connection::*; diff --git a/rttp_client/src/error.rs b/rttp_client/src/error.rs index d3ccf9a..917ec2d 100644 --- a/rttp_client/src/error.rs +++ b/rttp_client/src/error.rs @@ -44,26 +44,17 @@ impl Error { /// Returns true if the error is from a type Builder. pub fn is_builder(&self) -> bool { - match self.inner.kind { - Kind::Builder => true, - _ => false, - } + matches!(self.inner.kind, Kind::Builder) } /// Returns true if the error is from a `RedirectPolicy`. pub fn is_redirect(&self) -> bool { - match self.inner.kind { - Kind::Redirect => true, - _ => false, - } + matches!(self.inner.kind, Kind::Redirect) } /// Returns true if the error is from `Response::error_for_status`. pub fn is_status(&self) -> bool { - match self.inner.kind { - Kind::Status(_) => true, - _ => false, - } + matches!(self.inner.kind, Kind::Status(_)) } /// Returns true if the error is related to a timeout. @@ -88,7 +79,7 @@ impl Error { #[allow(unused)] pub(crate) fn into_io(self) -> io::Error { - io::Error::new(io::ErrorKind::Other, self) + io::Error::other(self) } } @@ -287,9 +278,6 @@ pub(crate) fn decode_io(e: io::Error) -> Error { #[derive(Debug)] pub(crate) struct TimedOut; -#[derive(Debug)] -pub(crate) struct BlockingClientInAsyncContext; - impl fmt::Display for TimedOut { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("operation timed out") @@ -297,11 +285,3 @@ impl fmt::Display for TimedOut { } impl StdError for TimedOut {} - -impl fmt::Display for BlockingClientInAsyncContext { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("blocking Client used inside a Future context") - } -} - -impl StdError for BlockingClientInAsyncContext {} diff --git a/rttp_client/src/request/builder/build_body_async.rs b/rttp_client/src/request/builder/build_body_async.rs index cb312ea..959de67 100644 --- a/rttp_client/src/request/builder/build_body_async.rs +++ b/rttp_client/src/request/builder/build_body_async.rs @@ -9,7 +9,10 @@ use crate::types::{FormDataType, RoUrl}; #[cfg(feature = "async")] impl<'a> RawBuilder<'a> { - pub async fn build_body_async(&mut self, rourl: &mut RoUrl) -> error::Result> { + pub async fn build_body_async( + &mut self, + rourl: &mut RoUrl, + ) -> error::Result> { if let Some(body) = self.build_body_common(rourl)? { return Ok(Some(body)); } diff --git a/rttp_client/src/request/builder/build_body_block.rs b/rttp_client/src/request/builder/build_body_block.rs index d1358a7..d293fc5 100644 --- a/rttp_client/src/request/builder/build_body_block.rs +++ b/rttp_client/src/request/builder/build_body_block.rs @@ -49,7 +49,7 @@ impl<'a> RawBuilder<'a> { .map_err(error::builder)?, ); - let body = raw.clone().map(|raw| RequestBody::with_text(raw)); + let body = raw.clone().map(RequestBody::with_text); if !paras.is_empty() { for para in paras { rourl.para(para); @@ -98,7 +98,7 @@ impl<'a> RawBuilder<'a> { return self.build_body_with_form_data_block(); } - return Ok(None); + Ok(None) } fn build_body_with_form_data_block(&mut self) -> error::Result> { @@ -118,7 +118,7 @@ impl<'a> RawBuilder<'a> { let file = formdata .file() .clone() - .ok_or(error::builder_with_message(&format!( + .ok_or(error::builder_with_message(format!( "Missing file path for field: {}", formdata.name() )))?; @@ -244,7 +244,7 @@ impl<'a> RawBuilder<'a> { value )); if i + 1 < len { - body.push_str("&"); + body.push('&'); } } let req_body = RequestBody::with_text(body); diff --git a/rttp_client/src/request/builder/build_header.rs b/rttp_client/src/request/builder/build_header.rs index 9ec2e37..efd5a2e 100644 --- a/rttp_client/src/request/builder/build_header.rs +++ b/rttp_client/src/request/builder/build_header.rs @@ -49,7 +49,7 @@ impl<'a> RawBuilder<'a> { return url.as_str().to_owned(); } - let mut result = format!("{}", url.path()); + let mut result = url.path().to_string(); if let Some(query) = url.query() { result.push_str(&format!("?{}", query)); } @@ -64,8 +64,7 @@ impl<'a> RawBuilder<'a> { .request .headers() .iter() - .find(|item| item.name().eq_ignore_ascii_case(name.as_ref())) - .is_some() + .any(|item| item.name().eq_ignore_ascii_case(name.as_ref())) } } @@ -77,7 +76,7 @@ impl<'a> RawBuilder<'a> { let host = url.host_str().ok_or(error::url_bad_host(url.clone()))?; let header = match url.port() { Some(port) => Header::new("Host", format!("{}:{}", host, port)), - None => Header::new("Host", format!("{}", host)), + None => Header::new("Host", host), }; self.request.headers_mut().push(header); @@ -122,11 +121,8 @@ impl<'a> RawBuilder<'a> { // not form-data request if self.request.formdatas().is_empty() && !self.found_header("content-type") { let header = match &self.content_type { - Some(ct) => Header::new("Content-Type", ct.to_string()), - None => Header::new( - "Content-Type", - mime::APPLICATION_WWW_FORM_URLENCODED.to_string(), - ), + Some(ct) => Header::new("Content-Type", ct), + None => Header::new("Content-Type", &mime::APPLICATION_WWW_FORM_URLENCODED), }; self.request.headers_mut().push(header); @@ -143,9 +139,10 @@ impl<'a> RawBuilder<'a> { headers.retain(|item| !item.name().eq_ignore_ascii_case("content-type")); let header = match &self.content_type { - Some(ct) => Header::new("Content-Type", ct.to_string()), - None => origin - .unwrap_or_else(|| Header::new("Content-Type", mime::APPLICATION_OCTET_STREAM.to_string())), + Some(ct) => Header::new("Content-Type", ct), + None => { + origin.unwrap_or_else(|| Header::new("Content-Type", &mime::APPLICATION_OCTET_STREAM)) + } }; headers.push(header); diff --git a/rttp_client/src/request/builder/form_data.rs b/rttp_client/src/request/builder/form_data.rs index 692ca69..32122a1 100644 --- a/rttp_client/src/request/builder/form_data.rs +++ b/rttp_client/src/request/builder/form_data.rs @@ -54,11 +54,7 @@ impl Disposition { DISPOSITION_PREFIX, HYPHENS, self.boundary, DISPOSITION_END, name, filename, DISPOSITION_END ); - disposition.push_str(&format!( - "Content-Type: {}{}", - mime.to_string(), - DISPOSITION_END - )); + disposition.push_str(&format!("Content-Type: {}{}", mime, DISPOSITION_END)); disposition.push_str(DISPOSITION_END); disposition } diff --git a/rttp_client/src/request/mod.rs b/rttp_client/src/request/mod.rs index f26430f..72c283a 100644 --- a/rttp_client/src/request/mod.rs +++ b/rttp_client/src/request/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::module_inception)] + pub use self::raw_request::RawRequest; pub use self::request::*; diff --git a/rttp_client/src/request/raw_request.rs b/rttp_client/src/request/raw_request.rs index eccd691..b4384e9 100644 --- a/rttp_client/src/request/raw_request.rs +++ b/rttp_client/src/request/raw_request.rs @@ -22,7 +22,7 @@ impl<'a> RawRequest<'a> { } pub fn origin(&self) -> &Request { - &self.origin + self.origin } pub fn url(&self) -> &RoUrl { @@ -42,6 +42,6 @@ impl<'a> RawRequest<'a> { } pub(crate) fn origin_mut(&mut self) -> &mut Request { - &mut self.origin + self.origin } } diff --git a/rttp_client/src/response/mod.rs b/rttp_client/src/response/mod.rs index adc7db7..a10a64a 100644 --- a/rttp_client/src/response/mod.rs +++ b/rttp_client/src/response/mod.rs @@ -1,5 +1,6 @@ +#![allow(clippy::module_inception)] pub use self::response::*; -mod response; mod raw_response; +mod response; diff --git a/rttp_client/src/response/raw_response.rs b/rttp_client/src/response/raw_response.rs index 6a86abb..02bba37 100644 --- a/rttp_client/src/response/raw_response.rs +++ b/rttp_client/src/response/raw_response.rs @@ -13,7 +13,6 @@ static CRLF: &str = "\r\n"; #[derive(Clone)] pub struct RawResponse { _url: Url, - url: RoUrl, binary: Vec, code: u32, version: String, @@ -28,7 +27,6 @@ impl RawResponse { let _url = url.to_url().map_err(error::builder)?; let mut response = RawResponse { _url, - url, binary: vec![], code: 0, version: "".to_string(), @@ -178,12 +176,12 @@ impl Parser { fn parse_header(&self, response: &mut RawResponse, text: String) -> error::Result<()> { let parts: Vec<&str> = text.split(CRLF).collect(); let status_line = parts - .get(0) + .first() .ok_or(error::bad_response("Response not have status line"))?; let status_parts: Vec<&str> = status_line.splitn(3, " ").collect(); let http_version = status_parts - .get(0) + .first() .ok_or(error::bad_response("Response status not have http version"))?; let status_code: u32 = match status_parts .get(1) @@ -206,20 +204,13 @@ impl Parser { .filter(|(_, v)| !v.is_empty()) .map(|(_, v)| v.into_headers()) .filter(|hs| !hs.is_empty()) - .map(|hs| match hs.get(0) { - Some(h) => Some(h.clone()), - None => None, - }) - .filter(Option::is_some) - .map(|h| h.unwrap()) + .filter_map(|hs| hs.first().cloned()) .collect::>(); let cookies: Vec = headers .iter() .filter(|header| header.name().eq_ignore_ascii_case("set-cookie")) - .map(|header| Cookie::parse(header.value()).ok()) - .filter(|ck| ck.is_some()) - .map(|ck| ck.unwrap()) + .filter_map(|header| Cookie::parse(header.value()).ok()) .collect(); response.headers(headers); diff --git a/rttp_client/src/response/response.rs b/rttp_client/src/response/response.rs index b6318c0..0290413 100644 --- a/rttp_client/src/response/response.rs +++ b/rttp_client/src/response/response.rs @@ -8,13 +8,13 @@ use crate::types::{Cookie, Header, RoUrl}; #[derive(Clone)] pub struct Response { - raw: RawResponse + raw: RawResponse, } impl Response { pub fn new(url: RoUrl, binary: Vec) -> error::Result { Ok(Self { - raw: RawResponse::new(url, binary)? + raw: RawResponse::new(url, binary)?, }) } } @@ -25,11 +25,11 @@ impl Response { } pub fn is_redirect(&self) -> bool { - self.code() == 301 || - self.code() == 302 || - self.code() == 303 || - self.code() == 307 || - self.header_value("Location").is_some() + self.code() == 301 + || self.code() == 302 + || self.code() == 303 + || self.code() == 307 + || self.header_value("Location").is_some() } pub fn code(&self) -> u32 { @@ -69,18 +69,24 @@ impl Response { } pub fn headers_of_name>(&self, name: S) -> Vec<&Header> { - self.headers().iter() + self + .headers() + .iter() .filter(|header| header.name().eq_ignore_ascii_case(name.as_ref())) .collect() } pub fn header>(&self, name: S) -> Option<&Header> { - self.headers().iter() + self + .headers() + .iter() .find(|header| header.name().eq_ignore_ascii_case(name.as_ref())) } pub fn header_values>(&self, name: S) -> Vec<&String> { - self.headers().iter() + self + .headers() + .iter() .filter(|header| header.name().eq_ignore_ascii_case(name.as_ref())) .map(|header| header.value()) .collect() @@ -95,11 +101,13 @@ impl Response { } pub fn cookie>(&self, name: S) -> Option<&Cookie> { - self.cookies().iter().find(|cookie| cookie.name().eq_ignore_ascii_case(name.as_ref())) + self + .cookies() + .iter() + .find(|cookie| cookie.name().eq_ignore_ascii_case(name.as_ref())) } } - impl fmt::Debug for Response { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -114,13 +122,11 @@ impl fmt::Display for Response { } } - #[derive(Clone)] pub struct ResponseBody { - binary: Vec + binary: Vec, } - impl ResponseBody { pub fn new(binary: Vec) -> Self { Self { binary } diff --git a/rttp_client/src/types/cookie.rs b/rttp_client/src/types/cookie.rs index af10193..52d1312 100644 --- a/rttp_client/src/types/cookie.rs +++ b/rttp_client/src/types/cookie.rs @@ -18,23 +18,39 @@ pub struct Cookie { } impl Cookie { - pub fn name(&self) -> &String { &self.name } - pub fn value(&self) -> &String { &self.value } - pub fn expires(&self) -> &Option { &self.expires } - pub fn path(&self) -> &Option { &self.path } - pub fn domain(&self) -> &Option { &self.domain } - pub fn secure(&self) -> bool { self.secure } - pub fn http_only(&self) -> bool { self.http_only } - pub fn persistent(&self) -> bool { self.persistent } - pub fn host_only(&self) -> bool { self.host_only } - pub fn same_site(&self) -> &Option { &self.same_site } + pub fn name(&self) -> &String { + &self.name + } + pub fn value(&self) -> &String { + &self.value + } + pub fn expires(&self) -> &Option { + &self.expires + } + pub fn path(&self) -> &Option { + &self.path + } + pub fn domain(&self) -> &Option { + &self.domain + } + pub fn secure(&self) -> bool { + self.secure + } + pub fn http_only(&self) -> bool { + self.http_only + } + pub fn persistent(&self) -> bool { + self.persistent + } + pub fn host_only(&self) -> bool { + self.host_only + } + pub fn same_site(&self) -> &Option { + &self.same_site + } pub fn string(&self) -> String { - let mut text = format!( - "{}={}", - self.name, - self.value, - ); + let mut text = format!("{}={}", self.name, self.value,); if let Some(path) = &self.path { text.push_str(&format!("; path={}", path)); } @@ -75,8 +91,13 @@ impl Cookie { let parts: Vec<&str> = text.as_ref().split(";").collect(); for item in parts { let nvs: Vec<&str> = item.split("=").collect(); - let name = nvs.get(0).ok_or(error::bad_cookie("Cookie not have name"))?.trim(); - let value: String = nvs.iter().enumerate() + let name = nvs + .first() + .ok_or(error::bad_cookie("Cookie not have name"))? + .trim(); + let value: String = nvs + .iter() + .enumerate() .filter(|(ix, _)| *ix > 0) .map(|(_, v)| *v) .collect::>() @@ -89,7 +110,7 @@ impl Cookie { Ok(v) => { builder.expires(v); } - Err(e) => eprintln!("=> {:?}", e) + Err(e) => eprintln!("=> {:?}", e), } } "path" => { @@ -102,21 +123,33 @@ impl Cookie { if value.is_empty() { builder.secure(true); } else { - builder.secure(value.parse().map_err(|_| error::bad_cookie("Cookie secure can not parse to bool"))?); + builder.secure( + value + .parse() + .map_err(|_| error::bad_cookie("Cookie secure can not parse to bool"))?, + ); } } "http_only" | "httponly" | "httpOnly" => { if value.is_empty() { builder.http_only(true); } else { - builder.http_only(value.parse().map_err(|_| error::bad_cookie("Cookie httpOnly can not parse to bool"))?); + builder.http_only( + value + .parse() + .map_err(|_| error::bad_cookie("Cookie httpOnly can not parse to bool"))?, + ); } } "host_only" | "hostonly" | "hostOnly" => { if value.is_empty() { builder.host_only(true); } else { - builder.host_only(value.parse().map_err(|_| error::bad_cookie("Cookie hostOnly can not parse to bool"))?); + builder.host_only( + value + .parse() + .map_err(|_| error::bad_cookie("Cookie hostOnly can not parse to bool"))?, + ); } } "same_site" | "SameSite" | "samSite" => { @@ -132,7 +165,6 @@ impl Cookie { } } - impl fmt::Debug for Cookie { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -147,7 +179,8 @@ impl fmt::Display for Cookie { } } - +#[allow(dead_code)] +#[allow(clippy::wrong_self_convention)] pub trait ToCookie { fn to_cookie(&self) -> error::Result; } @@ -170,10 +203,9 @@ impl ToCookie for &str { } } - #[derive(Clone)] pub struct CookieBuilder { - cookie: Cookie + cookie: Cookie, } impl CookieBuilder { @@ -190,7 +222,7 @@ impl CookieBuilder { persistent: false, host_only: false, same_site: None, - } + }, } } @@ -248,4 +280,3 @@ impl AsRef for CookieBuilder { &self.cookie } } - diff --git a/rttp_client/src/types/form_data.rs b/rttp_client/src/types/form_data.rs index cffd244..37d15f7 100644 --- a/rttp_client/src/types/form_data.rs +++ b/rttp_client/src/types/form_data.rs @@ -140,7 +140,7 @@ impl ToFormData for FormData { } } -impl<'a> ToFormData for &'a str { +impl ToFormData for &str { /// Support format text /// ## sample /// ```text @@ -153,7 +153,7 @@ impl<'a> ToFormData for &'a str { .iter() .map(|part: &&str| { let pvs: Vec<&str> = part.split("=").collect::>(); - let name = pvs.get(0).map_or("".to_string(), |v| v.trim().to_string()); + let name = pvs.first().map_or("".to_string(), |v| v.trim().to_string()); let value = pvs.get(1).map_or("".to_string(), |v| v.trim().to_string()); if !value.starts_with("@") { return FormData::with_text(name, value); @@ -162,7 +162,7 @@ impl<'a> ToFormData for &'a str { let path = Path::new(&value[1..]); return FormData::with_file(name, path); } - let hasps: Vec<&str> = (&value[1..]).split("#").collect::>(); + let hasps: Vec<&str> = value[1..].split("#").collect::>(); let len = hasps.len(); let filename = hasps .iter() @@ -195,14 +195,14 @@ impl + Eq + std::hash::Hash, V: AsRef> ToFormData for HashMap if let Some(value) = self.get(name) { let value = value.as_ref(); if !value.starts_with("@") { - rets.push(FormData::with_text(&name, value)); + rets.push(FormData::with_text(name, value)); continue; } if !value.contains("#") { let path = Path::new(&value[1..]); - rets.push(FormData::with_file(&name, path)); + rets.push(FormData::with_file(name, path)); } - let hasps: Vec<&str> = (&value[1..]).split("#").collect::>(); + let hasps: Vec<&str> = value[1..].split("#").collect::>(); let len = hasps.len(); let filename = hasps .iter() @@ -215,20 +215,20 @@ impl + Eq + std::hash::Hash, V: AsRef> ToFormData for HashMap .get(len - 1) .map_or("".to_string(), |v| v.trim().to_string()); let path = Path::new(&path); - rets.push(FormData::with_file_and_name(&name, path, filename)); + rets.push(FormData::with_file_and_name(name, path, filename)); } } rets } } -impl<'a, IU: ToFormData> ToFormData for &'a IU { +impl ToFormData for &IU { fn to_formdatas(&self) -> Vec { (*self).to_formdatas() } } -impl<'a, IU: ToFormData> ToFormData for &'a mut IU { +impl ToFormData for &mut IU { fn to_formdatas(&self) -> Vec { (**self).to_formdatas() } diff --git a/rttp_client/src/types/header.rs b/rttp_client/src/types/header.rs index b3dfa09..6817413 100644 --- a/rttp_client/src/types/header.rs +++ b/rttp_client/src/types/header.rs @@ -6,7 +6,7 @@ pub struct Header { value: String, } - +#[allow(clippy::wrong_self_convention)] pub trait IntoHeader { fn into_headers(&self) -> Vec
; } @@ -42,14 +42,18 @@ impl Header { } } -impl<'a> IntoHeader for &'a str { +impl IntoHeader for &str { fn into_headers(&self) -> Vec
{ - self.split("\n").collect::>() + self + .split("\n") + .collect::>() .iter() .map(|part: &&str| { let pvs: Vec<&str> = part.split(":").collect::>(); - let name = pvs.get(0); - let value = pvs.iter().enumerate() + let name = pvs.first(); + let value = pvs + .iter() + .enumerate() .filter(|(ix, _)| *ix > 0) .map(|(_, v)| v.to_string()) .collect::>() @@ -64,14 +68,12 @@ impl<'a> IntoHeader for &'a str { } } - impl IntoHeader for String { fn into_headers(&self) -> Vec
{ (&self[..]).into_headers() } } - impl IntoHeader for Header { fn into_headers(&self) -> Vec
{ vec![self.clone()] @@ -90,26 +92,22 @@ impl + Eq + std::hash::Hash, V: AsRef> IntoHeader for HashMap } } - -impl<'a, IU: IntoHeader> IntoHeader for &'a IU { +impl IntoHeader for &IU { fn into_headers(&self) -> Vec
{ (*self).into_headers() } } -impl<'a, IU: IntoHeader> IntoHeader for &'a mut IU { +impl IntoHeader for &mut IU { fn into_headers(&self) -> Vec
{ (**self).into_headers() } } - - - - - macro_rules! replace_expr { - ($_t:tt $sub:ty) => {$sub}; + ($_t:tt $sub:ty) => { + $sub + }; } macro_rules! tuple_to_header { @@ -180,6 +178,3 @@ tuple_to_header! { a b c d e f g h i j k l m n o p q r s t u v w } tuple_to_header! { a b c d e f g h i j k l m n o p q r s t u v w x } tuple_to_header! { a b c d e f g h i j k l m n o p q r s t u v w x y } tuple_to_header! { a b c d e f g h i j k l m n o p q r s t u v w x y z } - - - diff --git a/rttp_client/src/types/mod.rs b/rttp_client/src/types/mod.rs index a4ea099..ea96072 100644 --- a/rttp_client/src/types/mod.rs +++ b/rttp_client/src/types/mod.rs @@ -1,18 +1,17 @@ - -pub use self::status::*; -pub use self::url::*; -pub use self::para::*; -pub use self::header::*; +pub use self::cookie::Cookie; pub use self::form_data::*; +pub use self::header::*; +pub use self::para::*; pub use self::proxy::*; -pub use self::cookie::Cookie; +pub use self::status::*; +pub use self::url::*; -mod status; -mod url; -mod para; -mod header; +mod cookie; mod form_data; +mod header; +mod para; mod proxy; -mod cookie; +mod status; +mod url; mod type_helper; diff --git a/rttp_client/src/types/para.rs b/rttp_client/src/types/para.rs index 35f3a0f..4c27175 100644 --- a/rttp_client/src/types/para.rs +++ b/rttp_client/src/types/para.rs @@ -16,6 +16,7 @@ pub struct Para { array: bool, } +#[allow(clippy::wrong_self_convention)] pub trait IntoPara { // Besides parsing as a valid `Url`, the `Url` must be a valid // `http::Uri`, in that it makes sense to use in a network request. @@ -93,7 +94,7 @@ impl IntoPara for Para { } } -impl<'a> IntoPara for &'a str { +impl IntoPara for &str { fn into_paras(&self) -> Vec { self .split("&") @@ -102,7 +103,7 @@ impl<'a> IntoPara for &'a str { .map(|part: &&str| { let pvs: Vec<&str> = part.split("=").collect::>(); Para::with_form( - pvs.get(0).map_or("".to_string(), |v| v.to_string()).trim(), + pvs.first().map_or("".to_string(), |v| v.to_string()).trim(), pvs.get(1).map_or("".to_string(), |v| v.to_string()).trim(), ) }) @@ -129,13 +130,13 @@ impl + Eq + std::hash::Hash, V: AsRef> IntoPara for HashMap IntoPara for &'a IU { +impl IntoPara for &IU { fn into_paras(&self) -> Vec { (*self).into_paras() } } -impl<'a, IU: IntoPara> IntoPara for &'a mut IU { +impl IntoPara for &mut IU { fn into_paras(&self) -> Vec { (**self).into_paras() } diff --git a/rttp_client/src/types/proxy.rs b/rttp_client/src/types/proxy.rs index 107d15d..31ac3cd 100644 --- a/rttp_client/src/types/proxy.rs +++ b/rttp_client/src/types/proxy.rs @@ -1,4 +1,3 @@ - #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ProxyType { HTTP, @@ -17,12 +16,16 @@ pub struct Proxy { } impl Proxy { - pub fn builder(type_: ProxyType) -> ProxyBuilder { ProxyBuilder::new(type_) } - pub fn http_with_authorization, U: AsRef, P: AsRef>(host: H, port: u32, username: U, password: P) -> Self { + pub fn http_with_authorization, U: AsRef, P: AsRef>( + host: H, + port: u32, + username: U, + password: P, + ) -> Self { Self::builder(ProxyType::HTTP) .host(host) .port(port) @@ -32,13 +35,15 @@ impl Proxy { } pub fn http>(host: H, port: u32) -> Self { - Self::builder(ProxyType::HTTP) - .host(host) - .port(port) - .build() + Self::builder(ProxyType::HTTP).host(host).port(port).build() } - pub fn https_with_authorization, U: AsRef, P: AsRef>(host: H, port: u32, username: U, password: P) -> Self { + pub fn https_with_authorization, U: AsRef, P: AsRef>( + host: H, + port: u32, + username: U, + password: P, + ) -> Self { Self::builder(ProxyType::HTTPS) .host(host) .port(port) @@ -61,7 +66,12 @@ impl Proxy { .build() } - pub fn socks4_with_authorization, U: AsRef, P: AsRef>(host: H, port: u32, username: U, password: P) -> Self { + pub fn socks4_with_authorization, U: AsRef, P: AsRef>( + host: H, + port: u32, + username: U, + password: P, + ) -> Self { Self::builder(ProxyType::SOCKS4) .host(host) .port(port) @@ -77,7 +87,12 @@ impl Proxy { .build() } - pub fn socks5_with_authorization, U: AsRef, P: AsRef>(host: H, port: u32, username: U, password: P) -> Self { + pub fn socks5_with_authorization, U: AsRef, P: AsRef>( + host: H, + port: u32, + username: U, + password: P, + ) -> Self { Self::builder(ProxyType::SOCKS5) .host(host) .port(port) @@ -86,15 +101,25 @@ impl Proxy { .build() } - pub fn host(&self) -> &String { &self.host } - pub fn port(&self) -> u32 { self.port } - pub fn username(&self) -> &Option { &self.username } - pub fn password(&self) -> &Option { &self.password } - pub fn type_(&self) -> &ProxyType { &self.type_ } + pub fn host(&self) -> &String { + &self.host + } + pub fn port(&self) -> u32 { + self.port + } + pub fn username(&self) -> &Option { + &self.username + } + pub fn password(&self) -> &Option { + &self.password + } + pub fn type_(&self) -> &ProxyType { + &self.type_ + } } pub struct ProxyBuilder { - proxy: Proxy + proxy: Proxy, } impl ProxyBuilder { @@ -106,7 +131,7 @@ impl ProxyBuilder { username: None, password: None, type_, - } + }, } } @@ -133,10 +158,8 @@ impl ProxyBuilder { self.proxy.password = Some(password.as_ref().into()); self } - } - impl AsRef for Proxy { fn as_ref(&self) -> &Proxy { self @@ -148,4 +171,3 @@ impl AsRef for ProxyBuilder { &self.proxy } } - diff --git a/rttp_client/src/types/status.rs b/rttp_client/src/types/status.rs index 7112e80..867ddd1 100644 --- a/rttp_client/src/types/status.rs +++ b/rttp_client/src/types/status.rs @@ -69,7 +69,7 @@ impl StatusCode { /// ``` #[inline] pub fn from_u16(src: u16) -> Result { - if src < 100 || src >= 600 { + if !(100..600).contains(&src) { return Err(InvalidStatusCode::new()); } @@ -246,7 +246,7 @@ impl FromStr for StatusCode { impl<'a> From<&'a StatusCode> for StatusCode { #[inline] fn from(t: &'a StatusCode) -> Self { - t.clone() + *t } } @@ -486,7 +486,7 @@ impl fmt::Debug for InvalidStatusCode { impl fmt::Display for InvalidStatusCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&self.to_string()) + f.write_str("invalid status code") } } diff --git a/rttp_client/src/types/type_helper.rs b/rttp_client/src/types/type_helper.rs index f7eb29e..bbaf20d 100644 --- a/rttp_client/src/types/type_helper.rs +++ b/rttp_client/src/types/type_helper.rs @@ -1,8 +1,6 @@ - - - pub fn stand_uri(uri: String) -> String { - uri.split("/") + uri + .split("/") .collect::>() .iter() .filter(|v| !v.is_empty()) @@ -10,4 +8,3 @@ pub fn stand_uri(uri: String) -> String { .collect::>() .join("/") } - diff --git a/rttp_client/src/types/url.rs b/rttp_client/src/types/url.rs index 5484897..b99a5ad 100644 --- a/rttp_client/src/types/url.rs +++ b/rttp_client/src/types/url.rs @@ -54,7 +54,7 @@ impl RoUrl { let url = url.as_ref(); let netloc_and_para: Vec<&str> = url.split("?").collect::>(); let url = netloc_and_para - .get(0) + .first() .map_or("".to_string(), |v| v.to_string()); let mut para_string = String::new(); let mut fragment = None; @@ -66,7 +66,7 @@ impl RoUrl { if para_and_fragment.is_empty() { para_string.push_str(nap); } else { - if let Some(last_para) = para_and_fragment.get(0) { + if let Some(last_para) = para_and_fragment.first() { para_string.push_str(last_para); } @@ -82,7 +82,7 @@ impl RoUrl { } } } - let mut paras = (¶_string).into_paras(); + let mut paras = para_string.into_paras(); for para in &mut paras { *para.type_mut() = ParaType::URL; } @@ -116,7 +116,7 @@ impl RoUrl { &self.fragment } pub(crate) fn traditional_get(&self) -> Option { - self.traditional.clone() + self.traditional } /// Set fragment to url @@ -192,10 +192,7 @@ impl RoUrl { .filter(|&p| p.is_url() || p.is_form()) .map(|p| { let name = p.name(); - let traditional = match self.traditional { - Some(v) => v, - None => true, - }; + let traditional = self.traditional.unwrap_or(true); if traditional { return format!( "{}={}", @@ -210,7 +207,7 @@ impl RoUrl { .len() > 1; let ends_with_bracket = name.ends_with("[]"); - return format!( + format!( "{}{}={}", name, if !ends_with_bracket && (is_array || p.array()) { @@ -219,7 +216,7 @@ impl RoUrl { "" }, p.value().clone().map_or("".to_string(), |t| t) - ); + ) }) .collect::>() .join("&"); @@ -292,13 +289,13 @@ impl AsRef for RoUrl { } } -impl<'a, IU: ToRoUrl> ToRoUrl for &'a IU { +impl ToRoUrl for &IU { fn to_rourl(&self) -> RoUrl { (*self).to_rourl() } } -impl<'a, IU: ToRoUrl> ToRoUrl for &'a mut IU { +impl ToRoUrl for &mut IU { fn to_rourl(&self) -> RoUrl { (**self).to_rourl() } diff --git a/rttp_client/tests/support/mod.rs b/rttp_client/tests/support/mod.rs index ab811c8..bfce4c7 100644 --- a/rttp_client/tests/support/mod.rs +++ b/rttp_client/tests/support/mod.rs @@ -2,6 +2,48 @@ use std::io::{Read, Write}; use std::net::{SocketAddr, TcpListener}; use std::thread::{self, JoinHandle}; +fn read_http_request(stream: &mut R) { + let mut request = Vec::new(); + let mut buf = [0u8; 1024]; + let mut content_length = None; + + loop { + let Ok(read) = stream.read(&mut buf) else { + break; + }; + if read == 0 { + break; + } + + request.extend_from_slice(&buf[..read]); + + let header_end = request.windows(4).position(|window| window == b"\r\n\r\n"); + if content_length.is_none() { + if let Some(header_end) = header_end { + let headers = String::from_utf8_lossy(&request[..header_end + 4]); + content_length = headers + .lines() + .find_map(|line| { + let (name, value) = line.split_once(':')?; + if name.eq_ignore_ascii_case("content-length") { + value.trim().parse::().ok() + } else { + None + } + }) + .or(Some(0)); + } + } + + if let (Some(header_end), Some(content_length)) = (header_end, content_length) { + let expected_len = header_end + 4 + content_length; + if request.len() >= expected_len { + break; + } + } + } +} + pub fn spawn_http_server() -> (SocketAddr, JoinHandle<()>) { spawn_http_server_count(1) } @@ -12,8 +54,7 @@ pub fn spawn_http_server_count(count: usize) -> (SocketAddr, JoinHandle<()>) { let handle = thread::spawn(move || { for _ in 0..count { if let Ok((mut stream, _)) = listener.accept() { - let mut buf = [0u8; 1024]; - let _ = stream.read(&mut buf); + read_http_request(&mut stream); let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK"; let _ = stream.write_all(response); } @@ -27,8 +68,7 @@ pub fn spawn_redirect_server() -> (SocketAddr, JoinHandle<()>) { let addr = listener.local_addr().expect("redirect addr"); let handle = thread::spawn(move || { if let Ok((mut stream, _)) = listener.accept() { - let mut buf = [0u8; 1024]; - let _ = stream.read(&mut buf); + read_http_request(&mut stream); let response = format!( "HTTP/1.1 302 Found\r\nLocation: http://{}/final\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", addr @@ -37,8 +77,7 @@ pub fn spawn_redirect_server() -> (SocketAddr, JoinHandle<()>) { } if let Ok((mut stream, _)) = listener.accept() { - let mut buf = [0u8; 1024]; - let _ = stream.read(&mut buf); + read_http_request(&mut stream); let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK"; let _ = stream.write_all(response); } @@ -49,30 +88,35 @@ pub fn spawn_redirect_server() -> (SocketAddr, JoinHandle<()>) { #[cfg(feature = "tls-rustls")] pub fn spawn_tls_server() -> (SocketAddr, JoinHandle<()>) { use rcgen::generate_simple_self_signed; - use rustls::{NoClientAuth, ServerConfig, ServerSession, Stream}; + use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; + use rustls::{ServerConfig, ServerConnection, StreamOwned}; use std::sync::Arc; let cert = generate_simple_self_signed(vec!["localhost".to_string()]).expect("generate cert"); let cert_der = cert.serialize_der().expect("cert der"); let key_der = cert.serialize_private_key_der(); - let mut config = ServerConfig::new(NoClientAuth::new()); - config - .set_single_cert(vec![rustls::Certificate(cert_der)], rustls::PrivateKey(key_der)) + let config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert( + vec![CertificateDer::from(cert_der)], + PrivateKeyDer::from(PrivatePkcs8KeyDer::from(key_der)), + ) .expect("set cert"); let config = Arc::new(config); let listener = TcpListener::bind("127.0.0.1:0").expect("bind tls server"); let addr = listener.local_addr().expect("tls addr"); let handle = thread::spawn(move || { - if let Ok((mut stream, _)) = listener.accept() { - let mut session = ServerSession::new(&config); - let mut tls = Stream::new(&mut session, &mut stream); - let mut buf = [0u8; 1024]; - let _ = tls.read(&mut buf); + if let Ok((stream, _)) = listener.accept() { + let session = ServerConnection::new(config.clone()).expect("server connection"); + let mut tls = StreamOwned::new(session, stream); + read_http_request(&mut tls); let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK"; let _ = tls.write_all(response); let _ = tls.flush(); + tls.conn.send_close_notify(); + let _ = tls.flush(); } }); (addr, handle) diff --git a/rttp_client/tests/test_http_basic.rs b/rttp_client/tests/test_http_basic.rs index 027d12e..eb05ca6 100644 --- a/rttp_client/tests/test_http_basic.rs +++ b/rttp_client/tests/test_http_basic.rs @@ -116,7 +116,11 @@ fn test_http_with_url() { let (addr, _handle) = support::spawn_http_server(); client() .method("get") - .url(RoUrl::with(format!("http://{}", addr)).path("/get").para(("name", "Chico"))) + .url( + RoUrl::with(format!("http://{}", addr)) + .path("/get") + .para(("name", "Chico")), + ) .emit() .expect("REQUEST FAIL"); } @@ -169,8 +173,15 @@ fn test_connection_closed() { assert!(resp1.is_err()); let resp2 = self::client().url(format!("http://{}/get", addr)).emit(); assert!(resp2.is_ok()); - let resp3 = self::client().post().url(format!("http://{}/post", addr)).emit(); + let resp3 = self::client() + .post() + .url(format!("http://{}/post", addr)) + .emit(); assert!(resp3.is_ok()); - let resp4 = client.reset().post().url(format!("http://{}/post", addr)).emit(); + let resp4 = client + .reset() + .post() + .url(format!("http://{}/post", addr)) + .emit(); assert!(resp4.is_ok()); } diff --git a/rttp_client/tests/test_httpdate.rs b/rttp_client/tests/test_httpdate.rs index e74f9f8..fa58f62 100644 --- a/rttp_client/tests/test_httpdate.rs +++ b/rttp_client/tests/test_httpdate.rs @@ -5,10 +5,14 @@ use httpdate::parse_http_date; #[test] fn test_rfc_example() { let d = UNIX_EPOCH + Duration::from_secs(784111777); - assert_eq!(d, - parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT").expect("#1")); - assert_eq!(d, - parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT").expect("#2")); + assert_eq!( + d, + parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT").expect("#1") + ); + assert_eq!( + d, + parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT").expect("#2") + ); assert_eq!(d, parse_http_date("Sun Nov 6 08:49:37 1994").expect("#3")); } diff --git a/rttp_client/tests/test_response.rs b/rttp_client/tests/test_response.rs index abd9d1d..754a1b3 100644 --- a/rttp_client/tests/test_response.rs +++ b/rttp_client/tests/test_response.rs @@ -69,7 +69,10 @@ fn test_parse_response_1() { \"origin\": \"222.69.134.133, 222.69.134.133\", \"url\": \"https://httpbin.org/post?id=1&name=jack&name=Julia\" }"; - let response = Response::new(RoUrl::with("https://httpbin.org/post"), s.as_bytes().to_vec()); + let response = Response::new( + RoUrl::with("https://httpbin.org/post"), + s.as_bytes().to_vec(), + ); assert!(response.is_ok()); let response = response.unwrap(); println!("{}", response); diff --git a/rttp_client/tests/test_rustls.rs b/rttp_client/tests/test_rustls.rs index 8f7a66c..a79b9b0 100644 --- a/rttp_client/tests/test_rustls.rs +++ b/rttp_client/tests/test_rustls.rs @@ -9,37 +9,38 @@ use std::net::TcpStream; use std::sync::Arc; #[cfg(feature = "tls-rustls")] -use rustls; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; #[cfg(feature = "tls-rustls")] -use rustls::Session; +use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; #[cfg(feature = "tls-rustls")] -use webpki; +use rustls::{ClientConfig, ClientConnection, DigitallySignedStruct, SignatureScheme, StreamOwned}; #[test] #[cfg(feature = "tls-rustls")] fn test_rustls() { let (addr, _handle) = support::spawn_tls_server(); - let mut config = rustls::ClientConfig::new(); - config + let config = ClientConfig::builder() .dangerous() - .set_certificate_verifier(Arc::new(NoCertificateVerification)); - - let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap(); - let mut sess = rustls::ClientSession::new(&Arc::new(config), dns_name); - let mut sock = TcpStream::connect(addr).unwrap(); - let mut tls = rustls::Stream::new(&mut sess, &mut sock); - tls.write_all( - concat!( - "GET / HTTP/1.1\r\n", - "Host: localhost\r\n", - "Connection: close\r\n", - "Accept-Encoding: identity\r\n", - "\r\n" + .with_custom_certificate_verifier(Arc::new(NoCertificateVerification)) + .with_no_client_auth(); + + let dns_name = ServerName::try_from("localhost").unwrap().to_owned(); + let sess = ClientConnection::new(Arc::new(config), dns_name).unwrap(); + let sock = TcpStream::connect(addr).unwrap(); + let mut tls = StreamOwned::new(sess, sock); + tls + .write_all( + concat!( + "GET / HTTP/1.1\r\n", + "Host: localhost\r\n", + "Connection: close\r\n", + "Accept-Encoding: identity\r\n", + "\r\n" + ) + .as_bytes(), ) - .as_bytes(), - ) - .unwrap(); - let _ciphersuite = tls.sess.get_negotiated_ciphersuite().unwrap(); + .unwrap(); + let _ciphersuite = tls.conn.negotiated_cipher_suite().unwrap(); let mut plaintext = Vec::new(); tls.read_to_end(&mut plaintext).unwrap(); let text = String::from_utf8(plaintext).unwrap(); @@ -47,17 +48,53 @@ fn test_rustls() { } #[cfg(feature = "tls-rustls")] +#[derive(Debug)] struct NoCertificateVerification; #[cfg(feature = "tls-rustls")] -impl rustls::ServerCertVerifier for NoCertificateVerification { +impl ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, _ocsp_response: &[u8], - ) -> Result { - Ok(rustls::ServerCertVerified::assertion()) + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + SignatureScheme::RSA_PKCS1_SHA1, + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + ] } } diff --git a/rttp_client/tests/test_url.rs b/rttp_client/tests/test_url.rs index d886e0f..2be23c9 100644 --- a/rttp_client/tests/test_url.rs +++ b/rttp_client/tests/test_url.rs @@ -1,5 +1,5 @@ -use url::Url; use rttp_client::types::{RoUrl, ToUrl}; +use url::Url; #[test] fn test_url_gen() { @@ -23,7 +23,6 @@ fn test_rourl_gen() { .expect("BAD URL"); println!("{}", url); - let rourl: RoUrl = url.into(); println!("{:?}", rourl); println!("{}", rourl.to_url().expect("BAD URL"));