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