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::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};
49use crate::trace::{Outcome, RequestSummary, TraceEmitter};
50
51#[derive(Clone)]
53pub struct AppState {
54 pub config: Config,
55 pub middlewares: LoadedMiddlewares,
56 pub tracer: TraceEmitter,
58}
59
60pub struct Server {
62 pub app_state: AppState,
63 pub http_addr: Option<SocketAddr>,
64 pub https_addr: Option<SocketAddr>,
65}
66
67impl Server {
68 pub async fn new(config: Config) -> ServerResult<Self> {
75 let http_addr = resolve_listener(config.listener_http_addr().as_deref())?;
76 let https_addr = resolve_listener(config.listener_https_addr().as_deref())?;
77
78 let relative_dir_path = config
80 .current_dir_to_parent_dir_relative_path()
81 .map_err(ServerError::Config)?;
82
83 let middlewares = LoadedMiddlewares::compile(
84 config
85 .service
86 .middlewares_file_paths
87 .as_deref()
88 .unwrap_or(&[]),
89 relative_dir_path.as_str(),
90 )?;
91 if !middlewares.is_empty() {
92 log::info!("middleware is activated: {} file(s)", middlewares.len());
93 }
94
95 Ok(Server {
96 http_addr,
97 https_addr,
98 app_state: AppState {
99 config,
100 middlewares,
101 tracer: TraceEmitter::new(),
102 },
103 })
104 }
105
106 pub async fn start(&self) {
108 let http = self.http_start();
109 let https = self.https_start();
110 tokio::join!(http, https);
111 }
112
113 async fn http_start(&self) {
114 let Some(addr) = self.http_addr else {
115 return;
116 };
117
118 let listener = match TcpListener::bind(addr).await {
119 Ok(l) => l,
120 Err(err) => {
121 log::error!("failed to bind HTTP listener at {}: {}", addr, err);
122 return;
123 }
124 };
125
126 log::info!(
127 "Greetings from apimock-rs (API Mock) !!\nListening on {} ...\n",
128 style(format!("http://{}", addr)).cyan()
129 );
130
131 let app_state = Arc::new(Mutex::new(self.app_state.clone()));
132 loop {
133 let (stream, _) = match listener.accept().await {
134 Ok(pair) => pair,
135 Err(err) => {
136 log::error!("HTTP accept failed: {}", err);
137 continue;
138 }
139 };
140 let io = TokioIo::new(stream);
141
142 let app_state = app_state.clone();
143 tokio::task::spawn(async move {
144 if let Err(err) = Builder::new(TokioExecutor::new())
145 .serve_connection(
146 io,
147 service_fn(move |request: hyper::Request<body::Incoming>| {
148 service(request, app_state.clone())
149 }),
150 )
151 .await
152 {
153 log::error!("{} to build connection: {:?}", style("failed").red(), err);
154 }
155 });
156 }
157 }
158
159 async fn https_start(&self) {
160 let Some(addr) = self.https_addr else {
161 return;
162 };
163
164 let tls = match self
165 .app_state
166 .config
167 .listener
168 .as_ref()
169 .and_then(|l| l.tls.as_ref())
170 {
171 Some(t) => t.clone(),
172 None => {
173 log::error!("internal: HTTPS listener scheduled without TLS config");
174 return;
175 }
176 };
177
178 let certs = match load_certs(tls.cert.as_str()) {
179 Ok(c) => c,
180 Err(err) => {
181 log::error!("{}", err);
182 return;
183 }
184 };
185 let key = match load_private_key(tls.key.as_str()) {
186 Ok(k) => k,
187 Err(err) => {
188 log::error!("{}", err);
189 return;
190 }
191 };
192
193 let (tls_config, resolver) = match build_server_config_reloadable(certs, key) {
196 Ok(pair) => pair,
197 Err(err) => {
198 log::error!("failed to build TLS config: {}", err);
199 return;
200 }
201 };
202 let acceptor = TlsAcceptor::from(Arc::new(tls_config));
203 drop(resolver); let listener = match TcpListener::bind(addr).await {
206 Ok(l) => l,
207 Err(err) => {
208 log::error!("failed to bind HTTPS listener at {}: {}", addr, err);
209 return;
210 }
211 };
212
213 log::info!(
214 "Greetings from apimock-rs (API Mock) !!\nListening on {} ...\n",
215 style(format!("https://{}", addr)).cyan()
216 );
217
218 let app_state = Arc::new(Mutex::new(self.app_state.clone()));
219 loop {
220 let (stream, _) = match listener.accept().await {
221 Ok(pair) => pair,
222 Err(err) => {
223 log::error!("HTTPS accept failed: {}", err);
224 continue;
225 }
226 };
227 let acceptor = acceptor.clone();
228 let app_state = app_state.clone();
229
230 tokio::spawn(async move {
231 let tls_stream = match acceptor.accept(stream).await {
232 Ok(s) => s,
233 Err(e) => {
234 log::error!("TLS handshake failed: {:?}", e);
235 return;
236 }
237 };
238 let io = TokioIo::new(tls_stream);
239 let app_state = app_state.clone();
240 tokio::task::spawn(async move {
241 if let Err(err) = Builder::new(TokioExecutor::new())
242 .serve_connection(
243 io,
244 service_fn(move |request: hyper::Request<body::Incoming>| {
245 service(request, app_state.clone())
246 }),
247 )
248 .await
249 {
250 log::error!("{} to build connection: {:?}", style("failed").red(), err);
251 }
252 });
253 });
254 }
255 }
256}
257
258#[allow(clippy::result_large_err)]
263fn resolve_listener(addr_str: Option<&str>) -> ServerResult<Option<SocketAddr>> {
264 let Some(addr_str) = addr_str else {
265 return Ok(None);
266 };
267
268 let mut addrs = addr_str
269 .to_socket_addrs()
270 .map_err(|e| ServerError::ListenerAddress {
271 addr: addr_str.to_owned(),
272 reason: e.to_string(),
273 })?;
274
275 addrs
276 .next()
277 .map(Some)
278 .ok_or_else(|| ServerError::ListenerAddress {
279 addr: addr_str.to_owned(),
280 reason: "address resolved to no socket addresses".to_owned(),
281 })
282}
283
284pub async fn service(
291 request: hyper::Request<body::Incoming>,
292 app_state: Arc<Mutex<AppState>>,
293) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
294 let request_headers = request.headers().clone();
295
296 if request.method() == hyper::Method::OPTIONS {
297 return handle_options(&request_headers);
298 }
299
300 let parsed_request = match parsed_request_from(request).await {
301 Ok(x) => x,
302 Err(err) => return internal_server_error_response(err.as_str(), &request_headers),
303 };
304
305 let shared_app_state = { app_state.lock().await.clone() };
306
307 let config = shared_app_state.config;
308 let middlewares = shared_app_state.middlewares;
309 let tracer = shared_app_state.tracer;
310
311 let received_at_ms = std::time::SystemTime::now()
312 .duration_since(std::time::UNIX_EPOCH)
313 .unwrap_or_default()
314 .as_millis() as u64;
315 let start = std::time::Instant::now();
316
317 capture_in_log(
318 &parsed_request,
319 config.log.clone().unwrap_or_default().verbose,
320 );
321
322 if let Some(response) = middleware_response(&middlewares, &parsed_request).await {
323 return response;
324 }
325
326 if let Some(response) = rule_set_response(&config, &parsed_request).await {
327 if tracer.has_subscribers() {
329 let mut summary = RequestSummary {
330 method: parsed_request.component_parts.method.to_string(),
331 url_path: parsed_request.url_path.clone(),
332 headers: parsed_request
333 .component_parts
334 .headers
335 .iter()
336 .filter_map(|(k, v)| v.to_str().ok().map(|v| (k.to_string(), v.to_owned())))
337 .collect(),
338 body_json: None,
339 body_truncated: false,
340 };
341 tracer.enrich_with_body(&mut summary, parsed_request.body_json.as_ref());
342 tracer.emit(
343 received_at_ms,
344 start.elapsed().as_millis() as u32,
345 summary,
346 Outcome::Miss { status: 0 }, );
348 }
349 return response;
350 }
351
352 dyn_route_content(
353 parsed_request.url_path.as_str(),
354 config.service.fallback_respond_dir.as_str(),
355 &request_headers,
356 )
357 .await
358}
359
360async fn middleware_response(
362 middlewares: &LoadedMiddlewares,
363 parsed_request: &ParsedRequest,
364) -> Option<Result<hyper::Response<BoxBody>, hyper::http::Error>> {
365 for handler in middlewares.iter() {
366 match handler
367 .handle(
368 parsed_request.url_path.as_str(),
369 parsed_request.body_json.as_ref(),
370 &parsed_request.component_parts.headers,
371 )
372 .await
373 {
374 Some(x) => return Some(x),
375 None => continue,
376 }
377 }
378 None
379}
380
381async fn rule_set_response(
383 config: &Config,
384 parsed_request: &ParsedRequest,
385) -> Option<Result<hyper::Response<BoxBody>, hyper::http::Error>> {
386 for (rule_set_idx, rule_set) in config.service.rule_sets.iter().enumerate() {
387 if let Some(respond) = rule_set.find_matched(
388 parsed_request,
389 config.service.strategy.as_ref(),
390 rule_set_idx,
391 ) {
392 let dir_prefix = rule_set.dir_prefix();
393 return Some(respond_response(&respond, dir_prefix.as_str(), parsed_request).await);
394 }
395 }
396 None
397}
398
399fn handle_options(
401 request_headers: &HeaderMap,
402) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
403 let mut response = Response::new(Empty::new().boxed());
404 *response.status_mut() = hyper::StatusCode::NO_CONTENT;
405 response
406 .headers_mut()
407 .insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
408
409 for (header_key, header_value) in default_response_headers(request_headers).into_iter() {
410 if let Some(header_key) = header_key {
411 response.headers_mut().insert(header_key, header_value);
412 }
413 }
414
415 Ok(response)
416}