1use 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::path::PathBuf;
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_with_trace_config, parsed_request_from},
42 respond_response::respond_response,
43 response::{confine::canonical_dir, error_response::internal_server_error_response},
44 response_handler::default_response_headers,
45 tls::{build_server_config_reloadable, load_certs, load_private_key},
46 types::BoxBody,
47};
48
49pub use crate::control::{ReloadHint, ServerControl, ServerHandle, ServerState};
50use crate::trace::{Outcome, RequestSummary, TraceEmitter};
51
52#[derive(Clone)]
54#[non_exhaustive]
55pub struct AppState {
56 pub config: Config,
57 pub middlewares: LoadedMiddlewares,
58 pub tracer: TraceEmitter,
60 canonical_fallback_respond_dir: Option<PathBuf>,
64 canonical_rule_set_respond_dirs: Vec<Option<PathBuf>>,
66}
67
68impl AppState {
69 pub fn new(config: Config, middlewares: LoadedMiddlewares, tracer: TraceEmitter) -> Self {
70 let canonical_fallback_respond_dir =
71 canonical_dir(config.service.fallback_respond_dir.as_str());
72 let canonical_rule_set_respond_dirs = config
73 .service
74 .rule_sets
75 .iter()
76 .map(|rule_set| canonical_dir(rule_set.dir_prefix().as_str()))
77 .collect();
78 Self {
79 config,
80 middlewares,
81 tracer,
82 canonical_fallback_respond_dir,
83 canonical_rule_set_respond_dirs,
84 }
85 }
86}
87
88#[non_exhaustive]
90pub struct Server {
91 pub app_state: AppState,
92 pub http_addr: Option<SocketAddr>,
93 pub https_addr: Option<SocketAddr>,
94}
95
96impl Server {
97 pub async fn new(config: Config) -> ServerResult<Self> {
104 let http_addr = resolve_listener(config.listener_http_addr().as_deref())?;
105 let https_addr = resolve_listener(config.listener_https_addr().as_deref())?;
106
107 let relative_dir_path = config
109 .current_dir_to_parent_dir_relative_path()
110 .map_err(ServerError::Config)?;
111
112 let middlewares = LoadedMiddlewares::compile(
113 config
114 .service
115 .middlewares_file_paths
116 .as_deref()
117 .unwrap_or(&[]),
118 relative_dir_path.as_str(),
119 )?;
120 if !middlewares.is_empty() {
121 log::info!("middleware is activated: {} file(s)", middlewares.len());
122 }
123
124 Ok(Server {
125 http_addr,
126 https_addr,
127 app_state: AppState::new(config, middlewares, TraceEmitter::new()),
128 })
129 }
130
131 pub async fn start(&self) {
133 let http = self.http_start();
134 let https = self.https_start();
135 tokio::join!(http, https);
136 }
137
138 pub async fn bind_http(&self) -> ServerResult<Option<TcpListener>> {
153 let Some(addr) = self.http_addr else {
154 return Ok(None);
155 };
156
157 let listener =
158 TcpListener::bind(addr)
159 .await
160 .map_err(|err| ServerError::ListenerAddress {
161 addr: addr.to_string(),
162 reason: err.to_string(),
163 })?;
164
165 Ok(Some(listener))
166 }
167
168 pub async fn serve_http(&self, listener: TcpListener) {
170 if let Ok(addr) = listener.local_addr() {
171 log::info!(
172 "Greetings from apimock-rs (API Mock) !!\nListening on {} ...\n",
173 style(format!("http://{}", addr)).cyan()
174 );
175 }
176
177 let app_state = Arc::new(Mutex::new(self.app_state.clone()));
178 loop {
179 let (stream, _) = match listener.accept().await {
180 Ok(pair) => pair,
181 Err(err) => {
182 log::error!("HTTP accept failed: {}", err);
183 continue;
184 }
185 };
186 let io = TokioIo::new(stream);
187
188 let app_state = app_state.clone();
189 tokio::task::spawn(async move {
190 if let Err(err) = Builder::new(TokioExecutor::new())
191 .serve_connection(
192 io,
193 service_fn(move |request: hyper::Request<body::Incoming>| {
194 service(request, app_state.clone())
195 }),
196 )
197 .await
198 {
199 log::error!("{} to build connection: {:?}", style("failed").red(), err);
200 }
201 });
202 }
203 }
204
205 async fn http_start(&self) {
206 match self.bind_http().await {
207 Ok(Some(listener)) => self.serve_http(listener).await,
208 Ok(None) => (),
209 Err(err) => log::error!("{}", err),
210 }
211 }
212
213 pub async fn bind_https(&self) -> ServerResult<Option<(TcpListener, TlsAcceptor)>> {
221 let Some(addr) = self.https_addr else {
222 return Ok(None);
223 };
224
225 let tls = self
226 .app_state
227 .config
228 .listener
229 .as_ref()
230 .and_then(|l| l.tls.as_ref())
231 .cloned()
232 .ok_or_else(|| ServerError::ListenerAddress {
233 addr: addr.to_string(),
234 reason: "internal: HTTPS listener scheduled without TLS config".to_owned(),
235 })?;
236
237 let certs = load_certs(tls.cert.as_str())?;
238 let key = load_private_key(tls.key.as_str())?;
239
240 let (tls_config, resolver) = build_server_config_reloadable(certs, key).map_err(|err| {
243 ServerError::ListenerAddress {
244 addr: addr.to_string(),
245 reason: format!("failed to build TLS config: {}", err),
246 }
247 })?;
248 let acceptor = TlsAcceptor::from(Arc::new(tls_config));
249 drop(resolver); let listener =
252 TcpListener::bind(addr)
253 .await
254 .map_err(|err| ServerError::ListenerAddress {
255 addr: addr.to_string(),
256 reason: err.to_string(),
257 })?;
258
259 Ok(Some((listener, acceptor)))
260 }
261
262 pub async fn serve_https(&self, listener: TcpListener, acceptor: TlsAcceptor) {
264 if let Ok(addr) = listener.local_addr() {
265 log::info!(
266 "Greetings from apimock-rs (API Mock) !!\nListening on {} ...\n",
267 style(format!("https://{}", addr)).cyan()
268 );
269 }
270
271 let app_state = Arc::new(Mutex::new(self.app_state.clone()));
272 loop {
273 let (stream, _) = match listener.accept().await {
274 Ok(pair) => pair,
275 Err(err) => {
276 log::error!("HTTPS accept failed: {}", err);
277 continue;
278 }
279 };
280 let acceptor = acceptor.clone();
281 let app_state = app_state.clone();
282
283 tokio::spawn(async move {
284 let tls_stream = match acceptor.accept(stream).await {
285 Ok(s) => s,
286 Err(e) => {
287 log::error!("TLS handshake failed: {:?}", e);
288 return;
289 }
290 };
291 let io = TokioIo::new(tls_stream);
292 let app_state = app_state.clone();
293 tokio::task::spawn(async move {
294 if let Err(err) = Builder::new(TokioExecutor::new())
295 .serve_connection(
296 io,
297 service_fn(move |request: hyper::Request<body::Incoming>| {
298 service(request, app_state.clone())
299 }),
300 )
301 .await
302 {
303 log::error!("{} to build connection: {:?}", style("failed").red(), err);
304 }
305 });
306 });
307 }
308 }
309
310 async fn https_start(&self) {
311 match self.bind_https().await {
312 Ok(Some((listener, acceptor))) => self.serve_https(listener, acceptor).await,
313 Ok(None) => (),
314 Err(err) => log::error!("{}", err),
315 }
316 }
317}
318
319fn resolve_listener(addr_str: Option<&str>) -> ServerResult<Option<SocketAddr>> {
321 let Some(addr_str) = addr_str else {
322 return Ok(None);
323 };
324
325 let mut addrs = addr_str
326 .to_socket_addrs()
327 .map_err(|e| ServerError::ListenerAddress {
328 addr: addr_str.to_owned(),
329 reason: e.to_string(),
330 })?;
331
332 addrs
333 .next()
334 .map(Some)
335 .ok_or_else(|| ServerError::ListenerAddress {
336 addr: addr_str.to_owned(),
337 reason: "address resolved to no socket addresses".to_owned(),
338 })
339}
340
341pub async fn service(
348 request: hyper::Request<body::Incoming>,
349 app_state: Arc<Mutex<AppState>>,
350) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
351 let request_headers = request.headers().clone();
352
353 if request.method() == hyper::Method::OPTIONS {
354 return handle_options(&request_headers);
355 }
356
357 let parsed_request = match parsed_request_from(request).await {
358 Ok(x) => x,
359 Err(err) => return internal_server_error_response(err.as_str(), &request_headers),
360 };
361
362 let shared_app_state = { app_state.lock().await.clone() };
363
364 let config = shared_app_state.config;
365 let middlewares = shared_app_state.middlewares;
366 let tracer = shared_app_state.tracer;
367 let canonical_fallback_respond_dir = shared_app_state.canonical_fallback_respond_dir;
368 let canonical_rule_set_respond_dirs = shared_app_state.canonical_rule_set_respond_dirs;
369
370 let received_at_ms = std::time::SystemTime::now()
371 .duration_since(std::time::UNIX_EPOCH)
372 .unwrap_or_default()
373 .as_millis() as u64;
374 let start = std::time::Instant::now();
375
376 capture_in_log_with_trace_config(
377 &parsed_request,
378 config.log.clone().unwrap_or_default().verbose,
379 &tracer.config,
380 );
381
382 if let Some(response) = middleware_response(&middlewares, &parsed_request).await {
383 return response;
384 }
385
386 if let Some(response) = rule_set_response(
387 &config,
388 &parsed_request,
389 canonical_rule_set_respond_dirs.as_slice(),
390 )
391 .await
392 {
393 if tracer.has_subscribers() {
395 let headers = parsed_request
396 .component_parts
397 .headers
398 .iter()
399 .filter_map(|(k, v)| v.to_str().ok().map(|v| (k.to_string(), v.to_owned())))
400 .collect();
401 let mut summary = RequestSummary::new(
402 parsed_request.component_parts.method.to_string(),
403 parsed_request.url_path.clone(),
404 headers,
405 parsed_request.body_len,
406 &tracer.config,
407 );
408 tracer.enrich_with_body(&mut summary, parsed_request.body_json.as_ref());
409 tracer.emit(
410 received_at_ms,
411 start.elapsed().as_millis() as u32,
412 summary,
413 Outcome::Miss { status: 0 }, );
415 }
416 return response;
417 }
418
419 dyn_route_content(
420 parsed_request.url_path.as_str(),
421 config.service.fallback_respond_dir.as_str(),
422 &request_headers,
423 canonical_fallback_respond_dir.as_deref(),
424 )
425 .await
426}
427
428async fn middleware_response(
430 middlewares: &LoadedMiddlewares,
431 parsed_request: &ParsedRequest,
432) -> Option<Result<hyper::Response<BoxBody>, hyper::http::Error>> {
433 for handler in middlewares.iter() {
434 match handler
435 .handle(
436 parsed_request.url_path.as_str(),
437 parsed_request.body_json.as_ref(),
438 &parsed_request.component_parts.headers,
439 )
440 .await
441 {
442 Some(x) => return Some(x),
443 None => continue,
444 }
445 }
446 None
447}
448
449async fn rule_set_response(
454 config: &Config,
455 parsed_request: &ParsedRequest,
456 canonical_rule_set_respond_dirs: &[Option<PathBuf>],
457) -> Option<Result<hyper::Response<BoxBody>, hyper::http::Error>> {
458 for (rule_set_idx, rule_set) in config.service.rule_sets.iter().enumerate() {
459 if let Some((_rule_idx, respond)) = rule_set.find_matched(
460 parsed_request,
461 config.service.strategy.as_ref(),
462 rule_set_idx,
463 ) {
464 let dir_prefix = rule_set.dir_prefix();
465 let rule_set_default_delay_ms = rule_set
466 .default
467 .as_ref()
468 .and_then(|default| default.delay_response_milliseconds);
469 let confine_to = canonical_rule_set_respond_dirs
470 .get(rule_set_idx)
471 .and_then(|dir| dir.as_deref());
472 return Some(
473 respond_response(
474 &respond,
475 dir_prefix.as_str(),
476 parsed_request,
477 rule_set_default_delay_ms,
478 confine_to,
479 )
480 .await,
481 );
482 }
483 }
484 None
485}
486
487pub fn handle_options(
491 request_headers: &HeaderMap,
492) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
493 let mut response = Response::new(Empty::new().boxed());
494 *response.status_mut() = hyper::StatusCode::NO_CONTENT;
495 response
496 .headers_mut()
497 .insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
498
499 for (header_key, header_value) in default_response_headers(request_headers).into_iter() {
500 if let Some(header_key) = header_key {
501 response.headers_mut().insert(header_key, header_value);
502 }
503 }
504
505 Ok(response)
506}