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