1use crate::{
4 Layer, Service,
5 cli::ForwardKind,
6 combinators::{Either3, Either7},
7 error::{BoxError, OpaqueError},
8 http::{
9 Request, Response, Version,
10 headers::forwarded::{CFConnectingIp, ClientIp, TrueClientIp, XClientIp, XRealIp},
11 layer::{
12 forwarded::GetForwardedHeaderLayer, required_header::AddRequiredResponseHeadersLayer,
13 trace::TraceLayer, ua::UserAgentClassifierLayer,
14 },
15 server::HttpServer,
16 service::{
17 fs::{DirectoryServeMode, ServeDir, ServeFile},
18 web::StaticService,
19 web::response::{Html, IntoResponse},
20 },
21 },
22 layer::{ConsumeErrLayer, LimitLayer, TimeoutLayer, limit::policy::ConcurrentPolicy},
23 net::stream::layer::http::BodyLimitLayer,
24 proxy::haproxy::server::HaProxyLayer,
25 rt::Executor,
26 telemetry::tracing,
27};
28
29use std::{convert::Infallible, path::PathBuf, time::Duration};
30use tokio::net::TcpStream;
31
32#[cfg(feature = "boring")]
33use crate::{
34 net::tls::server::ServerConfig,
35 tls::boring::server::{TlsAcceptorData, TlsAcceptorLayer},
36};
37
38#[cfg(all(feature = "rustls", not(feature = "boring")))]
39use crate::tls::rustls::server::{TlsAcceptorData, TlsAcceptorLayer};
40
41#[cfg(feature = "boring")]
42type TlsConfig = ServerConfig;
43
44#[cfg(all(feature = "rustls", not(feature = "boring")))]
45type TlsConfig = TlsAcceptorData;
46
47#[derive(Debug, Clone)]
48pub struct ServeServiceBuilder<H> {
51 concurrent_limit: usize,
52 body_limit: usize,
53 timeout: Duration,
54 forward: Option<ForwardKind>,
55
56 #[cfg(any(feature = "rustls", feature = "boring"))]
57 tls_server_config: Option<TlsConfig>,
58
59 http_version: Option<Version>,
60
61 http_service_builder: H,
62
63 content_path: Option<PathBuf>,
64 dir_serve_mode: DirectoryServeMode,
65}
66
67impl Default for ServeServiceBuilder<()> {
68 fn default() -> Self {
69 Self {
70 concurrent_limit: 0,
71 body_limit: 1024 * 1024,
72 timeout: Duration::ZERO,
73 forward: None,
74
75 #[cfg(any(feature = "rustls", feature = "boring"))]
76 tls_server_config: None,
77
78 http_version: None,
79
80 http_service_builder: (),
81
82 content_path: None,
83 dir_serve_mode: DirectoryServeMode::HtmlFileList,
84 }
85 }
86}
87
88impl ServeServiceBuilder<()> {
89 #[must_use]
91 pub fn new() -> Self {
92 Self::default()
93 }
94}
95
96impl<H> ServeServiceBuilder<H> {
97 #[must_use]
101 pub fn concurrent(mut self, limit: usize) -> Self {
102 self.concurrent_limit = limit;
103 self
104 }
105
106 pub fn set_concurrent(&mut self, limit: usize) -> &mut Self {
110 self.concurrent_limit = limit;
111 self
112 }
113
114 #[must_use]
116 pub fn body_limit(mut self, limit: usize) -> Self {
117 self.body_limit = limit;
118 self
119 }
120
121 pub fn set_body_limit(&mut self, limit: usize) -> &mut Self {
123 self.body_limit = limit;
124 self
125 }
126
127 #[must_use]
131 pub fn timeout(mut self, timeout: Duration) -> Self {
132 self.timeout = timeout;
133 self
134 }
135
136 pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self {
140 self.timeout = timeout;
141 self
142 }
143
144 #[must_use]
156 pub fn forward(self, kind: ForwardKind) -> Self {
157 self.maybe_forward(Some(kind))
158 }
159
160 pub fn set_forward(&mut self, kind: ForwardKind) -> &mut Self {
164 self.forward = Some(kind);
165 self
166 }
167
168 #[must_use]
172 pub fn maybe_forward(mut self, maybe_kind: Option<ForwardKind>) -> Self {
173 self.forward = maybe_kind;
174 self
175 }
176
177 #[cfg(any(feature = "rustls", feature = "boring"))]
178 #[must_use]
181 pub fn tls_server_config(mut self, cfg: TlsConfig) -> Self {
182 self.tls_server_config = Some(cfg);
183 self
184 }
185
186 #[cfg(any(feature = "rustls", feature = "boring"))]
187 pub fn set_tls_server_config(&mut self, cfg: TlsConfig) -> &mut Self {
190 self.tls_server_config = Some(cfg);
191 self
192 }
193
194 #[cfg(any(feature = "rustls", feature = "boring"))]
195 #[must_use]
198 pub fn maybe_tls_server_config(mut self, cfg: Option<TlsConfig>) -> Self {
199 self.tls_server_config = cfg;
200 self
201 }
202
203 #[must_use]
205 pub fn http_version(mut self, version: Version) -> Self {
206 self.http_version = Some(version);
207 self
208 }
209
210 #[must_use]
212 pub fn maybe_http_version(mut self, version: Option<Version>) -> Self {
213 self.http_version = version;
214 self
215 }
216
217 pub fn set_http_version(&mut self, version: Version) -> &mut Self {
219 self.http_version = Some(version);
220 self
221 }
222
223 #[must_use]
225 pub fn http_layer<H2>(self, layer: H2) -> ServeServiceBuilder<(H, H2)> {
226 ServeServiceBuilder {
227 concurrent_limit: self.concurrent_limit,
228 body_limit: self.body_limit,
229 timeout: self.timeout,
230 forward: self.forward,
231
232 #[cfg(any(feature = "rustls", feature = "boring"))]
233 tls_server_config: self.tls_server_config,
234
235 http_version: self.http_version,
236
237 http_service_builder: (self.http_service_builder, layer),
238
239 content_path: self.content_path,
240 dir_serve_mode: self.dir_serve_mode,
241 }
242 }
243
244 #[must_use]
246 pub fn content_path(mut self, path: impl Into<PathBuf>) -> Self {
247 self.content_path = Some(path.into());
248 self
249 }
250
251 #[must_use]
253 pub fn maybe_content_path(mut self, path: Option<PathBuf>) -> Self {
254 self.content_path = path;
255 self
256 }
257
258 pub fn set_content_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
260 self.content_path = Some(path.into());
261 self
262 }
263
264 #[must_use]
272 pub fn directory_serve_mode(mut self, mode: DirectoryServeMode) -> Self {
273 self.dir_serve_mode = mode;
274 self
275 }
276
277 pub fn set_directory_serve_mode(&mut self, mode: DirectoryServeMode) -> &mut Self {
284 self.dir_serve_mode = mode;
285 self
286 }
287}
288
289impl<H> ServeServiceBuilder<H>
290where
291 H: Layer<ServeService, Service: Service<Request, Response = Response, Error = BoxError>>,
292{
293 pub fn build(
295 self,
296 executor: Executor,
297 ) -> Result<impl Service<TcpStream, Response = (), Error = Infallible>, BoxError> {
298 let tcp_forwarded_layer = match &self.forward {
299 Some(ForwardKind::HaProxy) => Some(HaProxyLayer::default()),
300 _ => None,
301 };
302
303 let http_service = self.build_http()?;
304
305 #[cfg(all(feature = "rustls", not(feature = "boring")))]
306 let tls_cfg = self.tls_server_config;
307
308 #[cfg(feature = "boring")]
309 let tls_cfg: Option<TlsAcceptorData> = match self.tls_server_config {
310 Some(cfg) => Some(cfg.try_into()?),
311 None => None,
312 };
313
314 let tcp_service_builder = (
315 ConsumeErrLayer::trace(tracing::Level::DEBUG),
316 (self.concurrent_limit > 0)
317 .then(|| LimitLayer::new(ConcurrentPolicy::max(self.concurrent_limit))),
318 (!self.timeout.is_zero()).then(|| TimeoutLayer::new(self.timeout)),
319 tcp_forwarded_layer,
320 BodyLimitLayer::request_only(self.body_limit),
321 #[cfg(any(feature = "rustls", feature = "boring"))]
322 tls_cfg.map(|cfg| {
323 #[cfg(feature = "boring")]
324 return TlsAcceptorLayer::new(cfg).with_store_client_hello(true);
325 #[cfg(all(feature = "rustls", not(feature = "boring")))]
326 TlsAcceptorLayer::new(cfg).with_store_client_hello(true)
327 }),
328 );
329
330 let http_transport_service = match self.http_version {
331 Some(Version::HTTP_2) => Either3::A(HttpServer::h2(executor).service(http_service)),
332 Some(Version::HTTP_11 | Version::HTTP_10 | Version::HTTP_09) => {
333 Either3::B(HttpServer::http1().service(http_service))
334 }
335 Some(_) => {
336 return Err(OpaqueError::from_display("unsupported http version").into_boxed());
337 }
338 None => Either3::C(HttpServer::auto(executor).service(http_service)),
339 };
340
341 Ok(tcp_service_builder.into_layer(http_transport_service))
342 }
343
344 pub fn build_http(
346 &self,
347 ) -> Result<impl Service<Request, Response: IntoResponse, Error = Infallible> + use<H>, BoxError>
348 {
349 let http_forwarded_layer = match &self.forward {
350 None | Some(ForwardKind::HaProxy) => None,
351 Some(ForwardKind::Forwarded) => Some(Either7::A(GetForwardedHeaderLayer::forwarded())),
352 Some(ForwardKind::XForwardedFor) => {
353 Some(Either7::B(GetForwardedHeaderLayer::x_forwarded_for()))
354 }
355 Some(ForwardKind::XClientIp) => {
356 Some(Either7::C(GetForwardedHeaderLayer::<XClientIp>::new()))
357 }
358 Some(ForwardKind::ClientIp) => {
359 Some(Either7::D(GetForwardedHeaderLayer::<ClientIp>::new()))
360 }
361 Some(ForwardKind::XRealIp) => {
362 Some(Either7::E(GetForwardedHeaderLayer::<XRealIp>::new()))
363 }
364 Some(ForwardKind::CFConnectingIp) => {
365 Some(Either7::F(GetForwardedHeaderLayer::<CFConnectingIp>::new()))
366 }
367 Some(ForwardKind::TrueClientIp) => {
368 Some(Either7::G(GetForwardedHeaderLayer::<TrueClientIp>::new()))
369 }
370 };
371
372 let serve_service = match &self.content_path {
373 None => Either3::A(StaticService::new(Html(include_str!(
374 "../../../docs/index.html"
375 )))),
376 Some(path) if path.is_file() => Either3::B(ServeFile::new(path.clone())),
377 Some(path) if path.is_dir() => {
378 Either3::C(ServeDir::new(path).with_directory_serve_mode(self.dir_serve_mode))
379 }
380 Some(path) => {
381 return Err(OpaqueError::from_display(format!(
382 "invalid path {path:?}: no such file or directory"
383 ))
384 .into_boxed());
385 }
386 };
387
388 let http_service = (
389 TraceLayer::new_for_http(),
390 AddRequiredResponseHeadersLayer::default(),
391 UserAgentClassifierLayer::new(),
392 ConsumeErrLayer::default(),
393 http_forwarded_layer,
394 )
395 .into_layer(self.http_service_builder.layer(serve_service));
396
397 Ok(http_service)
398 }
399}
400
401type ServeStaticHtml = StaticService<Html<&'static str>>;
402type ServeService = Either3<ServeStaticHtml, ServeFile, ServeDir>;