1#![doc = include_str!("../readme.md")]
6#![forbid(unsafe_code)]
7
8mod body;
9mod connection;
10mod driver;
11mod error;
12mod outgoing;
13mod reuse;
14mod sink;
15mod writable;
16pub use writable::{PendingResponse, RequestBodyWriter, StreamingResponseBody, WritableRequest};
17
18pub use body::ResponseBody;
19pub use connection::Connection;
20pub use driver::Driver;
21pub use error::Error;
22
23use base_http::{Authority, InterimHandler, Method, Request, Response, Scheme, body::ReadableBody};
24use base_pool::MultiplexPool;
25use base_scheduler::{
26 Cancellation, SocketClose, SocketConnect, SocketOpen, SocketSetOption, SocketStreamReading,
27 SocketStreamWriting, TcpNoDelay,
28};
29use driver::{CancelOnDrop, Operation, Pending, Queue, checked};
30use std::{
31 convert::Infallible,
32 net::SocketAddr,
33 sync::{Arc, atomic::Ordering},
34};
35
36#[derive(Clone, Debug)]
39pub struct Endpoint {
40 pub address: SocketAddr,
41 pub authority: Authority,
42 #[cfg(feature = "rustls")]
43 pub(crate) tls: Option<Tls>,
44}
45#[cfg(feature = "rustls")]
46#[derive(Clone, Debug)]
47pub(crate) struct Tls {
48 config: Arc<rustls::ClientConfig>,
49 server_name: String,
50}
51impl Endpoint {
52 pub fn new(address: SocketAddr) -> Self {
53 Self {
54 address,
55 authority: address.to_string().into(),
56 #[cfg(feature = "rustls")]
57 tls: None,
58 }
59 }
60 #[cfg(feature = "rustls")]
63 pub fn with_tls(
64 mut self,
65 config: Arc<rustls::ClientConfig>,
66 server_name: impl Into<String>,
67 ) -> Self {
68 self.tls = Some(Tls {
69 config,
70 server_name: server_name.into(),
71 });
72 self
73 }
74 pub fn scheme(&self) -> &'static str {
75 #[cfg(feature = "rustls")]
76 if self.tls.is_some() {
77 return "https";
78 }
79 "http"
80 }
81 pub(crate) fn request_scheme(&self) -> Scheme {
83 #[cfg(feature = "rustls")]
84 if self.tls.is_some() {
85 return Scheme::HTTPS;
86 }
87 Scheme::HTTP
88 }
89}
90
91#[derive(Clone, Debug)]
92pub struct Options {
93 pub pool: base_pool::Options,
94 pub http1: base_http1::Options,
95 pub protocols: base_http_protocol::HttpProtocols,
97 pub max_requests: usize,
99 pub response_buffer_bytes: usize,
100 pub response_buffer_frames: usize,
101 pub tcp_nodelay: bool,
105}
106impl Default for Options {
107 fn default() -> Self {
108 Self {
109 pool: base_pool::Options::default(),
110 http1: base_http1::Options::default(),
111 protocols: base_http_protocol::HttpProtocols::default(),
112 max_requests: 1024,
113 response_buffer_bytes: 64 * 1024,
114 response_buffer_frames: 16,
115 tcp_nodelay: true,
116 }
117 }
118}
119impl Options {
120 pub fn validate(&self) -> Result<(), Error> {
121 self.pool.validate()?;
122 self.http1.validate()?;
123 if self.max_requests == 0
124 || self.response_buffer_frames == 0
125 || self.response_buffer_bytes < self.http1.read_size.max(self.http1.max_head_bytes)
126 {
127 return Err(Error::InvalidOptions);
128 }
129 Ok(())
130 }
131}
132
133pub struct Client<'scope> {
137 endpoint: Endpoint,
138 queue: Arc<Queue<'scope>>,
139 pool: MultiplexPool<Connection<'scope>>,
140 context: Arc<driver::Context<'scope>>,
141}
142impl<'scope> Client<'scope> {
143 pub fn new<S>(
147 scheduler: &'scope S,
148 endpoint: Endpoint,
149 options: Options,
150 ) -> Result<(Self, Driver<'scope>), Error>
151 where
152 S: SocketOpen
153 + SocketConnect
154 + SocketClose
155 + SocketSetOption<TcpNoDelay>
156 + SocketStreamReading
157 + SocketStreamWriting
158 + 'scope,
159 S::Socket: 'static,
160 {
161 options.validate()?;
162 let driver::ClientParts {
163 queue,
164 pool,
165 driver,
166 context,
167 } = driver::make(scheduler, endpoint.clone(), options);
168 Ok((
169 Self {
170 endpoint,
171 queue,
172 pool,
173 context,
174 },
175 driver,
176 ))
177 }
178 pub fn endpoint(&self) -> &Endpoint {
179 &self.endpoint
180 }
181 pub fn pool_stats(&self) -> base_pool::Stats {
182 self.pool.stats()
183 }
184 pub fn begin_close(&self) {
187 self.queue.begin_close();
188 }
189
190 pub async fn call<B>(
193 &self,
194 request: Request<B>,
195 cancellation: &Cancellation,
196 ) -> Result<Response<ResponseBody>, Error>
197 where
198 B: ReadableBody + 'scope,
199 B::Data: Into<Vec<u8>>,
200 {
201 self.call_with_interim(
202 request,
203 |_: base_http::ResponseMetadata| async { Ok::<_, Infallible>(()) },
204 cancellation,
205 )
206 .await
207 }
208 pub async fn call_with_interim<B, I>(
210 &self,
211 mut request: Request<B>,
212 interim: I,
213 cancellation: &Cancellation,
214 ) -> Result<Response<ResponseBody>, Error>
215 where
216 B: ReadableBody + 'scope,
217 B::Data: Into<Vec<u8>>,
218 I: InterimHandler + 'scope,
219 {
220 if cancellation.is_cancelled() {
221 request.body.close(None);
222 return Err(Error::Cancelled);
223 }
224 if request.method == Method::CONNECT
225 || request.protocol.is_some()
226 || request.fields.contains("upgrade")
227 {
228 request.body.close(None);
229 return Err(Error::Unsupported("managed client tunnels and upgrades"));
230 }
231 if request
232 .scheme
233 .as_deref()
234 .is_some_and(|scheme| scheme != self.endpoint.scheme())
235 {
236 request.body.close(None);
237 return Err(Error::Unsupported(
238 "request scheme does not match the endpoint",
239 ));
240 }
241 request
242 .scheme
243 .get_or_insert_with(|| self.endpoint.request_scheme());
244 request
245 .authority
246 .get_or_insert_with(|| self.endpoint.authority.clone());
247 let local = Cancellation::new();
248 let mut guard = CancelOnDrop(local.clone(), true);
249 let (reply, response) = futures_channel::oneshot::channel();
250 let pending = match Pending::new(self.queue.clone()) {
251 Ok(pending) => pending,
252 Err(error) => {
253 request.body.close(None);
254 return Err(error);
255 }
256 };
257 let operation = Operation {
258 request: request.map_body(|body| outgoing::OutgoingBody::new(body, local.clone())),
259 interim: Box::new(interim),
260 reply: Some(reply),
261 cancellation: local,
262 lifetime: cancellation.clone(),
263 _pending: Some(pending),
264 };
265 let _ = self.queue.push(operation);
267 let response = checked(
268 async { response.await.map_err(|_| Error::Closed)? },
269 cancellation,
270 )
271 .await?;
272 guard.1 = false;
273 Ok(response)
274 }
275}
276impl Clone for Client<'_> {
277 fn clone(&self) -> Self {
278 self.queue.handles.fetch_add(1, Ordering::Relaxed);
279 Self {
280 endpoint: self.endpoint.clone(),
281 queue: self.queue.clone(),
282 pool: self.pool.clone(),
283 context: self.context.clone(),
284 }
285 }
286}
287impl Drop for Client<'_> {
288 fn drop(&mut self) {
289 if self.queue.handles.fetch_sub(1, Ordering::AcqRel) == 1 {
290 self.queue.begin_close();
291 }
292 }
293}