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