1use crate::{
4 Layer, Service,
5 cli::ForwardKind,
6 combinators::Either,
7 combinators::{Either3, Either7},
8 error::{BoxError, OpaqueError},
9 http::{
10 Request, Response, Version,
11 headers::exotic::XClacksOverhead,
12 headers::forwarded::{CFConnectingIp, ClientIp, TrueClientIp, XClientIp, XRealIp},
13 layer::set_header::SetResponseHeaderLayer,
14 layer::{
15 forwarded::GetForwardedHeaderLayer, required_header::AddRequiredResponseHeadersLayer,
16 trace::TraceLayer,
17 },
18 server::HttpServer,
19 service::{
20 fs::{DirectoryServeMode, ServeDir, ServeFile},
21 web::StaticService,
22 web::response::{Html, IntoResponse},
23 },
24 },
25 layer::limit::policy::UnlimitedPolicy,
26 layer::{ConsumeErrLayer, LimitLayer, TimeoutLayer, limit::policy::ConcurrentPolicy},
27 net::stream::layer::http::BodyLimitLayer,
28 proxy::haproxy::server::HaProxyLayer,
29 rt::Executor,
30 tcp::TcpStream,
31 telemetry::tracing,
32 ua::layer::classifier::UserAgentClassifierLayer,
33};
34
35use std::{convert::Infallible, path::PathBuf, time::Duration};
36
37#[cfg(feature = "boring")]
38use crate::{
39 net::tls::server::ServerConfig,
40 tls::boring::server::{TlsAcceptorData, TlsAcceptorLayer},
41};
42
43#[cfg(all(feature = "rustls", not(feature = "boring")))]
44use crate::tls::rustls::server::{TlsAcceptorData, TlsAcceptorLayer};
45
46#[cfg(feature = "boring")]
47type TlsConfig = ServerConfig;
48
49#[cfg(all(feature = "rustls", not(feature = "boring")))]
50type TlsConfig = TlsAcceptorData;
51
52#[derive(Debug, Clone)]
53pub struct FsServiceBuilder<H> {
56 concurrent_limit: usize,
57 body_limit: usize,
58 timeout: Duration,
59 forward: Option<ForwardKind>,
60
61 #[cfg(any(feature = "rustls", feature = "boring"))]
62 tls_server_config: Option<TlsConfig>,
63
64 http_version: Option<Version>,
65
66 http_service_builder: H,
67
68 content_path: Option<PathBuf>,
69 dir_serve_mode: DirectoryServeMode,
70}
71
72impl Default for FsServiceBuilder<()> {
73 fn default() -> Self {
74 Self {
75 concurrent_limit: 0,
76 body_limit: 1024 * 1024,
77 timeout: Duration::ZERO,
78 forward: None,
79
80 #[cfg(any(feature = "rustls", feature = "boring"))]
81 tls_server_config: None,
82
83 http_version: None,
84
85 http_service_builder: (),
86
87 content_path: None,
88 dir_serve_mode: DirectoryServeMode::HtmlFileList,
89 }
90 }
91}
92
93impl FsServiceBuilder<()> {
94 #[must_use]
96 pub fn new() -> Self {
97 Self::default()
98 }
99}
100
101impl<H> FsServiceBuilder<H> {
102 rama_utils::macros::generate_set_and_with! {
103 pub fn concurrent(mut self, limit: usize) -> Self {
107 self.concurrent_limit = limit;
108 self
109 }
110 }
111
112 rama_utils::macros::generate_set_and_with! {
113 pub fn body_limit(mut self, limit: usize) -> Self {
115 self.body_limit = limit;
116 self
117 }
118 }
119
120 rama_utils::macros::generate_set_and_with! {
121 pub fn timeout(mut self, timeout: Duration) -> Self {
125 self.timeout = timeout;
126 self
127 }
128 }
129
130 rama_utils::macros::generate_set_and_with! {
131 pub fn forward(mut self, kind: Option<ForwardKind>) -> Self {
143 self.forward = kind;
144 self
145 }
146 }
147
148 #[cfg(any(feature = "rustls", feature = "boring"))]
149 rama_utils::macros::generate_set_and_with! {
150 pub fn tls_server_config(mut self, cfg: Option<TlsConfig>) -> Self {
153 self.tls_server_config = cfg;
154 self
155 }
156 }
157
158 rama_utils::macros::generate_set_and_with! {
159 pub fn http_version(mut self, version: Option<Version>) -> Self {
161 self.http_version = version;
162 self
163 }
164 }
165
166 #[must_use]
168 pub fn with_http_layer<H2>(self, layer: H2) -> FsServiceBuilder<(H, H2)> {
169 FsServiceBuilder {
170 concurrent_limit: self.concurrent_limit,
171 body_limit: self.body_limit,
172 timeout: self.timeout,
173 forward: self.forward,
174
175 #[cfg(any(feature = "rustls", feature = "boring"))]
176 tls_server_config: self.tls_server_config,
177
178 http_version: self.http_version,
179
180 http_service_builder: (self.http_service_builder, layer),
181
182 content_path: self.content_path,
183 dir_serve_mode: self.dir_serve_mode,
184 }
185 }
186
187 rama_utils::macros::generate_set_and_with! {
188 pub fn content_path(mut self, path: impl Into<PathBuf>) -> Self {
190 self.content_path = Some(path.into());
191 self
192 }
193 }
194
195 #[must_use]
197 pub fn maybe_with_content_path<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {
198 self.content_path = path.map(Into::into);
199 self
200 }
201
202 pub fn maybe_set_content_path<P: Into<PathBuf>>(&mut self, path: Option<P>) -> &mut Self {
204 self.content_path = path.map(Into::into);
205 self
206 }
207
208 rama_utils::macros::generate_set_and_with! {
209 pub fn directory_serve_mode(mut self, mode: DirectoryServeMode) -> Self {
217 self.dir_serve_mode = mode;
218 self
219 }
220 }
221}
222
223impl<H> FsServiceBuilder<H>
224where
225 H: Layer<ServeService, Service: Service<Request, Response = Response, Error: Into<BoxError>>>,
226{
227 pub fn build(
229 self,
230 executor: Executor,
231 ) -> Result<impl Service<TcpStream, Response = (), Error = Infallible>, BoxError> {
232 let tcp_forwarded_layer = match &self.forward {
233 Some(ForwardKind::HaProxy) => Some(HaProxyLayer::default()),
234 _ => None,
235 };
236
237 let http_service = self.build_http()?;
238
239 #[cfg(all(feature = "rustls", not(feature = "boring")))]
240 let tls_cfg = self.tls_server_config;
241
242 #[cfg(feature = "boring")]
243 let tls_cfg: Option<TlsAcceptorData> = match self.tls_server_config {
244 Some(cfg) => Some(cfg.try_into()?),
245 None => None,
246 };
247
248 let tcp_service_builder = (
249 ConsumeErrLayer::trace(tracing::Level::DEBUG),
250 LimitLayer::new(if self.concurrent_limit > 0 {
251 Either::A(ConcurrentPolicy::max(self.concurrent_limit))
252 } else {
253 Either::B(UnlimitedPolicy::new())
254 }),
255 if !self.timeout.is_zero() {
256 TimeoutLayer::new(self.timeout)
257 } else {
258 TimeoutLayer::never()
259 },
260 tcp_forwarded_layer,
261 BodyLimitLayer::request_only(self.body_limit),
262 #[cfg(any(feature = "rustls", feature = "boring"))]
263 tls_cfg.map(|cfg| {
264 #[cfg(feature = "boring")]
265 return TlsAcceptorLayer::new(cfg).with_store_client_hello(true);
266 #[cfg(all(feature = "rustls", not(feature = "boring")))]
267 TlsAcceptorLayer::new(cfg).with_store_client_hello(true)
268 }),
269 );
270
271 let http_transport_service = match self.http_version {
272 Some(Version::HTTP_2) => Either3::A(HttpServer::h2(executor).service(http_service)),
273 Some(Version::HTTP_11 | Version::HTTP_10 | Version::HTTP_09) => {
274 Either3::B(HttpServer::http1().service(http_service))
275 }
276 Some(_) => {
277 return Err(OpaqueError::from_display("unsupported http version").into_boxed());
278 }
279 None => Either3::C(HttpServer::auto(executor).service(http_service)),
280 };
281
282 Ok(tcp_service_builder.into_layer(http_transport_service))
283 }
284
285 pub fn build_http(
287 &self,
288 ) -> Result<impl Service<Request, Response: IntoResponse, Error = Infallible> + use<H>, BoxError>
289 {
290 let http_forwarded_layer = match &self.forward {
291 None | Some(ForwardKind::HaProxy) => None,
292 Some(ForwardKind::Forwarded) => Some(Either7::A(GetForwardedHeaderLayer::forwarded())),
293 Some(ForwardKind::XForwardedFor) => {
294 Some(Either7::B(GetForwardedHeaderLayer::x_forwarded_for()))
295 }
296 Some(ForwardKind::XClientIp) => {
297 Some(Either7::C(GetForwardedHeaderLayer::<XClientIp>::new()))
298 }
299 Some(ForwardKind::ClientIp) => {
300 Some(Either7::D(GetForwardedHeaderLayer::<ClientIp>::new()))
301 }
302 Some(ForwardKind::XRealIp) => {
303 Some(Either7::E(GetForwardedHeaderLayer::<XRealIp>::new()))
304 }
305 Some(ForwardKind::CFConnectingIp) => {
306 Some(Either7::F(GetForwardedHeaderLayer::<CFConnectingIp>::new()))
307 }
308 Some(ForwardKind::TrueClientIp) => {
309 Some(Either7::G(GetForwardedHeaderLayer::<TrueClientIp>::new()))
310 }
311 };
312
313 let serve_service = match &self.content_path {
314 None => Either3::A(StaticService::new(Html(include_str!(
315 "../../../docs/index.html"
316 )))),
317 Some(path) if path.is_file() => Either3::B(ServeFile::new(path.clone())),
318 Some(path) if path.is_dir() => {
319 Either3::C(ServeDir::new(path).with_directory_serve_mode(self.dir_serve_mode))
320 }
321 Some(path) => {
322 return Err(OpaqueError::from_display(format!(
323 "invalid path {path:?}: no such file or directory"
324 ))
325 .into_boxed());
326 }
327 };
328
329 let http_service = (
330 TraceLayer::new_for_http(),
331 SetResponseHeaderLayer::<XClacksOverhead>::if_not_present_default_typed(),
332 AddRequiredResponseHeadersLayer::default(),
333 UserAgentClassifierLayer::new(),
334 ConsumeErrLayer::default(),
335 http_forwarded_layer,
336 )
337 .into_layer(self.http_service_builder.layer(serve_service));
338
339 Ok(http_service)
340 }
341}
342
343type ServeStaticHtml = StaticService<Html<&'static str>>;
344type ServeService = Either3<ServeStaticHtml, ServeFile, ServeDir>;