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