structured_proxy/
serve.rs1use std::pin::Pin;
5use std::sync::Arc;
6use std::task::{Context, Poll};
7use std::time::Duration;
8
9use hyper_util::rt::{TokioExecutor, TokioIo, TokioTimer};
10use hyper_util::server::conn::auto::Builder;
11use hyper_util::service::TowerToHyperService;
12use pin_project_lite::pin_project;
13use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
14use tokio::net::TcpListener;
15use tokio::sync::{OwnedSemaphorePermit, Semaphore};
16use tonic::transport::server::Connected;
17
18use crate::service::{ConnectionInfo, ProxyService};
19use crate::upstream::Upstream;
20
21mod idle;
22
23#[derive(Clone, Debug)]
37pub struct ServeOptions {
38 max_connections: Option<usize>,
39 tls: Option<Arc<rustls::ServerConfig>>,
40 idle_timeout: Option<Duration>,
41 header_read_timeout: Duration,
42 tls_handshake_timeout: Duration,
43}
44
45impl Default for ServeOptions {
46 fn default() -> Self {
47 Self {
48 max_connections: None,
49 tls: None,
50 idle_timeout: Some(Duration::from_secs(60)),
51 header_read_timeout: Duration::from_secs(30),
52 tls_handshake_timeout: Duration::from_secs(10),
53 }
54 }
55}
56
57impl ServeOptions {
58 pub fn new() -> Self {
62 Self::default()
63 }
64
65 #[must_use]
74 pub fn idle_timeout(mut self, timeout: Option<Duration>) -> Self {
75 self.idle_timeout = timeout;
76 self
77 }
78
79 #[must_use]
82 pub fn header_read_timeout(mut self, timeout: Duration) -> Self {
83 self.header_read_timeout = timeout;
84 self
85 }
86
87 #[must_use]
91 pub fn tls_handshake_timeout(mut self, timeout: Duration) -> Self {
92 self.tls_handshake_timeout = timeout;
93 self
94 }
95
96 #[must_use]
103 pub fn max_connections(mut self, max: usize) -> Self {
104 assert!(max > 0, "max_connections must be at least 1");
105 self.max_connections = Some(max.min(Semaphore::MAX_PERMITS));
106 self
107 }
108
109 #[must_use]
114 pub fn tls(mut self, mut config: rustls::ServerConfig) -> Self {
115 if config.alpn_protocols.is_empty() {
116 config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
117 }
118 self.tls = Some(Arc::new(config));
119 self
120 }
121}
122
123pub async fn serve<U: Upstream>(
146 listener: TcpListener,
147 service: ProxyService<U>,
148) -> std::io::Result<()> {
149 serve_with(listener, service, ServeOptions::new()).await
150}
151
152pub async fn serve_with<U: Upstream>(
177 listener: TcpListener,
178 service: ProxyService<U>,
179 options: ServeOptions,
180) -> std::io::Result<()> {
181 let slots = options
182 .max_connections
183 .map(|max| Arc::new(Semaphore::new(max)));
184 let acceptor = options.tls.map(tokio_rustls::TlsAcceptor::from);
185 let handshake_timeout = options.tls_handshake_timeout;
186 let limits = ConnectionLimits {
187 idle_timeout: options.idle_timeout,
188 header_read_timeout: options.header_read_timeout,
189 };
190 loop {
191 let slot = match &slots {
195 Some(slots) => Some(
196 slots
197 .clone()
198 .acquire_owned()
199 .await
200 .expect("the connection semaphore is never closed"),
201 ),
202 None => None,
203 };
204 let tcp = match listener.accept().await {
205 Ok((tcp, _)) => tcp,
206 Err(error) => {
207 accept_failed(error).await;
208 continue;
209 }
210 };
211 let service = service.clone();
212 let acceptor = acceptor.clone();
213 tokio::spawn(async move {
214 if let Err(error) = tcp.set_nodelay(true) {
216 tracing::debug!(%error, "cannot set TCP_NODELAY");
217 }
218 match acceptor {
219 None => {
220 let service = service.for_connection(tcp.connect_info());
221 serve_connection(SlotIo { io: tcp, slot }, service, limits).await;
222 }
223 Some(acceptor) => {
224 let stream =
227 match tokio::time::timeout(handshake_timeout, acceptor.accept(tcp)).await {
228 Ok(Ok(stream)) => stream,
229 Ok(Err(error)) => {
230 tracing::debug!(%error, "TLS handshake failed");
231 return;
232 }
233 Err(_) => {
234 tracing::debug!("TLS handshake timed out");
235 return;
236 }
237 };
238 let service =
239 service.for_connection(ConnectionInfo::tls(stream.connect_info()));
240 serve_connection(SlotIo { io: stream, slot }, service, limits).await;
241 }
242 }
243 });
244 }
245}
246
247pin_project! {
248 struct SlotIo<I> {
253 #[pin]
254 io: I,
255 slot: Option<OwnedSemaphorePermit>,
256 }
257}
258
259impl<I: AsyncRead> AsyncRead for SlotIo<I> {
260 #[inline]
261 fn poll_read(
262 self: Pin<&mut Self>,
263 cx: &mut Context<'_>,
264 buf: &mut ReadBuf<'_>,
265 ) -> Poll<std::io::Result<()>> {
266 self.project().io.poll_read(cx, buf)
267 }
268}
269
270impl<I: AsyncWrite> AsyncWrite for SlotIo<I> {
271 #[inline]
272 fn poll_write(
273 self: Pin<&mut Self>,
274 cx: &mut Context<'_>,
275 buf: &[u8],
276 ) -> Poll<std::io::Result<usize>> {
277 self.project().io.poll_write(cx, buf)
278 }
279
280 #[inline]
281 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
282 self.project().io.poll_flush(cx)
283 }
284
285 #[inline]
286 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
287 self.project().io.poll_shutdown(cx)
288 }
289
290 #[inline]
291 fn poll_write_vectored(
292 self: Pin<&mut Self>,
293 cx: &mut Context<'_>,
294 bufs: &[std::io::IoSlice<'_>],
295 ) -> Poll<std::io::Result<usize>> {
296 self.project().io.poll_write_vectored(cx, bufs)
297 }
298
299 #[inline]
300 fn is_write_vectored(&self) -> bool {
301 self.io.is_write_vectored()
302 }
303}
304
305#[derive(Clone, Copy, Debug)]
307struct ConnectionLimits {
308 idle_timeout: Option<Duration>,
309 header_read_timeout: Duration,
310}
311
312async fn serve_connection<U, I>(io: I, service: ProxyService<U>, limits: ConnectionLimits)
314where
315 U: Upstream,
316 I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
317{
318 let mut builder = Builder::new(TokioExecutor::new());
319 builder
322 .http1()
323 .timer(TokioTimer::new())
324 .header_read_timeout(limits.header_read_timeout);
325 builder.http2().timer(TokioTimer::new());
326 let served = match limits.idle_timeout {
327 None => {
328 builder
329 .serve_connection_with_upgrades(TokioIo::new(io), TowerToHyperService::new(service))
330 .await
331 }
332 Some(timeout) => {
333 let activity = Arc::new(idle::Activity::default());
334 let service = idle::Tracked {
335 inner: service,
336 activity: Arc::clone(&activity),
337 };
338 let connection = builder.serve_connection_with_upgrades(
339 TokioIo::new(io),
340 TowerToHyperService::new(service),
341 );
342 tokio::pin!(connection);
343 tokio::select! {
344 served = connection.as_mut() => served,
345 () = activity.idle_for(timeout) => {
346 connection.as_mut().graceful_shutdown();
349 connection.await
350 }
351 }
352 }
353 };
354 if let Err(error) = served {
355 tracing::debug!(%error, "connection ended");
356 }
357}
358
359async fn accept_failed(error: std::io::Error) {
363 use std::io::ErrorKind;
364 if matches!(
365 error.kind(),
366 ErrorKind::ConnectionRefused | ErrorKind::ConnectionAborted | ErrorKind::ConnectionReset
367 ) {
368 return;
369 }
370 tracing::error!(%error, "accepting a connection failed; retrying in 1s");
371 tokio::time::sleep(Duration::from_secs(1)).await;
372}
373
374#[cfg(test)]
375mod tests;