1use crate::{
4 Layer, Service,
5 cli::{DEFAULT_CONNECTION_GRACE, ForwardKind, connection_scope},
6 combinators::{Either, Either3},
7 error::{BoxError, BoxErrorExt, ErrorExt},
8 graceful::WeakScope,
9 http::BodyLimitLayer,
10 http::{
11 Request, Response, Version,
12 headers::exotic::XClacksOverhead,
13 layer::set_header::SetResponseHeaderLayer,
14 layer::{
15 into_response::IntoResponseService, required_header::AddRequiredResponseHeadersLayer,
16 trace::TraceLayer,
17 },
18 server::HttpServer,
19 service::{
20 fs::{DirectoryServeMode, ServeDir, ServeDirSymlinkPolicy, ServeFile},
21 web::response::{Html, IntoResponse},
22 },
23 },
24 layer::limit::policy::UnlimitedPolicy,
25 layer::{
26 ConsumeErrLayer, LimitLayer,
27 limit::policy::{ConcurrentPolicy, RateLimitReached, RatePolicy},
28 },
29 net::stream::layer::{ThrottleLayer, ThrottleMode},
30 proxy::haproxy::server::HaProxyLayer,
31 service::StaticOutput,
32 tcp::TcpStream,
33 telemetry::tracing,
34 ua::layer::classifier::UserAgentClassifierLayer,
35 utils::{octets::mib, rate::Rate},
36};
37
38use std::{convert::Infallible, path::PathBuf, sync::Arc, time::Duration};
39
40core::cfg_select! {
41 feature = "boring" => {
42 use crate::tls::boring::server::TlsAcceptorLayer;
43 }
44 feature = "rustls" => {
45 use crate::tls::rustls::server::TlsAcceptorLayer;
46 }
47 _ => {}
48}
49
50#[cfg(any(feature = "boring", feature = "rustls"))]
51use crate::tls::server::TlsServerConfig;
52
53#[derive(Debug, Clone)]
54pub struct FsServiceBuilder<H> {
57 concurrent_limit: usize,
58 rate_limit: Option<Rate>,
59 throttle: Option<Rate>,
60 body_limit: usize,
61 timeout: Duration,
62 timeout_grace: Duration,
63 forward: Option<ForwardKind>,
64
65 #[cfg(any(feature = "rustls", feature = "boring"))]
66 tls_server_config: Option<TlsServerConfig>,
67
68 http_version: Option<Version>,
69
70 http_service_builder: H,
71
72 content_path: Option<PathBuf>,
73 dir_serve_mode: DirectoryServeMode,
74 html_as_default_extension: bool,
75 symlink_policy: ServeDirSymlinkPolicy,
76}
77
78impl Default for FsServiceBuilder<()> {
79 fn default() -> Self {
80 Self {
81 concurrent_limit: 0,
82 rate_limit: None,
83 throttle: None,
84 body_limit: mib(1),
85 timeout: Duration::ZERO,
86 timeout_grace: DEFAULT_CONNECTION_GRACE,
87 forward: None,
88
89 #[cfg(any(feature = "rustls", feature = "boring"))]
90 tls_server_config: None,
91
92 http_version: None,
93
94 http_service_builder: (),
95
96 content_path: None,
97 dir_serve_mode: DirectoryServeMode::HtmlFileList,
98 html_as_default_extension: false,
99 symlink_policy: ServeDirSymlinkPolicy::default(),
100 }
101 }
102}
103
104impl FsServiceBuilder<()> {
105 #[must_use]
107 pub fn new() -> Self {
108 Self::default()
109 }
110}
111
112impl<H> FsServiceBuilder<H> {
113 rama_utils::macros::generate_set_and_with! {
114 pub fn concurrent(mut self, limit: usize) -> Self {
118 self.concurrent_limit = limit;
119 self
120 }
121 }
122
123 rama_utils::macros::generate_set_and_with! {
124 pub fn rate_limit(mut self, rate: Option<Rate>) -> Self {
127 self.rate_limit = rate;
128 self
129 }
130 }
131
132 rama_utils::macros::generate_set_and_with! {
133 pub fn throttle(mut self, rate: Option<Rate>) -> Self {
136 self.throttle = rate;
137 self
138 }
139 }
140
141 rama_utils::macros::generate_set_and_with! {
142 pub fn body_limit(mut self, limit: usize) -> Self {
144 self.body_limit = limit;
145 self
146 }
147 }
148
149 rama_utils::macros::generate_set_and_with! {
150 pub fn timeout(mut self, timeout: Duration) -> Self {
155 self.timeout = timeout;
156 self
157 }
158 }
159
160 rama_utils::macros::generate_set_and_with! {
161 pub fn timeout_grace(mut self, timeout_grace: Duration) -> Self {
166 self.timeout_grace = timeout_grace;
167 self
168 }
169 }
170
171 rama_utils::macros::generate_set_and_with! {
172 pub fn forward(mut self, kind: Option<ForwardKind>) -> Self {
184 self.forward = kind;
185 self
186 }
187 }
188
189 #[cfg(any(feature = "rustls", feature = "boring"))]
190 rama_utils::macros::generate_set_and_with! {
191 pub fn tls_server_config(mut self, cfg: Option<TlsServerConfig>) -> Self {
194 self.tls_server_config = cfg;
195 self
196 }
197 }
198
199 rama_utils::macros::generate_set_and_with! {
200 pub fn http_version(mut self, version: Option<Version>) -> Self {
202 self.http_version = version;
203 self
204 }
205 }
206
207 #[must_use]
209 pub fn with_http_layer<H2>(self, layer: H2) -> FsServiceBuilder<(H, H2)> {
210 FsServiceBuilder {
211 concurrent_limit: self.concurrent_limit,
212 rate_limit: self.rate_limit,
213 throttle: self.throttle,
214 body_limit: self.body_limit,
215 timeout: self.timeout,
216 timeout_grace: self.timeout_grace,
217 forward: self.forward,
218
219 #[cfg(any(feature = "rustls", feature = "boring"))]
220 tls_server_config: self.tls_server_config,
221
222 http_version: self.http_version,
223
224 http_service_builder: (self.http_service_builder, layer),
225
226 content_path: self.content_path,
227 dir_serve_mode: self.dir_serve_mode,
228 html_as_default_extension: self.html_as_default_extension,
229 symlink_policy: self.symlink_policy,
230 }
231 }
232
233 rama_utils::macros::generate_set_and_with! {
234 pub fn content_path(mut self, path: impl Into<PathBuf>) -> Self {
236 self.content_path = Some(path.into());
237 self
238 }
239 }
240
241 #[must_use]
243 pub fn maybe_with_content_path<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {
244 self.content_path = path.map(Into::into);
245 self
246 }
247
248 pub fn maybe_set_content_path<P: Into<PathBuf>>(&mut self, path: Option<P>) -> &mut Self {
250 self.content_path = path.map(Into::into);
251 self
252 }
253
254 rama_utils::macros::generate_set_and_with! {
255 pub fn directory_serve_mode(mut self, mode: DirectoryServeMode) -> Self {
263 self.dir_serve_mode = mode;
264 self
265 }
266 }
267
268 rama_utils::macros::generate_set_and_with! {
269 pub fn html_as_default_extension(mut self, html_as_default_extension: bool) -> Self {
277 self.html_as_default_extension = html_as_default_extension;
278 self
279 }
280 }
281
282 rama_utils::macros::generate_set_and_with! {
283 pub fn symlink_policy(mut self, policy: ServeDirSymlinkPolicy) -> Self {
287 self.symlink_policy = policy;
288 self
289 }
290 }
291}
292
293impl<H> FsServiceBuilder<H>
294where
295 H: Layer<ServeService, Service: Service<Request, Output = Response, Error: Into<BoxError>>>,
296{
297 pub fn build(
299 self,
300 scope: impl Into<WeakScope>,
301 ) -> Result<impl Service<TcpStream, Output = (), Error = Infallible>, BoxError> {
302 let scope = scope.into();
303 let tcp_forwarded_layer = match &self.forward {
304 Some(ForwardKind::HaProxy) => Some(HaProxyLayer::default()),
305 _ => None,
306 };
307
308 let http_service = Arc::new(self.build_http()?);
309
310 let tcp_service_builder = (
311 ConsumeErrLayer::trace_as(tracing::Level::DEBUG),
312 LimitLayer::new(if self.concurrent_limit > 0 {
313 Either::A(ConcurrentPolicy::max(self.concurrent_limit))
314 } else {
315 Either::B(UnlimitedPolicy::new())
316 }),
317 connection_scope(&scope, self.timeout, self.timeout_grace),
318 self.throttle
319 .map(|rate| ThrottleLayer::symmetric(ThrottleMode::per_conn(rate))),
320 tcp_forwarded_layer,
321 BodyLimitLayer::request_only(self.body_limit),
322 #[cfg(any(feature = "rustls", feature = "boring"))]
323 self.tls_server_config
324 .map(|cfg| TlsAcceptorLayer::new(cfg).with_store_client_hello(true)),
325 );
326
327 let http_transport_service = match self.http_version {
328 Some(Version::HTTP_2) => Either3::A(HttpServer::new_h2(scope).service(http_service)),
329 Some(Version::HTTP_11 | Version::HTTP_10 | Version::HTTP_09) => {
330 Either3::B(HttpServer::new_http1(scope).service(http_service))
331 }
332 Some(version) => {
333 return Err(BoxError::from_static_str("unsupported http version")
334 .context_debug_field("version", version));
335 }
336 None => Either3::C(HttpServer::auto(scope).service(http_service)),
337 };
338
339 Ok(tcp_service_builder.into_layer(http_transport_service))
340 }
341
342 pub fn build_http(
344 &self,
345 ) -> Result<impl Service<Request, Output: IntoResponse, Error = Infallible> + use<H>, BoxError>
346 {
347 let http_forwarded_layer = super::http_forwarded_layer(self.forward.as_ref());
348
349 let serve_service = match &self.content_path {
350 None => Either3::A(IntoResponseService::new(StaticOutput::new(Html(
351 include_str!("../../../docs/index.html"),
352 )))),
353 Some(path) if path.is_file() => {
354 Either3::B(ServeFile::new(path.clone()).with_symlink_policy(self.symlink_policy))
355 }
356 Some(path) if path.is_dir() => Either3::C(
357 ServeDir::new(path)
358 .with_directory_serve_mode(self.dir_serve_mode)
359 .with_html_as_default_extension(self.html_as_default_extension)
360 .with_symlink_policy(self.symlink_policy),
361 ),
362 Some(path) => {
363 return Err(
364 BoxError::from_static_str("invalid path: no such file or directory")
365 .with_context_debug_field("path", || path.clone()),
366 );
367 }
368 };
369
370 let http_service = (
371 TraceLayer::new_for_http(),
372 SetResponseHeaderLayer::<XClacksOverhead>::if_not_present_default_typed(),
373 AddRequiredResponseHeadersLayer::default(),
374 self.rate_limit.map(|rate| {
375 LimitLayer::new(RatePolicy::abort(rate)).with_error_into_response_fn(
376 |err: RateLimitReached| Ok::<_, Infallible>(err.into_response()),
377 )
378 }),
379 UserAgentClassifierLayer::new(),
380 ConsumeErrLayer::default(),
381 http_forwarded_layer,
382 )
383 .into_layer(self.http_service_builder.layer(serve_service));
384
385 Ok(http_service)
386 }
387}
388
389type ServeStaticHtml = IntoResponseService<StaticOutput<Html<&'static str>>>;
390type ServeService = Either3<ServeStaticHtml, ServeFile, ServeDir>;