use std::error::Error as StdError; use std::fmt; use std::future::Future; use std::io; use std::marker::PhantomData; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::pin::Pin; use std::sync::Arc; use std::task::{self, Poll}; use std::time::Duration;
use futures_util::future::Either; use http::uri::{Scheme, Uri}; use pin_project_lite::pin_project; use tokio::net::{TcpSocket, TcpStream}; use tokio::time::Sleep; use tracing::{debug, trace, warn};
/// A connector for the `http` scheme. /// /// Performs DNS resolution in a thread pool, and then connects over TCP. /// /// # Note /// /// Sets the [`HttpInfo`](HttpInfo) value on responses, which includes /// transport information such as the remote socket address used. #[cfg_attr(docsrs, doc(cfg(feature = "tcp")))] #[derive(Clone)] pubstruct HttpConnector<R = GaiResolver> {
config: Arc<Config>,
resolver: R,
}
/// Extra information about the transport when an HttpConnector is used. /// /// # Example /// /// ``` /// # async fn doc() -> hyper::Result<()> { /// use hyper::Uri; /// use hyper::client::{Client, connect::HttpInfo}; /// /// let client = Client::new(); /// let uri = Uri::from_static("http://example.com"); /// /// let res = client.get(uri).await?; /// res /// .extensions() /// .get::<HttpInfo>() /// .map(|info| { /// println!("remote addr = {}", info.remote_addr()); /// }); /// # Ok(()) /// # } /// ``` /// /// # Note /// /// If a different connector is used besides [`HttpConnector`](HttpConnector), /// this value will not exist in the extensions. Consult that specific /// connector to see what "extra" information it might provide to responses. #[derive(Clone, Debug)] pubstruct HttpInfo {
remote_addr: SocketAddr,
local_addr: SocketAddr,
}
impl HttpConnector { /// Construct a new HttpConnector. pubfn new() -> HttpConnector {
HttpConnector::new_with_resolver(GaiResolver::new())
}
}
/* #[cfg(feature="runtime")] implHttpConnector<TokioThreadpoolGaiResolver>{ /// Construct a new HttpConnector using the `TokioThreadpoolGaiResolver`. /// /// This resolver **requires** the threadpool runtime to be used. pubfnnew_with_tokio_threadpool_resolver()->Self{ HttpConnector::new_with_resolver(TokioThreadpoolGaiResolver::new()) } }
*/
/// Option to enforce all `Uri`s have the `http` scheme. /// /// Enabled by default. #[inline] pubfn enforce_http(&mutself, is_enforced: bool) { self.config_mut().enforce_http = is_enforced;
}
/// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration. /// /// If `None`, the option will not be set. /// /// Default is `None`. #[inline] pubfn set_keepalive(&mutself, dur: Option<Duration>) { self.config_mut().keep_alive_timeout = dur;
}
/// Set that all sockets have `SO_NODELAY` set to the supplied value `nodelay`. /// /// Default is `false`. #[inline] pubfn set_nodelay(&mutself, nodelay: bool) { self.config_mut().nodelay = nodelay;
}
/// Sets the value of the SO_SNDBUF option on the socket. #[inline] pubfn set_send_buffer_size(&mutself, size: Option<usize>) { self.config_mut().send_buffer_size = size;
}
/// Sets the value of the SO_RCVBUF option on the socket. #[inline] pubfn set_recv_buffer_size(&mutself, size: Option<usize>) { self.config_mut().recv_buffer_size = size;
}
/// Set that all sockets are bound to the configured address before connection. /// /// If `None`, the sockets will not be bound. /// /// Default is `None`. #[inline] pubfn set_local_address(&mutself, addr: Option<IpAddr>) { let (v4, v6) = match addr {
Some(IpAddr::V4(a)) => (Some(a), None),
Some(IpAddr::V6(a)) => (None, Some(a)),
_ => (None, None),
};
/// Set that all sockets are bound to the configured IPv4 or IPv6 address (depending on host's /// preferences) before connection. #[inline] pubfn set_local_addresses(&mutself, addr_ipv4: Ipv4Addr, addr_ipv6: Ipv6Addr) { let cfg = self.config_mut();
/// Set the connect timeout. /// /// If a domain resolves to multiple IP addresses, the timeout will be /// evenly divided across them. /// /// Default is `None`. #[inline] pubfn set_connect_timeout(&mutself, dur: Option<Duration>) { self.config_mut().connect_timeout = dur;
}
/// Set timeout for [RFC 6555 (Happy Eyeballs)][RFC 6555] algorithm. /// /// If hostname resolves to both IPv4 and IPv6 addresses and connection /// cannot be established using preferred address family before timeout /// elapses, then connector will in parallel attempt connection using other /// address family. /// /// If `None`, parallel connection attempts are disabled. /// /// Default is 300 milliseconds. /// /// [RFC 6555]: https://tools.ietf.org/html/rfc6555 #[inline] pubfn set_happy_eyeballs_timeout(&mutself, dur: Option<Duration>) { self.config_mut().happy_eyeballs_timeout = dur;
}
/// Set that all socket have `SO_REUSEADDR` set to the supplied value `reuse_address`. /// /// Default is `false`. #[inline] pubfn set_reuse_address(&mutself, reuse_address: bool) -> &tyle='color:red'>mutSelf { self.config_mut().reuse_address = reuse_address; self
}
// private
fn config_mut(&mutself) -> &mut Config { // If the are HttpConnector clones, this will clone the inner // config. So mutating the config won't ever affect previous // clones.
Arc::make_mut(&mutself.config)
}
}
static INVALID_NOT_HTTP: &str = "invalid URL, scheme is not http"; static INVALID_MISSING_SCHEME: &str = "invalid URL, scheme is missing"; static INVALID_MISSING_HOST: &str = "invalid URL, host is missing";
// R: Debug required for now to allow adding it to debug output later... impl<R: fmt::Debug> fmt::Debug for HttpConnector<R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HttpConnector").finish()
}
}
impl<R> tower_service::Service<Uri> for HttpConnector<R> where
R: Resolve + Clone + Send + Sync + 'static,
R::Future: Send,
{ type Response = TcpStream; type Error = ConnectError; type Future = HttpConnecting<R>;
let host = match dst.host() {
Some(s) => s,
None => { return Err(ConnectError {
msg: INVALID_MISSING_HOST.into(),
cause: None,
})
}
}; let port = match dst.port() {
Some(port) => port.as_u16(),
None => { if dst.scheme() == Some(&Scheme::HTTPS) { 443
} else { 80
}
}
};
Ok((host, port))
}
impl<R> HttpConnector<R> where
R: Resolve,
{ asyncfn call_async(&mutself, dst: Uri) -> Result<TcpStream, ConnectError> { let config = &self.config;
let (host, port) = get_host_port(config, &dst)?; let host = host.trim_start_matches('[').trim_end_matches(']');
// If the host is already an IP addr (v4 or v6), // skip resolving the dns and start connecting right away. let addrs = iflet Some(addrs) = dns::SocketAddrs::try_parse(host, port) {
addrs
} else { let addrs = resolve(&mutself.resolver, dns::Name::new(host.into()))
.await
.map_err(ConnectError::dns)?; let addrs = addrs
.map(|mut addr| {
addr.set_port(port);
addr
})
.collect();
dns::SocketAddrs::new(addrs)
};
impl HttpInfo { /// Get the remote address of the transport used. pubfn remote_addr(&self) -> SocketAddr { self.remote_addr
}
/// Get the local address of the transport used. pubfn local_addr(&self) -> SocketAddr { self.local_addr
}
}
pin_project! { // Not publicly exported (so missing_docs doesn't trigger). // // We return this `Future` instead of the `Pin<Box<dyn Future>>` directly // so that users don't rely on it fitting in a `Pin<Box<dyn Future>>` slot // (and thus we can change the type in the future). #[must_use = "futures do nothing unless polled"] #[allow(missing_debug_implementations)] pubstruct HttpConnecting<R> { #[pin]
fut: BoxConnecting,
_marker: PhantomData<R>,
}
}
type ConnectResult = Result<TcpStream, ConnectError>; type BoxConnecting = Pin<Box<dyn Future<Output = ConnectResult> + Send>>;
impl<R: Resolve> Future for HttpConnecting<R> { type Output = ConnectResult;
fn connect(
addr: &SocketAddr,
config: &Config,
connect_timeout: Option<Duration>,
) -> Result<impl Future<Output = Result<TcpStream, ConnectError>>, ConnectError> { // TODO(eliza): if Tokio's `TcpSocket` gains support for setting the // keepalive timeout, it would be nice to use that instead of socket2, // and avoid the unsafe `into_raw_fd`/`from_raw_fd` dance... use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type}; use std::convert::TryInto;
let domain = Domain::for_address(*addr); let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))
.map_err(ConnectError::m("tcp open error"))?;
// When constructing a Tokio `TcpSocket` from a raw fd/socket, the user is // responsible for ensuring O_NONBLOCK is set.
socket
.set_nonblocking(true)
.map_err(ConnectError::m("tcp set_nonblocking error"))?;
bind_local_address(
&socket,
addr,
&config.local_address_ipv4,
&config.local_address_ipv6,
)
.map_err(ConnectError::m("tcp bind local error"))?;
#[cfg(unix)] let socket = unsafe { // Safety: `from_raw_fd` is only safe to call if ownership of the raw // file descriptor is transferred. Since we call `into_raw_fd` on the // socket2 socket, it gives up ownership of the fd and will not close // it, so this is safe. use std::os::unix::io::{FromRawFd, IntoRawFd};
TcpSocket::from_raw_fd(socket.into_raw_fd())
}; #[cfg(windows)] let socket = unsafe { // Safety: `from_raw_socket` is only safe to call if ownership of the raw // Windows SOCKET is transferred. Since we call `into_raw_socket` on the // socket2 socket, it gives up ownership of the SOCKET and will not close // it, so this is safe. use std::os::windows::io::{FromRawSocket, IntoRawSocket};
TcpSocket::from_raw_socket(socket.into_raw_socket())
};
let ips = pnet_datalink::interfaces()
.into_iter()
.flat_map(|i| i.ips.into_iter().map(|n| n.ip()));
for ip in ips { match ip {
IpAddr::V4(ip) if TcpListener::bind((ip, 0)).is_ok() => ip_v4 = Some(ip),
IpAddr::V6(ip) if TcpListener::bind((ip, 0)).is_ok() => ip_v6 = Some(ip),
_ => (),
}
if ip_v4.is_some() && ip_v6.is_some() { break;
}
}
let err = connect(connector, dst).await.unwrap_err();
assert_eq!(&*err.msg, super::INVALID_MISSING_SCHEME);
}
// NOTE: pnet crate that we use in this test doesn't compile on Windows #[cfg(any(target_os = "linux", target_os = "macos"))] #[tokio::test] asyncfn local_address() { use std::net::{IpAddr, TcpListener}; let _ = pretty_env_logger::try_init();
let (bind_ip_v4, bind_ip_v6) = get_local_ips(); let server4 = TcpListener::bind("127.0.0.1:0").unwrap(); let port = server4.local_addr().unwrap().port(); let server6 = TcpListener::bind(&format!("[::1]:{}", port)).unwrap();
#[test] #[cfg_attr(not(feature = "__internal_happy_eyeballs_tests"), ignore)] fn client_happy_eyeballs() { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, TcpListener}; use std::time::{Duration, Instant};
usesuper::dns; usesuper::ConnectingTcp;
let _ = pretty_env_logger::try_init(); let server4 = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = server4.local_addr().unwrap(); let _server6 = TcpListener::bind(&format!("[::1]:{}", addr.port())).unwrap(); let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let local_timeout = Duration::default(); let unreachable_v4_timeout = measure_connect(unreachable_ipv4_addr()).1; let unreachable_v6_timeout = measure_connect(unreachable_ipv6_addr()).1; let fallback_timeout = std::cmp::max(unreachable_v4_timeout, unreachable_v6_timeout)
+ Duration::from_millis(250);
// Scenarios for IPv6 -> IPv4 fallback require that host can access IPv6 network. // Otherwise, connection to "slow" IPv6 address will error-out immediately. let ipv6_accessible = measure_connect(slow_ipv6_addr()).0;
for &(hosts, family, timeout, needs_ipv6_access) in scenarios { if needs_ipv6_access && !ipv6_accessible { continue;
}
let (start, stream) = rt
.block_on(asyncmove { let addrs = hosts
.iter()
.map(|host| (host.clone(), addr.port()).into())
.collect(); let cfg = Config {
local_address_ipv4: None,
local_address_ipv6: None,
connect_timeout: None,
keep_alive_timeout: None,
happy_eyeballs_timeout: Some(fallback_timeout),
nodelay: false,
reuse_address: false,
enforce_http: false,
send_buffer_size: None,
recv_buffer_size: None,
}; let connecting_tcp = ConnectingTcp::new(dns::SocketAddrs::new(addrs), &cfg); let start = Instant::now();
Ok::<_, ConnectError>((start, ConnectingTcp::connect(connecting_tcp).await?))
})
.unwrap(); let res = if stream.peer_addr().unwrap().is_ipv4() { 4
} else { 6
}; let duration = start.elapsed();
// Allow actual duration to be +/- 150ms off. let min_duration = if timeout >= Duration::from_millis(150) {
timeout - Duration::from_millis(150)
} else {
Duration::default()
}; let max_duration = timeout + Duration::from_millis(150);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.