Skip to main content

apimock_server/
server.rs

1//! HTTP(S) server runtime.
2//!
3//! # 5.0 layout
4//!
5//! The [`Server`] struct holds the listener addresses and the shared
6//! application state. [`AppState`] in turn holds a `Config` (editable
7//! declarative data from `apimock-config`) alongside [`LoadedMiddlewares`]
8//! (compiled Rhai — runtime only, server-owned).
9//!
10//! Dispatch methods (`middleware_response`, `rule_set_response`) used
11//! to hang off `ServiceConfig` but were moved here in 5.0 because they
12//! build `hyper::Response` values, which a config crate must not do.
13//! They are now free functions in this module that take borrowed config
14//! + loaded state and produce an `hyper::Response`.
15
16use apimock_config::Config;
17use apimock_routing::ParsedRequest;
18use console::style;
19use http_body_util::{BodyExt, Empty};
20use hyper::{
21    HeaderMap, Response, body,
22    header::{CONTENT_LENGTH, HeaderValue},
23    service::service_fn,
24};
25use hyper_util::{
26    rt::{TokioExecutor, TokioIo},
27    server::conn::auto::Builder,
28};
29use tokio::net::TcpListener;
30use tokio::sync::Mutex;
31use tokio_rustls::TlsAcceptor;
32
33use std::net::{SocketAddr, ToSocketAddrs};
34use std::sync::Arc;
35
36use crate::{
37    dyn_route::dyn_route_content,
38    error::{ServerError, ServerResult},
39    middleware::LoadedMiddlewares,
40    parsed_request::{capture_in_log, parsed_request_from},
41    respond_response::respond_response,
42    response::error_response::internal_server_error_response,
43    response_handler::default_response_headers,
44    tls::{build_server_config_reloadable, load_certs, load_private_key},
45    types::BoxBody,
46};
47
48pub use crate::control::{ReloadHint, ServerControl, ServerHandle, ServerState};
49
50/// Shared state cloned into each per-request task.
51#[derive(Clone)]
52pub struct AppState {
53    pub config: Config,
54    pub middlewares: LoadedMiddlewares,
55}
56
57/// HTTP(S) server.
58pub struct Server {
59    pub app_state: AppState,
60    pub http_addr: Option<SocketAddr>,
61    pub https_addr: Option<SocketAddr>,
62}
63
64impl Server {
65    /// Resolve listener addresses and build the server shell.
66    ///
67    /// Also compiles Rhai middlewares listed in
68    /// `config.service.middlewares_file_paths`. Compilation happens here
69    /// (not in the config crate) because the compiled artefact is a
70    /// runtime object — see the server-level module docstring.
71    pub async fn new(config: Config) -> ServerResult<Self> {
72        let http_addr = resolve_listener(config.listener_http_addr().as_deref())?;
73        let https_addr = resolve_listener(config.listener_https_addr().as_deref())?;
74
75        // Resolve middleware paths against the config file's dir
76        let relative_dir_path = config
77            .current_dir_to_parent_dir_relative_path()
78            .map_err(ServerError::Config)?;
79
80        let middlewares = LoadedMiddlewares::compile(
81            config.service.middlewares_file_paths.as_deref().unwrap_or(&[]),
82            relative_dir_path.as_str(),
83        )?;
84        if !middlewares.is_empty() {
85            log::info!("middleware is activated: {} file(s)", middlewares.len());
86        }
87
88        Ok(Server {
89            http_addr,
90            https_addr,
91            app_state: AppState {
92                config,
93                middlewares,
94            },
95        })
96    }
97
98    /// Start both listeners (whichever are configured) and block.
99    pub async fn start(&self) {
100        let http = self.http_start();
101        let https = self.https_start();
102        tokio::join!(http, https);
103    }
104
105    async fn http_start(&self) {
106        let Some(addr) = self.http_addr else {
107            return;
108        };
109
110        let listener = match TcpListener::bind(addr).await {
111            Ok(l) => l,
112            Err(err) => {
113                log::error!("failed to bind HTTP listener at {}: {}", addr, err);
114                return;
115            }
116        };
117
118        log::info!(
119            "Greetings from apimock-rs (API Mock) !!\nListening on {} ...\n",
120            style(format!("http://{}", addr)).cyan()
121        );
122
123        let app_state = Arc::new(Mutex::new(self.app_state.clone()));
124        loop {
125            let (stream, _) = match listener.accept().await {
126                Ok(pair) => pair,
127                Err(err) => {
128                    log::error!("HTTP accept failed: {}", err);
129                    continue;
130                }
131            };
132            let io = TokioIo::new(stream);
133
134            let app_state = app_state.clone();
135            tokio::task::spawn(async move {
136                if let Err(err) = Builder::new(TokioExecutor::new())
137                    .serve_connection(
138                        io,
139                        service_fn(move |request: hyper::Request<body::Incoming>| {
140                            service(request, app_state.clone())
141                        }),
142                    )
143                    .await
144                {
145                    log::error!("{} to build connection: {:?}", style("failed").red(), err);
146                }
147            });
148        }
149    }
150
151    async fn https_start(&self) {
152        let Some(addr) = self.https_addr else {
153            return;
154        };
155
156        let tls = match self
157            .app_state
158            .config
159            .listener
160            .as_ref()
161            .and_then(|l| l.tls.as_ref())
162        {
163            Some(t) => t.clone(),
164            None => {
165                log::error!("internal: HTTPS listener scheduled without TLS config");
166                return;
167            }
168        };
169
170        let certs = match load_certs(tls.cert.as_str()) {
171            Ok(c) => c,
172            Err(err) => {
173                log::error!("{}", err);
174                return;
175            }
176        };
177        let key = match load_private_key(tls.key.as_str()) {
178            Ok(k) => k,
179            Err(err) => {
180                log::error!("{}", err);
181                return;
182            }
183        };
184
185        // RFC 020: use a reloadable resolver so TlsCertFile / TlsKeyFile
186        // changes are SoftReload (no listener rebind needed).
187        let (tls_config, resolver) = match build_server_config_reloadable(certs, key) {
188            Ok(pair) => pair,
189            Err(err) => {
190                log::error!("failed to build TLS config: {}", err);
191                return;
192            }
193        };
194        let acceptor = TlsAcceptor::from(Arc::new(tls_config));
195        drop(resolver); // Server holds the resolver via the config; expose via ServerHandle if needed
196
197        let listener = match TcpListener::bind(addr).await {
198            Ok(l) => l,
199            Err(err) => {
200                log::error!("failed to bind HTTPS listener at {}: {}", addr, err);
201                return;
202            }
203        };
204
205        log::info!(
206            "Greetings from apimock-rs (API Mock) !!\nListening on {} ...\n",
207            style(format!("https://{}", addr)).cyan()
208        );
209
210        let app_state = Arc::new(Mutex::new(self.app_state.clone()));
211        loop {
212            let (stream, _) = match listener.accept().await {
213                Ok(pair) => pair,
214                Err(err) => {
215                    log::error!("HTTPS accept failed: {}", err);
216                    continue;
217                }
218            };
219            let acceptor = acceptor.clone();
220            let app_state = app_state.clone();
221
222            tokio::spawn(async move {
223                let tls_stream = match acceptor.accept(stream).await {
224                    Ok(s) => s,
225                    Err(e) => {
226                        log::error!("TLS handshake failed: {:?}", e);
227                        return;
228                    }
229                };
230                let io = TokioIo::new(tls_stream);
231                let app_state = app_state.clone();
232                tokio::task::spawn(async move {
233                    if let Err(err) = Builder::new(TokioExecutor::new())
234                        .serve_connection(
235                            io,
236                            service_fn(move |request: hyper::Request<body::Incoming>| {
237                                service(request, app_state.clone())
238                            }),
239                        )
240                        .await
241                    {
242                        log::error!("{} to build connection: {:?}", style("failed").red(), err);
243                    }
244                });
245            });
246        }
247    }
248}
249
250/// Resolve an `ip:port` string into a single `SocketAddr`.
251fn resolve_listener(addr_str: Option<&str>) -> ServerResult<Option<SocketAddr>> {
252    let Some(addr_str) = addr_str else {
253        return Ok(None);
254    };
255
256    let mut addrs = addr_str
257        .to_socket_addrs()
258        .map_err(|e| ServerError::ListenerAddress {
259            addr: addr_str.to_owned(),
260            reason: e.to_string(),
261        })?;
262
263    addrs
264        .next()
265        .map(Some)
266        .ok_or_else(|| ServerError::ListenerAddress {
267            addr: addr_str.to_owned(),
268            reason: "address resolved to no socket addresses".to_owned(),
269        })
270}
271
272/// Entry point for each HTTP request.
273///
274/// # Routing order
275///
276/// OPTIONS → middleware → rule sets → dyn_route (fallback). See
277/// `respond_response` and `dyn_route_content` for each step's details.
278pub async fn service(
279    request: hyper::Request<body::Incoming>,
280    app_state: Arc<Mutex<AppState>>,
281) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
282    let request_headers = request.headers().clone();
283
284    if request.method() == hyper::Method::OPTIONS {
285        return handle_options(&request_headers);
286    }
287
288    let parsed_request = match parsed_request_from(request).await {
289        Ok(x) => x,
290        Err(err) => return internal_server_error_response(err.as_str(), &request_headers),
291    };
292
293    let shared_app_state = { app_state.lock().await.clone() };
294
295    let config = shared_app_state.config;
296    let middlewares = shared_app_state.middlewares;
297
298    capture_in_log(&parsed_request, config.log.clone().unwrap_or_default().verbose);
299
300    if let Some(response) = middleware_response(&middlewares, &parsed_request).await {
301        return response;
302    }
303
304    if let Some(response) = rule_set_response(&config, &parsed_request).await {
305        return response;
306    }
307
308    dyn_route_content(
309        parsed_request.url_path.as_str(),
310        config.service.fallback_respond_dir.as_str(),
311        &request_headers,
312    )
313    .await
314}
315
316/// Dispatch the request through every loaded middleware in order.
317async fn middleware_response(
318    middlewares: &LoadedMiddlewares,
319    parsed_request: &ParsedRequest,
320) -> Option<Result<hyper::Response<BoxBody>, hyper::http::Error>> {
321    for handler in middlewares.iter() {
322        match handler
323            .handle(
324                parsed_request.url_path.as_str(),
325                parsed_request.body_json.as_ref(),
326                &parsed_request.component_parts.headers,
327            )
328            .await
329        {
330            Some(x) => return Some(x),
331            None => continue,
332        }
333    }
334    None
335}
336
337/// Dispatch through the configured rule sets.
338async fn rule_set_response(
339    config: &Config,
340    parsed_request: &ParsedRequest,
341) -> Option<Result<hyper::Response<BoxBody>, hyper::http::Error>> {
342    for (rule_set_idx, rule_set) in config.service.rule_sets.iter().enumerate() {
343        if let Some(respond) =
344            rule_set.find_matched(parsed_request, config.service.strategy.as_ref(), rule_set_idx)
345        {
346            let dir_prefix = rule_set.dir_prefix();
347            return Some(respond_response(&respond, dir_prefix.as_str(), parsed_request).await);
348        }
349    }
350    None
351}
352
353/// OPTIONS request handler (CORS preflight).
354fn handle_options(
355    request_headers: &HeaderMap,
356) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
357    let mut response = Response::new(Empty::new().boxed());
358    *response.status_mut() = hyper::StatusCode::NO_CONTENT;
359    response
360        .headers_mut()
361        .insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
362
363    for (header_key, header_value) in default_response_headers(request_headers).into_iter() {
364        if let Some(header_key) = header_key {
365            response.headers_mut().insert(header_key, header_value);
366        }
367    }
368
369    Ok(response)
370}