diff --git a/ylong_http/src/h2/hpack/decoder.rs b/ylong_http/src/h2/hpack/decoder.rs index 700bac65a2fba344ebfdbabd75423e0d8968e5ea..7a953aad7740fca9fd7f3cb1e0faf33f2e1f8780 100644 --- a/ylong_http/src/h2/hpack/decoder.rs +++ b/ylong_http/src/h2/hpack/decoder.rs @@ -163,7 +163,7 @@ impl<'a> Updater<'a> { self.update_literal_without_indexing(name, value) } - fn check_header_list_size(&mut self, key: &Header, value: &String) -> Result<(), H2Error> { + fn check_header_list_size(&mut self, key: &Header, value: &str) -> Result<(), H2Error> { let line_size = header_line_length(key.len(), value.len()); self.update_size(line_size); if self.lines.header_size > self.header_list_size { diff --git a/ylong_http_client/examples/async_https_outside.rs b/ylong_http_client/examples/async_https_outside.rs index 1b953c99b3cc283fc983015e60542f6422719fc2..35dcb60f417ff16255690538c2eaeda28ae18f68 100644 --- a/ylong_http_client/examples/async_https_outside.rs +++ b/ylong_http_client/examples/async_https_outside.rs @@ -50,7 +50,7 @@ async fn req() -> Result<(), HttpClientError> { // Creates a `Request`. let request = Request::builder() - .url("https://www.baidu.com") + .url("https://www.example.com") .body(Body::empty())?; // Sends request and receives a `Response`. diff --git a/ylong_http_client/src/async_impl/client.rs b/ylong_http_client/src/async_impl/client.rs index fa61fc5ee4234a1944f592ca72a7cd5cb32c71f1..e0c2268842ae935462efd58643573b59641628f9 100644 --- a/ylong_http_client/src/async_impl/client.rs +++ b/ylong_http_client/src/async_impl/client.rs @@ -32,7 +32,7 @@ use crate::util::normalizer::RequestFormatter; use crate::util::proxy::Proxies; use crate::util::redirect::{RedirectInfo, Trigger}; use crate::util::request::RequestArc; -#[cfg(feature = "__tls")] +#[cfg(feature = "__c_openssl")] use crate::CertVerifier; use crate::Retry; @@ -420,10 +420,29 @@ impl ClientBuilder { /// let client = ClientBuilder::new().build(); /// ``` pub fn build(self) -> Result, HttpClientError> { + #[cfg(feature = "__c_openssl")] + use crate::util::{AlpnProtocol, AlpnProtocolList}; + + #[cfg(feature = "__c_openssl")] + let origin_builder = self.tls; + #[cfg(feature = "__c_openssl")] + let tls_builder = match self.http.version { + HttpVersion::Http1 => origin_builder, + #[cfg(feature = "http2")] + HttpVersion::Http2 => origin_builder.alpn_protos(AlpnProtocol::H2.wire_format_bytes()), + HttpVersion::Negotiate => { + let supported = AlpnProtocolList::new(); + #[cfg(feature = "http2")] + let supported = supported.extend(AlpnProtocol::H2); + let supported = supported.extend(AlpnProtocol::HTTP11); + origin_builder.alpn_proto_list(supported) + } + }; + let config = ConnectorConfig { proxies: self.proxies, #[cfg(feature = "__tls")] - tls: self.tls.build()?, + tls: tls_builder.build()?, }; let connector = HttpConnector::new(config); @@ -448,7 +467,7 @@ impl ClientBuilder { /// let builder = ClientBuilder::new().http2_prior_knowledge(); /// ``` pub fn http2_prior_knowledge(mut self) -> Self { - self.http.version = HttpVersion::Http2PriorKnowledge; + self.http.version = HttpVersion::Http2; self } diff --git a/ylong_http_client/src/async_impl/conn/http2.rs b/ylong_http_client/src/async_impl/conn/http2.rs index 602d4c3be8489b42021fe7d0c3c94014490307f5..d7e8d94d99d4857a481ca09c64d7b7c4ff44e435 100644 --- a/ylong_http_client/src/async_impl/conn/http2.rs +++ b/ylong_http_client/src/async_impl/conn/http2.rs @@ -115,11 +115,11 @@ where pub(crate) fn build_headers_frame( id: u32, - part: RequestPart, + mut part: RequestPart, is_end_stream: bool, ) -> Result { - check_connection_specific_headers(id, &part.headers)?; - let pseudo = build_pseudo_headers(&part); + remove_connection_specific_headers(&mut part.headers)?; + let pseudo = build_pseudo_headers(&mut part)?; let mut header_part = h2::Parts::new(); header_part.set_header_lines(part.headers); header_part.set_pseudo(pseudo); @@ -141,7 +141,7 @@ pub(crate) fn build_headers_frame( // [`Connection-Specific Headers`] implementation. // // [`Connection-Specific Headers`]: https://www.rfc-editor.org/rfc/rfc9113.html#name-connection-specific-header- -fn check_connection_specific_headers(id: u32, headers: &Headers) -> Result<(), HttpError> { +fn remove_connection_specific_headers(headers: &mut Headers) -> Result<(), HttpError> { const CONNECTION_SPECIFIC_HEADERS: &[&str; 5] = &[ "connection", "keep-alive", @@ -150,21 +150,19 @@ fn check_connection_specific_headers(id: u32, headers: &Headers) -> Result<(), H "transfer-encoding", ]; for specific_header in CONNECTION_SPECIFIC_HEADERS.iter() { - if headers.get(*specific_header).is_some() { - return Err(H2Error::StreamError(id, ErrorCode::ProtocolError).into()); - } + headers.remove(*specific_header); } if let Some(te_ref) = headers.get("te") { let te = te_ref.to_string()?; if te.as_str() != "trailers" { - return Err(H2Error::StreamError(id, ErrorCode::ProtocolError).into()); + headers.remove("te"); } } Ok(()) } -fn build_pseudo_headers(request_part: &RequestPart) -> PseudoHeaders { +fn build_pseudo_headers(request_part: &mut RequestPart) -> Result { let mut pseudo = PseudoHeaders::default(); match request_part.uri.scheme() { Some(scheme) => { @@ -179,8 +177,12 @@ fn build_pseudo_headers(request_part: &RequestPart) -> PseudoHeaders { .path_and_query() .or_else(|| Some(String::from("/"))), ); - pseudo.set_authority(request_part.uri.authority().map(|auth| auth.to_string())); - pseudo + let host = request_part + .headers + .remove("host") + .and_then(|auth| auth.to_string().ok()); + pseudo.set_authority(host); + Ok(pseudo) } struct TextIo { @@ -339,7 +341,7 @@ impl AsyncRead for TextIo { #[cfg(test)] mod ut_http2 { use ylong_http::body::TextBody; - use ylong_http::h2::{ErrorCode, H2Error, Payload}; + use ylong_http::h2::Payload; use ylong_http::request::RequestBuilder; use crate::async_impl::conn::http2::build_headers_frame; @@ -378,11 +380,9 @@ mod ut_http2 { Body: "Hi", } ); - let frame = build_headers_frame(1, request.part().clone(), false) - .expect("headers frame build failed"); + let frame = build_headers_frame(1, request.part().clone(), false).unwrap(); assert_eq!(frame.flags().bits(), 0x4); - let frame = build_headers_frame(1, request.part().clone(), true) - .expect("headers frame build failed"); + let frame = build_headers_frame(1, request.part().clone(), true).unwrap(); assert_eq!(frame.stream_id(), 1); assert_eq!(frame.flags().bits(), 0x5); if let Payload::Headers(headers) = frame.payload() { @@ -395,20 +395,5 @@ mod ut_http2 { } else { panic!("Unexpected frame type") } - let request = build_request!( - Request: { - Method: "GET", - Uri: "http://127.0.0.1:0/data", - Version: "HTTP/2.0", - Header: "upgrade", "h2", - Header: "host", "127.0.0.1:0", - Body: "Hi", - } - ); - let frame = build_headers_frame(1, request.part().clone(), true); - assert_eq!( - frame.err(), - Some(H2Error::StreamError(1, ErrorCode::ProtocolError).into()) - ); } } diff --git a/ylong_http_client/src/async_impl/connector/mod.rs b/ylong_http_client/src/async_impl/connector/mod.rs index 6f1cbc8c413e8d2a0371b4b0567ba7f49885bd70..a581566e54c1e53dea7d52e68448527890e4571d 100644 --- a/ylong_http_client/src/async_impl/connector/mod.rs +++ b/ylong_http_client/src/async_impl/connector/mod.rs @@ -98,6 +98,7 @@ mod no_tls { let peer = stream.peer_addr()?; let detail = ConnDetail { protocol: ConnProtocol::Tcp, + alpn: None, local, peer, addr, @@ -162,6 +163,7 @@ mod tls { let peer = stream.peer_addr()?; let detail = ConnDetail { protocol: ConnProtocol::Tcp, + alpn: None, local, peer, addr, @@ -185,18 +187,22 @@ mod tls { .ssl_new(&host_name) .and_then(|ssl| AsyncSslStream::new(ssl.into_inner(), tcp, pinned_key)) .map_err(|e| Error::new(ErrorKind::Other, e))?; + + Pin::new(&mut stream) + .connect() + .await + .map_err(|e| Error::new(ErrorKind::Other, e))?; + + let alpn = stream.negotiated_alpn_protocol().map(Vec::from); let detail = ConnDetail { protocol: ConnProtocol::Tcp, + alpn, local, peer, addr, proxy: is_proxy, }; - Pin::new(&mut stream) - .connect() - .await - .map_err(|e| Error::new(ErrorKind::Other, e))?; Ok(HttpStream::new(MixStream::Https(stream), detail)) }) } diff --git a/ylong_http_client/src/async_impl/interceptor/mod.rs b/ylong_http_client/src/async_impl/interceptor/mod.rs index 27edf37f7450361a36072fb975339510ba3e6d7f..ed10d32c8ac8211515be2112f5c1c9123490eba5 100644 --- a/ylong_http_client/src/async_impl/interceptor/mod.rs +++ b/ylong_http_client/src/async_impl/interceptor/mod.rs @@ -36,6 +36,7 @@ pub enum ConnProtocol { pub struct ConnDetail { /// Transport layer protocol type. pub(crate) protocol: ConnProtocol, + pub(crate) alpn: Option>, /// local socket address. pub(crate) local: SocketAddr, /// peer socket address. @@ -71,6 +72,11 @@ impl ConnDetail { pub fn proxy(&self) -> bool { self.proxy } + + /// Whether to use tls. + pub fn alpn(&self) -> Option<&[u8]> { + self.alpn.as_deref() + } } /// Network interceptor. diff --git a/ylong_http_client/src/async_impl/pool.rs b/ylong_http_client/src/async_impl/pool.rs index a7d0e76cd8e6e7c037ea8d3967e5b43a72590d00..bb67a0049ea5b63ca2808f8fd149d4b07d95f05c 100644 --- a/ylong_http_client/src/async_impl/pool.rs +++ b/ylong_http_client/src/async_impl/pool.rs @@ -11,16 +11,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error; -use std::future::Future; use std::mem::take; use std::sync::{Arc, Mutex}; use ylong_http::request::uri::Uri; +use crate::async_impl::connector::ConnInfo; use crate::async_impl::Connector; use crate::error::{ErrorKind, HttpClientError}; use crate::runtime::{AsyncRead, AsyncWrite}; +#[cfg(feature = "http2")] +use crate::util::config::H2Config; use crate::util::config::{HttpConfig, HttpVersion}; use crate::util::dispatcher::{Conn, ConnDispatcher, Dispatcher}; use crate::util::pool::{Pool, PoolKey}; @@ -48,16 +49,15 @@ impl ConnPool { self.pool .get(key, Conns::new) - .conn(self.config.clone(), self.connector.clone().connect(uri)) + .conn(self.config.clone(), self.connector.clone(), uri) .await } } pub(crate) struct Conns { list: Arc>>>, - #[cfg(feature = "http2")] - h2_occupation: Arc>, + h2_conn: Arc>>>, } impl Conns { @@ -66,7 +66,7 @@ impl Conns { list: Arc::new(Mutex::new(Vec::new())), #[cfg(feature = "http2")] - h2_occupation: Arc::new(crate::runtime::AsyncMutex::new(())), + h2_conn: Arc::new(crate::runtime::AsyncMutex::new(Vec::with_capacity(1))), } } } @@ -77,67 +77,154 @@ impl Clone for Conns { list: self.list.clone(), #[cfg(feature = "http2")] - h2_occupation: self.h2_occupation.clone(), + h2_conn: self.h2_conn.clone(), } } } -impl Conns { - async fn conn( - &self, +impl Conns { + async fn conn( + &mut self, config: HttpConfig, - connect_fut: F, + connector: Arc, + url: &Uri, ) -> Result, HttpClientError> where - F: Future>, - E: Into>, + C: Connector, { + #[cfg(feature = "http2")] + use ylong_http::request::uri::Scheme; + match config.version { #[cfg(feature = "http2")] - HttpVersion::Http2PriorKnowledge => { + HttpVersion::Http2 => { { // The lock `h2_occupation` is used to prevent multiple coroutines from sending // Requests at the same time under concurrent conditions, // resulting in the creation of multiple tcp connections - let _lock = self.h2_occupation.lock().await; - if let Some(conn) = self.get_exist_conn() { + let mut lock = self.h2_conn.lock().await; + + if let Some(conn) = Self::exist_h2_conn(&mut lock) { return Ok(conn); } - // create tcp connection. - let dispatcher = ConnDispatcher::http2( + let stream = connector + .connect(url) + .await + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; + let details = stream.conn_detail(); + let tls = if let Some(scheme) = url.scheme() { + *scheme == Scheme::HTTPS + } else { + false + }; + match details.alpn() { + None if tls => { + return err_from_msg!(Connect, "The peer does not support http/2.") + } + Some(protocol) if protocol != b"h2" => { + return err_from_msg!( + Connect, + "Alpn negotiate a wrong protocol version." + ) + } + _ => {} + } + + Ok(Self::dispatch_h2_conn( config.http2_config, - connect_fut - .await - .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?, - ); - Ok(self.dispatch_conn(dispatcher)) + stream, + &mut lock, + )) } } #[cfg(feature = "http1_1")] - HttpVersion::Http1 => { - if let Some(conn) = self.get_exist_conn() { - return Ok(conn); + HttpVersion::Http1 => self.conn_h1(connector, url).await, + HttpVersion::Negotiate => { + #[cfg(all(feature = "http2", feature = "http1_1"))] + match *url.scheme().unwrap() { + Scheme::HTTPS => { + let mut lock = self.h2_conn.lock().await; + + if let Some(conn) = Self::exist_h2_conn(&mut lock) { + return Ok(conn); + } + + if let Some(conn) = self.get_exist_conn() { + return Ok(conn); + } + + let stream = connector + .connect(url) + .await + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; + let details = stream.conn_detail(); + match details.alpn() { + None => { + let dispatcher = ConnDispatcher::http1(stream); + Ok(self.dispatch_h1_conn(dispatcher)) + } + Some(protocol) => { + if protocol == b"http/1.1" { + let dispatcher = ConnDispatcher::http1(stream); + Ok(self.dispatch_h1_conn(dispatcher)) + } else if protocol == b"h2" { + Ok(Self::dispatch_h2_conn( + config.http2_config, + stream, + &mut lock, + )) + } else { + err_from_msg!( + Connect, + "Alpn negotiate a wrong protocol version." + ) + } + } + } + } + Scheme::HTTP => self.conn_h1(connector, url).await, } - let dispatcher = ConnDispatcher::http1( - connect_fut - .await - .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?, - ); - Ok(self.dispatch_conn(dispatcher)) + + #[cfg(all(feature = "http1_1", not(feature = "http2")))] + self.conn_h1(connector, url).await } - #[cfg(not(feature = "http1_1"))] - HttpVersion::Http11 => Err(HttpClientError::from_str( - ErrorKind::Connect, - "Invalid HTTP VERSION", - )), } } - fn dispatch_conn(&self, dispatcher: ConnDispatcher) -> Conn { + async fn conn_h1(&self, connector: Arc, url: &Uri) -> Result, HttpClientError> + where + C: Connector, + { + if let Some(conn) = self.get_exist_conn() { + return Ok(conn); + } + let dispatcher = ConnDispatcher::http1( + connector + .connect(url) + .await + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?, + ); + Ok(self.dispatch_h1_conn(dispatcher)) + } + + fn dispatch_h1_conn(&self, dispatcher: ConnDispatcher) -> Conn { // We must be able to get the `Conn` here. let conn = dispatcher.dispatch().unwrap(); let mut list = self.list.lock().unwrap(); list.push(dispatcher); + + conn + } + + #[cfg(feature = "http2")] + fn dispatch_h2_conn( + config: H2Config, + stream: S, + lock: &mut crate::runtime::MutexGuard>>, + ) -> Conn { + let dispatcher = ConnDispatcher::http2(config, stream); + let conn = dispatcher.dispatch().unwrap(); + lock.push(dispatcher); conn } @@ -158,4 +245,19 @@ impl Conns { } conn } + + #[cfg(feature = "http2")] + fn exist_h2_conn( + lock: &mut crate::runtime::MutexGuard>>, + ) -> Option> { + if let Some(dispatcher) = lock.pop() { + if !dispatcher.is_shutdown() { + if let Some(conn) = dispatcher.dispatch() { + lock.push(dispatcher); + return Some(conn); + } + } + } + None + } } diff --git a/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs index 8ee6dca6318a8ea1c2947b394cc611598175eb79..75aa3ce0928f2d70a19589ec39dad5a4f87ba45c 100644 --- a/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs +++ b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs @@ -46,6 +46,10 @@ impl AsyncSslStream { // SAFETY: unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0.get_mut().stream) } } + + pub(crate) fn negotiated_alpn_protocol(&self) -> Option<&[u8]> { + self.0.ssl().negotiated_alpn_protocol() + } } impl AsyncSslStream diff --git a/ylong_http_client/src/lib.rs b/ylong_http_client/src/lib.rs index ae871a9081b8d837e292b561309641ab970608ff..6feeb2027ca392e8c6b1c614cc1ce9a1e789ae69 100644 --- a/ylong_http_client/src/lib.rs +++ b/ylong_http_client/src/lib.rs @@ -73,7 +73,7 @@ pub(crate) mod runtime { spawn, sync::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex as AsyncMutex, + Mutex as AsyncMutex, MutexGuard, }, }; #[cfg(all(feature = "tokio_base", feature = "async"))] @@ -94,7 +94,7 @@ pub(crate) mod runtime { spawn, sync::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex as AsyncMutex, + Mutex as AsyncMutex, MutexGuard, }, }; diff --git a/ylong_http_client/src/util/c_openssl/adapter.rs b/ylong_http_client/src/util/c_openssl/adapter.rs index ad77324b31a12dcb75de6eecd0bae516f3172d83..06bc44302fff905f531f443691461829bdd7c610 100644 --- a/ylong_http_client/src/util/c_openssl/adapter.rs +++ b/ylong_http_client/src/util/c_openssl/adapter.rs @@ -256,42 +256,24 @@ impl TlsConfigBuilder { self } - /// Sets the protocols to sent to the server for Application Layer Protocol - /// Negotiation (ALPN). - /// - /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. - /// - /// # Examples - /// ``` - /// use ylong_http_client::TlsConfigBuilder; - /// - /// let protocols = b"\x06spdy/1\x08http/1.1"; - /// let builder = TlsConfigBuilder::new().alpn_protos(protocols); - /// ``` - pub fn alpn_protos(mut self, protocols: &[u8]) -> Self { + // Sets the protocols to sent to the server for Application Layer Protocol + // Negotiation (ALPN). + // + // Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + #[cfg(feature = "http2")] + pub(crate) fn alpn_protos(mut self, protocols: &[u8]) -> Self { self.inner = self .inner .and_then(|mut builder| builder.set_alpn_protos(protocols).map(|_| builder)); self } - /// Sets the protocols to sent to the server for Application Layer Protocol - /// Negotiation (ALPN). - /// - /// This method is based on `openssl::SslContextBuilder::set_alpn_protos`. - /// - /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. - /// - /// # Examples - /// ``` - /// use ylong_http_client::{AlpnProtocol, AlpnProtocolList, TlsConfigBuilder}; - /// - /// let protocols = AlpnProtocolList::new() - /// .extend(AlpnProtocol::SPDY1) - /// .extend(AlpnProtocol::HTTP11); - /// let builder = TlsConfigBuilder::new().alpn_protos(protocols.as_slice()); - /// ``` - pub fn alpn_proto_list(mut self, list: AlpnProtocolList) -> Self { + // Sets the protocols to sent to the server for Application Layer Protocol + // Negotiation (ALPN). + // + // This method is based on `openssl::SslContextBuilder::set_alpn_protos`. + // Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + pub(crate) fn alpn_proto_list(mut self, list: AlpnProtocolList) -> Self { self.inner = self .inner .and_then(|mut builder| builder.set_alpn_protos(list.as_slice()).map(|_| builder)); diff --git a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs index c8f435812c9b4fa9a7309b5bdcd24286d3080e9d..09baff8690a3b72cf83f44a5ba4f7a6b8a6eb55a 100644 --- a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs +++ b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs @@ -79,11 +79,18 @@ extern "C" { /// Client sets the list of protocols available to be negotiated. pub(crate) fn SSL_CTX_set_alpn_protos( - s: *mut SSL_CTX, + ctx: *mut SSL_CTX, data: *const c_uchar, len: c_uint, ) -> c_int; + /// returns the selected protocol. It is not NUL-terminated. + pub(crate) fn SSL_get0_alpn_selected( + ssl: *const SSL, + data: *mut *const c_uchar, + len: *mut c_uint, + ); + /// Sets/replaces the certificate verification storage of ctx to/with store. /// If another X509_STORE object is currently set in ctx, it will be /// X509_STORE_free()ed. diff --git a/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs b/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs index 1f65d12706a979c03214e4ee1b95a7470b4777c2..f9820c7790cfaee18891b7d5f2cd5804cc0e73ce 100644 --- a/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs +++ b/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs @@ -12,9 +12,11 @@ // limitations under the License. use core::{cmp, ffi, fmt, str}; -use std::ffi::CString; +use std::ffi::{c_uchar, CString}; #[cfg(feature = "sync")] use std::io::{Read, Write}; +use std::ptr::null; +use std::slice::from_raw_parts; use libc::{c_char, c_int, c_long, c_void}; @@ -35,7 +37,7 @@ use crate::c_openssl::x509::{ }; use crate::util::c_openssl::check_ptr; use crate::util::c_openssl::error::ErrorStack; -use crate::util::c_openssl::ffi::ssl::{SSL_free, SSL_new, SSL}; +use crate::util::c_openssl::ffi::ssl::{SSL_free, SSL_get0_alpn_selected, SSL_new, SSL}; use crate::util::c_openssl::foreign::Foreign; foreign_type!( @@ -140,6 +142,19 @@ impl SslRef { Err(_) => param.set_host(host_name), } } + + pub(crate) fn negotiated_alpn_protocol(&self) -> Option<&[u8]> { + let mut data = null() as *const c_uchar; + let mut len = 0_u32; + unsafe { + SSL_get0_alpn_selected(self.as_ptr(), &mut data, &mut len); + if data.is_null() { + None + } else { + Some(from_raw_parts(data, len as usize)) + } + } + } } impl fmt::Debug for SslRef { diff --git a/ylong_http_client/src/util/config/http.rs b/ylong_http_client/src/util/config/http.rs index fc5e36889ef1ac9234963a0afe93ca032a990442..c89e448a6aefec8b159042d914ac8c19d65059e1 100644 --- a/ylong_http_client/src/util/config/http.rs +++ b/ylong_http_client/src/util/config/http.rs @@ -26,7 +26,7 @@ impl HttpConfig { /// Creates a new, default `HttpConfig`. pub(crate) fn new() -> Self { Self { - version: HttpVersion::Http1, + version: HttpVersion::Negotiate, #[cfg(feature = "http2")] http2_config: http2::H2Config::new(), @@ -47,8 +47,11 @@ pub(crate) enum HttpVersion { Http1, #[cfg(feature = "http2")] - /// Enforce `HTTP/2.0` requests without `HTTP/1.1` Upgrade. - Http2PriorKnowledge, + /// Enforce `HTTP/2.0` requests without `HTTP/1.1` Upgrade or ALPN. + Http2, + + /// Negotiate the protocol version through the ALPN. + Negotiate, } #[cfg(feature = "http2")] diff --git a/ylong_http_client/src/util/config/mod.rs b/ylong_http_client/src/util/config/mod.rs index 1b21d5dfa2d2dff68c89c0edccf9d08bf05fd841..66b79212321630267a763e645d4628ae8c140150 100644 --- a/ylong_http_client/src/util/config/mod.rs +++ b/ylong_http_client/src/util/config/mod.rs @@ -18,14 +18,15 @@ mod settings; pub(crate) use client::ClientConfig; pub(crate) use connector::ConnectorConfig; +#[cfg(feature = "http2")] +pub(crate) use http::http2::H2Config; pub(crate) use http::{HttpConfig, HttpVersion}; pub use settings::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; - -#[cfg(feature = "__tls")] +#[cfg(feature = "__c_openssl")] pub(crate) mod tls; -#[cfg(feature = "http2")] -pub(crate) use http::http2::H2Config; -#[cfg(feature = "__tls")] -pub use tls::{AlpnProtocol, AlpnProtocolList, CertVerifier, ServerCerts}; +#[cfg(feature = "__c_openssl")] +pub(crate) use tls::{AlpnProtocol, AlpnProtocolList}; +#[cfg(feature = "__c_openssl")] +pub use tls::{CertVerifier, ServerCerts}; #[cfg(feature = "tls_rust_ssl")] pub use tls::{Certificate, PrivateKey, TlsConfig, TlsConfigBuilder, TlsFileType, TlsVersion}; diff --git a/ylong_http_client/src/util/config/tls/alpn/mod.rs b/ylong_http_client/src/util/config/tls/alpn/mod.rs index d6c7dd3922169265a62b6a65f92052c7b6a59921..2c258264a0ed59d4b861519505f52ac02261525e 100644 --- a/ylong_http_client/src/util/config/tls/alpn/mod.rs +++ b/ylong_http_client/src/util/config/tls/alpn/mod.rs @@ -11,23 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// TLS Application-Layer Protocol Negotiation (ALPN) Protocol is defined in -/// [`RFC7301`]. `AlpnProtocol` contains some protocols used in HTTP, which -/// registered in [`IANA`]. -/// -/// [`RFC7301`]: https://www.rfc-editor.org/rfc/rfc7301.html#section-3 -/// [`IANA`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids -/// -/// # Examples -/// ``` -/// use ylong_http_client::AlpnProtocol; -/// -/// let alpn = AlpnProtocol::HTTP11; -/// assert_eq!(alpn.as_use_bytes(), b"\x08http/1.1"); -/// assert_eq!(alpn.id_sequence(), b"http/1.1"); -/// ``` +// TLS Application-Layer Protocol Negotiation (ALPN) Protocol is defined in +// [`RFC7301`]. `AlpnProtocol` contains some protocols used in HTTP, which +// registered in [`IANA`]. +// +// [`RFC7301`]: https://www.rfc-editor.org/rfc/rfc7301.html#section-3 +// [`IANA`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct AlpnProtocol(Inner); +pub(crate) struct AlpnProtocol(Inner); #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Inner { @@ -46,51 +37,51 @@ impl AlpnProtocol { /// `HTTP/0.9` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const HTTP09: Self = Self(Inner::HTTP09); + pub(crate) const HTTP09: Self = Self(Inner::HTTP09); /// `HTTP/1.0` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const HTTP10: Self = Self(Inner::HTTP10); + pub(crate) const HTTP10: Self = Self(Inner::HTTP10); /// `HTTP/1.1` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const HTTP11: Self = Self(Inner::HTTP11); + pub(crate) const HTTP11: Self = Self(Inner::HTTP11); /// `SPDY/1` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const SPDY1: Self = Self(Inner::SPDY1); + pub(crate) const SPDY1: Self = Self(Inner::SPDY1); /// `SPDY/2` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const SPDY2: Self = Self(Inner::SPDY2); + pub(crate) const SPDY2: Self = Self(Inner::SPDY2); /// `SPDY/3` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const SPDY3: Self = Self(Inner::SPDY3); + pub(crate) const SPDY3: Self = Self(Inner::SPDY3); /// `HTTP/2 over TLS` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const H2: Self = Self(Inner::H2); + pub(crate) const H2: Self = Self(Inner::H2); /// `HTTP/2 over TCP` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const H2C: Self = Self(Inner::H2C); + pub(crate) const H2C: Self = Self(Inner::H2C); /// `HTTP/3` in [`IANA Registration`]. /// /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub const H3: Self = Self(Inner::H3); + pub(crate) const H3: Self = Self(Inner::H3); /// Gets ALPN “wire format”, which consists protocol name prefixed by its /// byte length. - pub fn as_use_bytes(&self) -> &[u8] { + pub(crate) fn wire_format_bytes(&self) -> &[u8] { match *self { AlpnProtocol::HTTP09 => b"\x08http/0.9", AlpnProtocol::HTTP10 => b"\x08http/1.0", @@ -103,31 +94,16 @@ impl AlpnProtocol { AlpnProtocol::H3 => b"\x02h3", } } - - /// Gets ALPN protocol name, which also called identification sequence. - pub fn id_sequence(&self) -> &[u8] { - &self.as_use_bytes()[1..] - } } /// `AlpnProtocolList` consists of a sequence of supported protocol names /// prefixed by their byte length. -/// -/// # Examples -/// ``` -/// use ylong_http_client::{AlpnProtocol, AlpnProtocolList}; -/// -/// let list = AlpnProtocolList::new() -/// .extend(AlpnProtocol::SPDY1) -/// .extend(AlpnProtocol::HTTP11); -/// assert_eq!(list.as_slice(), b"\x06spdy/1\x08http/1.1"); -/// ``` #[derive(Debug, Default)] -pub struct AlpnProtocolList(Vec); +pub(crate) struct AlpnProtocolList(Vec); impl AlpnProtocolList { /// Creates a new `AlpnProtocolList`. - pub fn new() -> Self { + pub(crate) fn new() -> Self { AlpnProtocolList(vec![]) } @@ -136,20 +112,14 @@ impl AlpnProtocolList { } /// Adds an `AlpnProtocol`. - pub fn extend(mut self, protocol: AlpnProtocol) -> Self { - self.extend_from_slice(protocol.as_use_bytes()); + pub(crate) fn extend(mut self, protocol: AlpnProtocol) -> Self { + self.extend_from_slice(protocol.wire_format_bytes()); self } - /// Gets `Vec` of ALPN “wire format”, which consists of a sequence of - /// supported protocol names prefixed by their byte length. - pub fn into_bytes(self) -> Vec { - self.0 - } - /// Gets `&[u8]` of ALPN “wire format”, which consists of a sequence of /// supported protocol names prefixed by their byte length. - pub fn as_slice(&self) -> &[u8] { + pub(crate) fn as_slice(&self) -> &[u8] { self.0.as_slice() } } @@ -158,23 +128,23 @@ impl AlpnProtocolList { mod ut_alpn { use crate::util::{AlpnProtocol, AlpnProtocolList}; - /// UT test cases for `AlpnProtocol::as_use_bytes`. + /// UT test cases for `AlpnProtocol::wire_format_bytes`. /// /// # Brief /// 1. Creates a `AlpnProtocol`. - /// 2. Gets `&[u8]` by AlpnProtocol::as_use_bytes. + /// 2. Gets `&[u8]` by AlpnProtocol::wire_format_bytes. /// 3. Checks whether the result is correct. #[test] fn ut_alpn_as_use_bytes() { - assert_eq!(AlpnProtocol::HTTP09.as_use_bytes(), b"\x08http/0.9"); - assert_eq!(AlpnProtocol::HTTP10.as_use_bytes(), b"\x08http/1.0"); - assert_eq!(AlpnProtocol::HTTP11.as_use_bytes(), b"\x08http/1.1"); - assert_eq!(AlpnProtocol::SPDY1.as_use_bytes(), b"\x06spdy/1"); - assert_eq!(AlpnProtocol::SPDY2.as_use_bytes(), b"\x06spdy/2"); - assert_eq!(AlpnProtocol::SPDY3.as_use_bytes(), b"\x06spdy/3"); - assert_eq!(AlpnProtocol::H2.as_use_bytes(), b"\x02h2"); - assert_eq!(AlpnProtocol::H2C.as_use_bytes(), b"\x03h2c"); - assert_eq!(AlpnProtocol::H3.as_use_bytes(), b"\x02h3"); + assert_eq!(AlpnProtocol::HTTP09.wire_format_bytes(), b"\x08http/0.9"); + assert_eq!(AlpnProtocol::HTTP10.wire_format_bytes(), b"\x08http/1.0"); + assert_eq!(AlpnProtocol::HTTP11.wire_format_bytes(), b"\x08http/1.1"); + assert_eq!(AlpnProtocol::SPDY1.wire_format_bytes(), b"\x06spdy/1"); + assert_eq!(AlpnProtocol::SPDY2.wire_format_bytes(), b"\x06spdy/2"); + assert_eq!(AlpnProtocol::SPDY3.wire_format_bytes(), b"\x06spdy/3"); + assert_eq!(AlpnProtocol::H2.wire_format_bytes(), b"\x02h2"); + assert_eq!(AlpnProtocol::H2C.wire_format_bytes(), b"\x03h2c"); + assert_eq!(AlpnProtocol::H3.wire_format_bytes(), b"\x02h3"); } /// UT test cases for `AlpnProtocol::clone`. @@ -188,17 +158,6 @@ mod ut_alpn { assert_eq!(AlpnProtocol::HTTP09, AlpnProtocol::HTTP09.clone()); } - /// UT test cases for `AlpnProtocol::id_sequence`. - /// - /// # Brief - /// 1. Creates a `AlpnProtocol`. - /// 2. Gets `&[u8]` by AlpnProtocol::id_sequence. - /// 3. Checks whether the result is correct. - #[test] - fn ut_alpn_id_sequence() { - assert_eq!(AlpnProtocol::HTTP09.id_sequence(), b"http/0.9"); - } - /// UT test cases for `AlpnProtocolList::new`. /// /// # Brief @@ -251,21 +210,4 @@ mod ut_alpn { b"\x08http/0.9" ); } - - /// UT test cases for `AlpnProtocolList::to_bytes`. - /// - /// # Brief - /// 1. Creates a `AlpnProtocolList` and adds several `AlpnProtocol`s. - /// 2. Gets bytes by `AlpnProtocolList::to_bytes`. - /// 3. Checks whether the result is correct. - #[test] - fn ut_alpn_list_to_bytes() { - assert_eq!( - AlpnProtocolList::new() - .extend(AlpnProtocol::SPDY1) - .extend(AlpnProtocol::HTTP11) - .into_bytes(), - b"\x06spdy/1\x08http/1.1".to_vec() - ); - } } diff --git a/ylong_http_client/src/util/config/tls/mod.rs b/ylong_http_client/src/util/config/tls/mod.rs index 6a0353da98662a67b1db13b44dd9507707ece524..43f439f9795be8edefdd723c66d08d1c51ce93f3 100644 --- a/ylong_http_client/src/util/config/tls/mod.rs +++ b/ylong_http_client/src/util/config/tls/mod.rs @@ -12,7 +12,7 @@ // limitations under the License. mod alpn; -pub use alpn::{AlpnProtocol, AlpnProtocolList}; +pub(crate) use alpn::{AlpnProtocol, AlpnProtocolList}; mod verifier; pub(crate) use verifier::DefaultCertVerifier; diff --git a/ylong_http_client/src/util/config/tls/verifier/mod.rs b/ylong_http_client/src/util/config/tls/verifier/mod.rs index 812824d69790df09c4ff1f1755487b5dd3656e97..f88ca138d0f137dae8ca07d64e5cb0d865dbb94c 100644 --- a/ylong_http_client/src/util/config/tls/verifier/mod.rs +++ b/ylong_http_client/src/util/config/tls/verifier/mod.rs @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(feature = "__c_openssl")] mod openssl; -#[cfg(feature = "__c_openssl")] pub use self::openssl::ServerCerts; /// used to custom verify certs diff --git a/ylong_http_client/src/util/dispatcher.rs b/ylong_http_client/src/util/dispatcher.rs index 0ae3ef220b62a97bf5b0c42a6da18bd71ab8d09e..d25ce2f5a7719ae151c46ccbbb5c844e65925214 100644 --- a/ylong_http_client/src/util/dispatcher.rs +++ b/ylong_http_client/src/util/dispatcher.rs @@ -154,6 +154,7 @@ pub(crate) mod http2 { use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; + use ylong_http::error::HttpError; use ylong_http::h2::{ ErrorCode, Frame, FrameDecoder, FrameEncoder, FrameFlags, Goaway, H2Error, Payload, Settings, SettingsBuilder, @@ -166,6 +167,7 @@ pub(crate) mod http2 { use crate::util::config::H2Config; use crate::util::dispatcher::{ConnDispatcher, Dispatcher}; use crate::util::h2::{ConnManager, FlowControl, RecvData, RequestWrapper, SendData, Streams}; + use crate::ErrorKind::Request; use crate::{ErrorKind, HttpClientError}; const DEFAULT_MAX_STREAM_ID: u32 = u32::MAX >> 1; @@ -461,9 +463,7 @@ pub(crate) mod http2 { None => err_from_msg!(Request, "Response Receiver Closed !"), Some(message) => match message { RespMessage::Output(frame) => Ok(frame), - RespMessage::OutputExit(_e) => { - err_from_msg!(Request, "Response Recv Failed !") - } + RespMessage::OutputExit(e) => Err(dispatch_client_error(e)), }, } @@ -472,9 +472,7 @@ pub(crate) mod http2 { Err(err) => Err(HttpClientError::from_error(ErrorKind::Request, err)), Ok(message) => match message { RespMessage::Output(frame) => Ok(frame), - RespMessage::OutputExit(_e) => { - err_from_msg!(Request, "Response Recv Failed !") - } + RespMessage::OutputExit(e) => Err(dispatch_client_error(e)), }, } } @@ -497,9 +495,7 @@ pub(crate) mod http2 { } Poll::Ready(Some(message)) => match message { RespMessage::Output(frame) => Poll::Ready(Ok(frame)), - RespMessage::OutputExit(_e) => { - Poll::Ready(err_from_msg!(Request, "H2 io manager occurs error !")) - } + RespMessage::OutputExit(e) => Poll::Ready(Err(dispatch_client_error(e))), }, Poll::Pending => Poll::Pending, } @@ -511,9 +507,7 @@ pub(crate) mod http2 { } Poll::Ready(Ok(message)) => match message { RespMessage::Output(frame) => Poll::Ready(Ok(frame)), - RespMessage::OutputExit(_e) => { - Poll::Ready(err_from_msg!(Request, "H2 io manager occurs error !")) - } + RespMessage::OutputExit(e) => Poll::Ready(Err(dispatch_client_error(e))), }, Poll::Pending => Poll::Pending, } @@ -557,6 +551,21 @@ pub(crate) mod http2 { DispatchErrorKind::H2(err) } } + + pub(crate) fn dispatch_client_error(dispatch_error: DispatchErrorKind) -> HttpClientError { + match dispatch_error { + DispatchErrorKind::H2(e) => HttpClientError::from_error(Request, HttpError::from(e)), + DispatchErrorKind::Io(e) => { + HttpClientError::from_io_error(Request, std::io::Error::from(e)) + } + DispatchErrorKind::ChannelClosed => { + HttpClientError::from_str(Request, "Coroutine channel closed.") + } + DispatchErrorKind::Disconnect => { + HttpClientError::from_str(Request, "remote peer closed.") + } + } + } } #[cfg(test)] diff --git a/ylong_http_client/src/util/mod.rs b/ylong_http_client/src/util/mod.rs index c8860eb0aaeb1ebd015cde1ba92c90fe882147e5..58a5562fd61b4f2d3d3faee4c97f53314cb1cd37 100644 --- a/ylong_http_client/src/util/mod.rs +++ b/ylong_http_client/src/util/mod.rs @@ -45,8 +45,10 @@ pub use c_openssl::{ Cert, Certificate, PubKeyPins, PubKeyPinsBuilder, TlsConfig, TlsConfigBuilder, TlsFileType, TlsVersion, }; +#[cfg(feature = "__c_openssl")] +pub(crate) use config::{AlpnProtocol, AlpnProtocolList}; #[cfg(feature = "__tls")] -pub use config::{AlpnProtocol, AlpnProtocolList, CertVerifier, ServerCerts}; +pub use config::{CertVerifier, ServerCerts}; pub use config::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; #[cfg(all(feature = "async", feature = "ylong_base", feature = "http2"))] pub(crate) use h2::{split, Reader, Writer}; diff --git a/ylong_http_client/tests/common/mod.rs b/ylong_http_client/tests/common/mod.rs index 18608a696b997b61202daed9a51b4b596cf3886d..75c728396815675f8de74ead4549963331ea0c51 100644 --- a/ylong_http_client/tests/common/mod.rs +++ b/ylong_http_client/tests/common/mod.rs @@ -189,6 +189,11 @@ macro_rules! start_http_server { acceptor .set_certificate_chain_file("tests/file/cert.pem") .expect("Set cert error"); + acceptor.set_alpn_protos(b"\x08http/1.1").unwrap(); + acceptor.set_alpn_select_callback(|_, client| { + openssl::ssl::select_next_proto(b"\x08http/1.1", client).ok_or(openssl::ssl::AlpnError::NOACK) + }); + let acceptor = acceptor.build(); let (stream, _) = listener.accept().await.expect("TCP listener accept error"); @@ -285,7 +290,6 @@ macro_rules! set_server_fn { $( $method => { assert_eq!($method, request.method().as_str(), "Assert request method failed"); - assert_eq!( "/", request.uri().to_string(),