use hyper::service::HttpService; use std::future::Future; use std::marker::PhantomPinned; use std::mem::MaybeUninit; use std::pin::Pin; use std::task::{ready, Context, Poll}; use std::{error::Error as StdError, io, time::Duration};
use bytes::Bytes; use http::{Request, Response}; use http_body::Body; use hyper::{
body::Incoming,
rt::{Read, ReadBuf, Timer, Write},
service::Service,
};
#[cfg(feature = "http1")] use hyper::server::conn::http1;
#[cfg(feature = "http2")] use hyper::{rt::bounds::Http2ServerConnExec, server::conn::http2};
#[cfg(any(not(feature = "http2"), not(feature = "http1")))] use std::marker::PhantomData;
use pin_project_lite::pin_project;
usecrate::common::rewind::Rewind;
type Error = Box<dyn std::error::Error + Send + Sync>;
/// Only accepts HTTP/2 /// /// Does not do anything if used with [`serve_connection_with_upgrades`] /// /// [`serve_connection_with_upgrades`]: Builder::serve_connection_with_upgrades #[cfg(feature = "http2")] pubfn http2_only(mutself) -> Self {
assert!(self.version.is_none()); self.version = Some(Version::H2); self
}
/// Only accepts HTTP/1 /// /// Does not do anything if used with [`serve_connection_with_upgrades`] /// /// [`serve_connection_with_upgrades`]: Builder::serve_connection_with_upgrades #[cfg(feature = "http1")] pubfn http1_only(mutself) -> Self {
assert!(self.version.is_none()); self.version = Some(Version::H1); self
}
/// Set whether HTTP/1 connections will write header names as title case at /// the socket level. /// /// This setting only affects HTTP/1 connections. HTTP/2 connections are /// not affected by this setting. /// /// Default is false. /// /// # Example /// /// ``` /// use hyper_util::{ /// rt::TokioExecutor, /// server::conn::auto, /// }; /// /// auto::Builder::new(TokioExecutor::new()) /// .title_case_headers(true); /// ``` #[cfg(feature = "http1")] pubfn title_case_headers(mutself, enabled: bool) -> Self { self.http1.title_case_headers(enabled); self
}
/// Set whether HTTP/1 connections will preserve the original case of header names. /// /// This setting only affects HTTP/1 connections. HTTP/2 connections are /// not affected by this setting. /// /// Default is false. /// /// # Example /// /// ``` /// use hyper_util::{ /// rt::TokioExecutor, /// server::conn::auto, /// }; /// /// auto::Builder::new(TokioExecutor::new()) /// .preserve_header_case(true); /// ``` #[cfg(feature = "http1")] pubfn preserve_header_case(mutself, enabled: bool) -> Self { self.http1.preserve_header_case(enabled); self
}
/// Bind a connection together with a [`Service`]. pubfn serve_connection<I, S, B>(&self, io: I, service: S) -> Connection<'_, I, S, E> where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + 'static,
E: HttpServerConnExec<S::Future, B>,
{ let state = matchself.version { #[cfg(feature = "http1")]
Some(Version::H1) => { let io = Rewind::new_buffered(io, Bytes::new()); let conn = self.http1.serve_connection(io, service);
ConnState::H1 { conn }
} #[cfg(feature = "http2")]
Some(Version::H2) => { let io = Rewind::new_buffered(io, Bytes::new()); let conn = self.http2.serve_connection(io, service);
ConnState::H2 { conn }
} #[cfg(any(feature = "http1", feature = "http2"))]
_ => ConnState::ReadVersion {
read_version: read_version(io),
builder: Cow::Borrowed(self),
service: Some(service),
},
};
Connection { state }
}
/// Bind a connection together with a [`Service`], with the ability to /// handle HTTP upgrades. This requires that the IO object implements /// `Send`. /// /// Note that if you ever want to use [`hyper::upgrade::Upgraded::downcast`] /// with this crate, you'll need to use [`hyper_util::server::conn::auto::upgrade::downcast`] /// instead. See the documentation of the latter to understand why. /// /// [`hyper_util::server::conn::auto::upgrade::downcast`]: crate::server::conn::auto::upgrade::downcast pubfn serve_connection_with_upgrades<I, S, B>(
&self,
io: I,
service: S,
) -> UpgradeableConnection<'_, I, S, E> where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + Send + 'static,
E: HttpServerConnExec<S::Future, B>,
{
UpgradeableConnection {
state: UpgradeableConnState::ReadVersion {
read_version: read_version(io),
builder: Cow::Borrowed(self),
service: Some(service),
},
}
}
}
#[derive(Copy, Clone, Debug)] enum Version {
H1,
H2,
}
impl Version { #[must_use] #[cfg(any(not(feature = "http2"), not(feature = "http1")))] pubfn unsupported(self) -> Error { matchself {
Version::H1 => Error::from("HTTP/1 is not supported"),
Version::H2 => Error::from("HTTP/2 is not supported"),
}
}
}
impl<I> Future for ReadVersion<I> where
I: Read + Unpin,
{ type Output = io::Result<(Version, Rewind<I>)>;
fn poll(self: Pin<&mutSelf>, cx: &mut Context<'_>) -> Poll<Self::Output> { let this = self.project(); if *this.cancelled { return Poll::Ready(Err(io::Error::new(io::ErrorKind::Interrupted, "Cancelled")));
}
letmut buf = ReadBuf::uninit(&mut *this.buf); // SAFETY: `this.filled` tracks how many bytes have been read (and thus initialized) and // we're only advancing by that many. unsafe {
buf.unfilled().advance(*this.filled);
};
// We start as H2 and switch to H1 as soon as we don't have the preface. while buf.filled().len() < H2_PREFACE.len() { let len = buf.filled().len();
ready!(Pin::new(this.io.as_mut().unwrap()).poll_read(cx, buf.unfilled()))?;
*this.filled = buf.filled().len();
// We starts as H2 and switch to H1 when we don't get the preface. if buf.filled().len() == len
|| buf.filled()[len..] != H2_PREFACE[len..buf.filled().len()]
{
*this.version = Version::H1; break;
}
}
let io = this.io.take().unwrap(); let buf = buf.filled().to_vec();
Poll::Ready(Ok((
*this.version,
Rewind::new_buffered(io, Bytes::from(buf)),
)))
}
}
pin_project! { /// A [`Future`](core::future::Future) representing an HTTP/1 connection, returned from /// [`Builder::serve_connection`](struct.Builder.html#method.serve_connection). /// /// To drive HTTP on this connection this future **must be polled**, typically with /// `.await`. If it isn't polled, no progress will be made on this connection. #[must_use = "futures do nothing unless polled"] pubstruct Connection<'a, I, S, E> where
S: HttpService<Incoming>,
{ #[pin]
state: ConnState<'a, I, S, E>,
}
}
// A custom COW, since the libstd is has ToOwned bounds that are too eager. enum Cow<'a, T> {
Borrowed(&'a T),
Owned(T),
}
impl<I, S, E, B> Connection<'_, I, S, E> where
S: HttpService<Incoming, ResBody = B>,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
E: HttpServerConnExec<S::Future, B>,
{ /// Start a graceful shutdown process for this connection. /// /// This `Connection` should continue to be polled until shutdown can finish. /// /// # Note /// /// This should only be called while the `Connection` future is still pending. If called after /// `Connection::poll` has resolved, this does nothing. pubfn graceful_shutdown(self: Pin<&mutSelf>) { matchself.project().state.project() {
ConnStateProj::ReadVersion { read_version, .. } => read_version.cancel(), #[cfg(feature = "http1")]
ConnStateProj::H1 { conn } => conn.graceful_shutdown(), #[cfg(feature = "http2")]
ConnStateProj::H2 { conn } => conn.graceful_shutdown(), #[cfg(any(not(feature = "http1"), not(feature = "http2")))]
_ => unreachable!(),
}
}
pin_project! { /// An upgradable [`Connection`], returned by /// [`Builder::serve_upgradable_connection`](struct.Builder.html#method.serve_connection_with_upgrades). /// /// To drive HTTP on this connection this future **must be polled**, typically with /// `.await`. If it isn't polled, no progress will be made on this connection. #[must_use = "futures do nothing unless polled"] pubstruct UpgradeableConnection<'a, I, S, E> where
S: HttpService<Incoming>,
{ #[pin]
state: UpgradeableConnState<'a, I, S, E>,
}
}
#[cfg(feature = "http1")] type Http1UpgradeableConnection<I, S> = hyper::server::conn::http1::UpgradeableConnection<I, S>;
#[cfg(not(feature = "http1"))] type Http1UpgradeableConnection<I, S> = (PhantomData<I>, PhantomData<S>);
impl<I, S, E, B> UpgradeableConnection<'_, I, S, E> where
S: HttpService<Incoming, ResBody = B>,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
E: HttpServerConnExec<S::Future, B>,
{ /// Start a graceful shutdown process for this connection. /// /// This `UpgradeableConnection` should continue to be polled until shutdown can finish. /// /// # Note /// /// This should only be called while the `Connection` future is still nothing. pending. If /// called after `UpgradeableConnection::poll` has resolved, this does nothing. pubfn graceful_shutdown(self: Pin<&mutSelf>) { matchself.project().state.project() {
UpgradeableConnStateProj::ReadVersion { read_version, .. } => read_version.cancel(), #[cfg(feature = "http1")]
UpgradeableConnStateProj::H1 { conn } => conn.graceful_shutdown(), #[cfg(feature = "http2")]
UpgradeableConnStateProj::H2 { conn } => conn.graceful_shutdown(), #[cfg(any(not(feature = "http1"), not(feature = "http2")))]
_ => unreachable!(),
}
}
/// Set whether the `date` header should be included in HTTP responses. /// /// Note that including the `date` header is recommended by RFC 7231. /// /// Default is true. pubfn auto_date_header(&mutself, enabled: bool) -> &mutSelf { self.inner.http1.auto_date_header(enabled); self
}
/// Set whether HTTP/1 connections should support half-closures. /// /// Clients can chose to shutdown their write-side while waiting /// for the server to respond. Setting this to `true` will /// prevent closing the connection immediately if `read` /// detects an EOF in the middle of a request. /// /// Default is `false`. pubfn half_close(&mutself, val: bool) -> &mutSelf { self.inner.http1.half_close(val); self
}
/// Set whether HTTP/1 connections will write header names as title case at /// the socket level. /// /// Note that this setting does not affect HTTP/2. /// /// Default is false. pubfn title_case_headers(&mutself, enabled: bool) -> &'color:red'>mutSelf { self.inner.http1.title_case_headers(enabled); self
}
/// Set whether HTTP/1 connections will silently ignored malformed header lines. /// /// If this is enabled and a header line does not start with a valid header /// name, or does not include a colon at all, the line will be silently ignored /// and no error will be reported. /// /// Default is false. pubfn ignore_invalid_headers(&mutself, enabled: bool) -> &yle='color:red'>mutSelf { self.inner.http1.ignore_invalid_headers(enabled); self
}
/// Set whether to support preserving original header cases. /// /// Currently, this will record the original cases received, and store them /// in a private extension on the `Request`. It will also look for and use /// such an extension in any provided `Response`. /// /// Since the relevant extension is still private, there is no way to /// interact with the original cases. The only effect this can have now is /// to forward the cases in a proxy-like fashion. /// /// Note that this setting does not affect HTTP/2. /// /// Default is false. pubfn preserve_header_case(&mutself, enabled: bool) -> &e='color:red'>mutSelf { self.inner.http1.preserve_header_case(enabled); self
}
/// Set the maximum number of headers. /// /// When a request is received, the parser will reserve a buffer to store headers for optimal /// performance. /// /// If server receives more headers than the buffer size, it responds to the client with /// "431 Request Header Fields Too Large". /// /// The headers is allocated on the stack by default, which has higher performance. After /// setting this value, headers will be allocated in heap memory, that is, heap memory /// allocation will occur for each request, and there will be a performance drop of about 5%. /// /// Note that this setting does not affect HTTP/2. /// /// Default is 100. pubfn max_headers(&mutself, val: usize) -> &mutSelf { self.inner.http1.max_headers(val); self
}
/// Set a timeout for reading client request headers. If a client does not /// transmit the entire header within this time, the connection is closed. /// /// Requires a [`Timer`] set by [`Http1Builder::timer`] to take effect. Panics if `header_read_timeout` is configured /// without a [`Timer`]. /// /// Pass `None` to disable. /// /// Default is currently 30 seconds, but do not depend on that. pubfn header_read_timeout(&mutself, read_timeout: impl Into<Option<Duration>>) -> &mutSelf { self.inner.http1.header_read_timeout(read_timeout); self
}
/// Set whether HTTP/1 connections should try to use vectored writes, /// or always flatten into a single buffer. /// /// Note that setting this to false may mean more copies of body data, /// but may also improve performance when an IO transport doesn't /// support vectored writes well, such as most TLS implementations. /// /// Setting this to true will force hyper to use queued strategy /// which may eliminate unnecessary cloning on some TLS backends /// /// Default is `auto`. In this mode hyper will try to guess which /// mode to use pubfn writev(&mutself, val: bool) -> &mutSelf { self.inner.http1.writev(val); self
}
/// Set the maximum buffer size for the connection. /// /// Default is ~400kb. /// /// # Panics /// /// The minimum value allowed is 8192. This method panics if the passed `max` is less than the minimum. pubfn max_buf_size(&mutself, max: usize) -> &mutSelf { self.inner.http1.max_buf_size(max); self
}
/// Aggregates flushes to better support pipelined responses. /// /// Experimental, may have bugs. /// /// Default is false. pubfn pipeline_flush(&mutself, enabled: bool) -> &mutSelf { self.inner.http1.pipeline_flush(enabled); self
}
/// Set the timer used in background tasks. pubfn timer<M>(&mutself, timer: M) -> &mutSelf where
M: Timer + Send + Sync + 'static,
{ self.inner.http1.timer(timer); self
}
/// Bind a connection together with a [`Service`]. #[cfg(feature = "http2")] pubasyncfn serve_connection<I, S, B>(&self, io: I, service: S) -> Result<()> where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + 'static,
E: HttpServerConnExec<S::Future, B>,
{ self.inner.serve_connection(io, service).await
}
/// Bind a connection together with a [`Service`]. #[cfg(not(feature = "http2"))] pubasyncfn serve_connection<I, S, B>(&self, io: I, service: S) -> Result<()> where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + 'static,
{ self.inner.serve_connection(io, service).await
}
/// Bind a connection together with a [`Service`], with the ability to /// handle HTTP upgrades. This requires that the IO object implements /// `Send`. #[cfg(feature = "http2")] pubfn serve_connection_with_upgrades<I, S, B>(
&self,
io: I,
service: S,
) -> UpgradeableConnection<'_, I, S, E> where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + Send + 'static,
E: HttpServerConnExec<S::Future, B>,
{ self.inner.serve_connection_with_upgrades(io, service)
}
}
/// Http2 part of builder. #[cfg(feature = "http2")] pubstruct Http2Builder<'a, E> {
inner: &'a mut Builder<E>,
}
/// Configures the maximum number of pending reset streams allowed before a GOAWAY will be sent. /// /// This will default to the default value set by the [`h2` crate](https://crates.io/crates/h2). /// As of v0.4.0, it is 20. /// /// See <https://github.com/hyperium/hyper/issues/2877> for more information. pubfn max_pending_accept_reset_streams(&mutself, max: impl Into<Option<usize>>) -> &mutSelf { self.inner.http2.max_pending_accept_reset_streams(max); self
}
/// Configures the maximum number of local reset streams allowed before a GOAWAY will be sent. /// /// If not set, hyper will use a default, currently of 1024. /// /// If `None` is supplied, hyper will not apply any limit. /// This is not advised, as it can potentially expose servers to DOS vulnerabilities. /// /// See <https://rustsec.org/advisories/RUSTSEC-2024-0003.html> for more information. pubfn max_local_error_reset_streams(&mutself, max: impl Into<Option<usize>>) -> &mutSelf { self.inner.http2.max_local_error_reset_streams(max); self
}
/// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2 /// stream-level flow control. /// /// Passing `None` will do nothing. /// /// If not set, hyper will use a default. /// /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE pubfn initial_stream_window_size(&mutself, sz: impl Into<Option<u32>>) -> &mutSelf { self.inner.http2.initial_stream_window_size(sz); self
}
/// Sets the max connection-level flow control for HTTP2. /// /// Passing `None` will do nothing. /// /// If not set, hyper will use a default. pubfn initial_connection_window_size(&mutself, sz: impl Into<Option<u32>>) -> &mutSelf { self.inner.http2.initial_connection_window_size(sz); self
}
/// Sets whether to use an adaptive flow control. /// /// Enabling this will override the limits set in /// `http2_initial_stream_window_size` and /// `http2_initial_connection_window_size`. pubfn adaptive_window(&mutself, enabled: bool) -> &mutSelf { self.inner.http2.adaptive_window(enabled); self
}
/// Sets the maximum frame size to use for HTTP2. /// /// Passing `None` will do nothing. /// /// If not set, hyper will use a default. pubfn max_frame_size(&mutself, sz: impl Into<Option<u32>>) -> &yle='color:red'>mutSelf { self.inner.http2.max_frame_size(sz); self
}
/// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2 /// connections. /// /// Default is 200. Passing `None` will remove any limit. /// /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_MAX_CONCURRENT_STREAMS pubfn max_concurrent_streams(&mutself, max: impl Into<Option<u32>>) -> &mutSelf { self.inner.http2.max_concurrent_streams(max); self
}
/// Sets an interval for HTTP2 Ping frames should be sent to keep a /// connection alive. /// /// Pass `None` to disable HTTP2 keep-alive. /// /// Default is currently disabled. /// /// # Cargo Feature /// pubfn keep_alive_interval(&mutself, interval: impl Into<Option<Duration>>) -> &mutSelf { self.inner.http2.keep_alive_interval(interval); self
}
/// Sets a timeout for receiving an acknowledgement of the keep-alive ping. /// /// If the ping is not acknowledged within the timeout, the connection will /// be closed. Does nothing if `http2_keep_alive_interval` is disabled. /// /// Default is 20 seconds. /// /// # Cargo Feature /// pubfn keep_alive_timeout(&mutself, timeout: Duration) -> &yle='color:red'>mutSelf { self.inner.http2.keep_alive_timeout(timeout); self
}
/// Set the maximum write buffer size for each HTTP/2 stream. /// /// Default is currently ~400KB, but may change. /// /// # Panics /// /// The value must be no larger than `u32::MAX`. pubfn max_send_buf_size(&mutself, max: usize) -> &mutSelf { self.inner.http2.max_send_buf_size(max); self
}
/// Sets the max size of received header frames. /// /// Default is currently ~16MB, but may change. pubfn max_header_list_size(&mutself, max: u32) -> &mutSelf { self.inner.http2.max_header_list_size(max); self
}
/// Set the timer used in background tasks. pubfn timer<M>(&mutself, timer: M) -> &mutSelf where
M: Timer + Send + Sync + 'static,
{ self.inner.http2.timer(timer); self
}
/// Set whether the `date` header should be included in HTTP responses. /// /// Note that including the `date` header is recommended by RFC 7231. /// /// Default is true. pubfn auto_date_header(&mutself, enabled: bool) -> &mutSelf { self.inner.http2.auto_date_header(enabled); self
}
/// Bind a connection together with a [`Service`]. pubasyncfn serve_connection<I, S, B>(&self, io: I, service: S) -> Result<()> where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + 'static,
E: HttpServerConnExec<S::Future, B>,
{ self.inner.serve_connection(io, service).await
}
/// Bind a connection together with a [`Service`], with the ability to /// handle HTTP upgrades. This requires that the IO object implements /// `Send`. pubfn serve_connection_with_upgrades<I, S, B>(
&self,
io: I,
service: S,
) -> UpgradeableConnection<'_, I, S, E> where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + Send + 'static,
E: HttpServerConnExec<S::Future, B>,
{ self.inner.serve_connection_with_upgrades(io, service)
}
}
#[cfg(test)] mod tests { usecrate::{
rt::{TokioExecutor, TokioIo},
server::conn::auto,
}; use http::{Request, Response}; use http_body::Body; use http_body_util::{BodyExt, Empty, Full}; use hyper::{body, body::Bytes, client, service::service_fn}; use std::{convert::Infallible, error::Error as StdError, net::SocketAddr, time::Duration}; use tokio::{
net::{TcpListener, TcpStream},
pin,
};
#[test] #[cfg(feature = "http1")] fn title_case_headers_configuration() { // Test title_case_headers can be set on the main builder
auto::Builder::new(TokioExecutor::new()).title_case_headers(true);
// Can be combined with other configuration
auto::Builder::new(TokioExecutor::new())
.title_case_headers(true)
.http1_only();
}
#[test] #[cfg(feature = "http1")] fn preserve_header_case_configuration() { // Test preserve_header_case can be set on the main builder
auto::Builder::new(TokioExecutor::new()).preserve_header_case(true);
// Can be combined with other configuration
auto::Builder::new(TokioExecutor::new())
.preserve_header_case(true)
.http1_only();
}
let listener_addr = listener.local_addr().unwrap();
// Spawn the task in background so that we can connect there let listen_task = tokio::spawn(asyncmove { listener.accept().await.unwrap() }); // Only connect a stream, do not send headers or anything let _stream = TcpStream::connect(listener_addr).await.unwrap();
let (stream, _) = listen_task.await.unwrap(); let stream = TokioIo::new(stream); let builder = auto::Builder::new(TokioExecutor::new()); let connection = builder.serve_connection(stream, service_fn(hello));
pin!(connection);
connection.as_mut().graceful_shutdown();
let connection_error = tokio::time::timeout(Duration::from_millis(200), connection)
.await
.expect("Connection should have finished in a timely manner after graceful shutdown.")
.expect_err("Connection should have been interrupted.");
let connection_error = connection_error
.downcast_ref::<std::io::Error>()
.expect("The error should have been `std::io::Error`.");
assert_eq!(connection_error.kind(), std::io::ErrorKind::Interrupted);
}
asyncfn connect_h1<B>(addr: SocketAddr) -> client::conn::http1::SendRequest<B> where
B: Body + Send + 'static,
B::Data: Send,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
{ let stream = TokioIo::new(TcpStream::connect(addr).await.unwrap()); let (sender, connection) = client::conn::http1::handshake(stream).await.unwrap();
tokio::spawn(connection);
sender
}
asyncfn connect_h2<B>(addr: SocketAddr) -> client::conn::http2::SendRequest<B> where
B: Body + Unpin + Send + 'static,
B::Data: Send,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
{ let stream = TokioIo::new(TcpStream::connect(addr).await.unwrap()); let (sender, connection) = client::conn::http2::Builder::new(TokioExecutor::new())
.handshake(stream)
.await
.unwrap();
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.