use crate::handler::{BoxedHandler, IntoHandler};
use crate::{Error, HttpMethod, HttpRequest, HttpResponse, Router};
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::future::Future;
use std::net::ToSocketAddrs;
use std::pin::Pin;
use std::sync::Arc;
pub use crate::{HttpRequest as Request, HttpResponse as Response};
#[derive(Clone)]
pub struct Data<T: Clone + Send + Sync + 'static>(Arc<T>);
impl<T: Clone + Send + Sync + 'static> Data<T> {
pub fn new(data: T) -> Self {
Self(Arc::new(data))
}
pub fn get_ref(&self) -> &T {
&self.0
}
pub fn into_inner(self) -> Arc<T> {
self.0
}
}
impl<T: Clone + Send + Sync + 'static> std::ops::Deref for Data<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct App {
router: Router,
middleware: Vec<Arc<dyn Middleware>>,
state: AppState,
default_service: Option<BoxedHandler>,
}
#[derive(Default, Clone)]
struct AppState {
data: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
}
impl AppState {
fn insert<T: Clone + Send + Sync + 'static>(&mut self, data: T) {
self.data.insert(TypeId::of::<T>(), Arc::new(data));
}
#[allow(dead_code)]
pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<Data<T>> {
self.data
.get(&TypeId::of::<T>())
.and_then(|arc| arc.downcast_ref::<T>())
.map(|t| Data(Arc::new(t.clone())))
}
}
impl App {
pub fn new() -> Self {
Self {
router: Router::new(),
middleware: Vec::new(),
state: AppState::default(),
default_service: None,
}
}
pub fn data<T: Clone + Send + Sync + 'static>(mut self, data: T) -> Self {
self.state.insert(data);
self
}
pub fn wrap<M: Middleware + 'static>(mut self, middleware: M) -> Self {
self.middleware.push(Arc::new(middleware));
self
}
pub fn route(mut self, path: &str, route: RouteBuilder) -> Self {
for (method, handler) in route.handlers {
self.router.add_route(crate::routing::Route {
method,
path: path.to_string(),
handler,
constraints: None,
});
}
self
}
pub fn service(mut self, scope: Scope) -> Self {
for route in scope.routes {
let full_path = format!("{}{}", scope.prefix, route.path);
self.router.add_route(crate::routing::Route {
method: route.method,
path: full_path,
handler: route.handler,
constraints: route.constraints,
});
}
self
}
pub fn default_service<H, Args>(mut self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.default_service = Some(BoxedHandler::new(handler.into_handler()));
self
}
pub async fn run(self, addr: impl ToSocketAddrs) -> std::io::Result<()> {
let addr = addr.to_socket_addrs()?.next().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid address")
})?;
let app = Arc::new(BuiltApp {
router: self.router,
middleware: self.middleware,
state: self.state,
default_service: self.default_service,
});
run_server(app, addr).await
}
pub fn build(self) -> BuiltApp {
BuiltApp {
router: self.router,
middleware: self.middleware,
state: self.state,
default_service: self.default_service,
}
}
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
pub struct BuiltApp {
router: Router,
middleware: Vec<Arc<dyn Middleware>>,
state: AppState,
default_service: Option<BoxedHandler>,
}
impl BuiltApp {
pub async fn handle(&self, mut request: HttpRequest) -> Result<HttpResponse, Error> {
request.extensions.insert(self.state.clone());
let router = self.router.clone();
let default_service = self.default_service.clone();
let handler: Next = Box::new(move |req| {
let router = router.clone();
let default_service = default_service.clone();
Box::pin(async move {
match router.route(req).await {
Ok(response) => Ok(response),
Err(Error::RouteNotFound(_)) if default_service.is_some() => {
let req = HttpRequest::new("GET".to_string(), "/404".to_string());
default_service.unwrap().call(req).await
}
Err(e) => Err(e),
}
})
});
let mut next = handler;
for mw in self.middleware.iter().rev() {
let mw = mw.clone();
next = Box::new(move |req| mw.call(req, next));
}
next(request).await
}
}
pub struct RouteBuilder {
handlers: Vec<(HttpMethod, BoxedHandler)>,
}
impl RouteBuilder {
fn new() -> Self {
Self {
handlers: Vec::new(),
}
}
fn with_method<H, Args>(mut self, method: HttpMethod, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.handlers
.push((method, BoxedHandler::new(handler.into_handler())));
self
}
pub fn get<H, Args>(self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.with_method(HttpMethod::GET, handler)
}
pub fn post<H, Args>(self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.with_method(HttpMethod::POST, handler)
}
pub fn put<H, Args>(self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.with_method(HttpMethod::PUT, handler)
}
pub fn delete<H, Args>(self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.with_method(HttpMethod::DELETE, handler)
}
pub fn patch<H, Args>(self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.with_method(HttpMethod::PATCH, handler)
}
pub fn head<H, Args>(self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.with_method(HttpMethod::HEAD, handler)
}
pub fn options<H, Args>(self, handler: H) -> Self
where
H: IntoHandler<Args>,
{
self.with_method(HttpMethod::OPTIONS, handler)
}
}
pub fn get<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args>,
{
RouteBuilder::new().get(handler)
}
pub fn post<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args>,
{
RouteBuilder::new().post(handler)
}
pub fn put<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args>,
{
RouteBuilder::new().put(handler)
}
pub fn delete<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args>,
{
RouteBuilder::new().delete(handler)
}
pub fn patch<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args>,
{
RouteBuilder::new().patch(handler)
}
pub fn head<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args>,
{
RouteBuilder::new().head(handler)
}
pub fn options<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args>,
{
RouteBuilder::new().options(handler)
}
pub fn any<H, Args>(handler: H) -> RouteBuilder
where
H: IntoHandler<Args> + Clone,
{
RouteBuilder::new()
.get(handler.clone())
.post(handler.clone())
.put(handler.clone())
.delete(handler.clone())
.patch(handler.clone())
.head(handler.clone())
.options(handler)
}
pub struct Scope {
prefix: String,
routes: Vec<ScopeRoute>,
middleware: Vec<Arc<dyn Middleware>>,
}
struct ScopeRoute {
method: HttpMethod,
path: String,
handler: BoxedHandler,
constraints: Option<crate::route_constraint::RouteConstraints>,
}
impl Scope {
fn new(prefix: impl Into<String>) -> Self {
Self {
prefix: prefix.into(),
routes: Vec::new(),
middleware: Vec::new(),
}
}
pub fn route(mut self, path: &str, route: RouteBuilder) -> Self {
for (method, handler) in route.handlers {
self.routes.push(ScopeRoute {
method,
path: path.to_string(),
handler,
constraints: None,
});
}
self
}
pub fn wrap<M: Middleware + 'static>(mut self, middleware: M) -> Self {
self.middleware.push(Arc::new(middleware));
self
}
pub fn service(mut self, inner: Scope) -> Self {
for route in inner.routes {
let full_path = format!("{}{}", inner.prefix, route.path);
self.routes.push(ScopeRoute {
method: route.method,
path: full_path,
handler: route.handler,
constraints: route.constraints,
});
}
self
}
}
pub fn scope(prefix: impl Into<String>) -> Scope {
Scope::new(prefix)
}
pub type Next = Box<
dyn FnOnce(HttpRequest) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>
+ Send,
>;
pub trait Middleware: Send + Sync {
fn call(
&self,
req: HttpRequest,
next: Next,
) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
}
pub struct Logger {
#[allow(dead_code)]
format: LogFormat,
}
#[derive(Clone, Copy, Default)]
pub enum LogFormat {
#[default]
Default,
Combined,
Short,
}
impl Default for Logger {
fn default() -> Self {
Self {
format: LogFormat::Default,
}
}
}
impl Logger {
pub fn new(format: LogFormat) -> Self {
Self { format }
}
}
impl Middleware for Logger {
fn call(
&self,
req: HttpRequest,
next: Next,
) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>> {
let method = req.method.clone();
let path = req.path.clone();
Box::pin(async move {
let start = std::time::Instant::now();
let result = next(req).await;
let elapsed = start.elapsed();
match &result {
Ok(response) => {
tracing::info!(
method = %method,
path = %path,
status = response.status,
duration_ms = elapsed.as_millis() as u64,
"Request completed"
);
}
Err(e) => {
tracing::error!(
method = %method,
path = %path,
error = %e,
duration_ms = elapsed.as_millis() as u64,
"Request failed"
);
}
}
result
})
}
}
pub struct Cors {
allowed_origins: Vec<String>,
allowed_methods: Vec<String>,
allowed_headers: Vec<String>,
allow_credentials: bool,
max_age: u32,
}
impl Default for Cors {
fn default() -> Self {
Self {
allowed_origins: vec!["*".to_string()],
allowed_methods: vec![
"GET".to_string(),
"POST".to_string(),
"PUT".to_string(),
"DELETE".to_string(),
"PATCH".to_string(),
"OPTIONS".to_string(),
],
allowed_headers: vec!["*".to_string()],
allow_credentials: false,
max_age: 86400,
}
}
}
impl Cors {
pub fn permissive() -> Self {
Self::default()
}
pub fn allowed_origins(mut self, origins: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.allowed_origins = origins.into_iter().map(Into::into).collect();
self
}
pub fn allowed_methods(mut self, methods: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.allowed_methods = methods.into_iter().map(Into::into).collect();
self
}
pub fn allowed_headers(mut self, headers: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.allowed_headers = headers.into_iter().map(Into::into).collect();
self
}
pub fn allow_credentials(mut self, allow: bool) -> Self {
self.allow_credentials = allow;
self
}
pub fn max_age(mut self, seconds: u32) -> Self {
self.max_age = seconds;
self
}
}
impl Middleware for Cors {
fn call(
&self,
req: HttpRequest,
next: Next,
) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>> {
let is_preflight = req.method == "OPTIONS";
let allowed_origins = self.allowed_origins.clone();
let allowed_methods = self.allowed_methods.join(", ");
let allowed_headers = self.allowed_headers.join(", ");
let allow_credentials = self.allow_credentials;
let max_age = self.max_age;
Box::pin(async move {
if is_preflight {
let mut response = HttpResponse::no_content();
response.headers.insert(
"Access-Control-Allow-Origin".to_string(),
allowed_origins.first().cloned().unwrap_or_default(),
);
response
.headers
.insert("Access-Control-Allow-Methods".to_string(), allowed_methods);
response
.headers
.insert("Access-Control-Allow-Headers".to_string(), allowed_headers);
response
.headers
.insert("Access-Control-Max-Age".to_string(), max_age.to_string());
if allow_credentials {
response.headers.insert(
"Access-Control-Allow-Credentials".to_string(),
"true".to_string(),
);
}
return Ok(response);
}
let mut response = next(req).await?;
response.headers.insert(
"Access-Control-Allow-Origin".to_string(),
allowed_origins.first().cloned().unwrap_or_default(),
);
if allow_credentials {
response.headers.insert(
"Access-Control-Allow-Credentials".to_string(),
"true".to_string(),
);
}
Ok(response)
})
}
}
pub struct Compress {
#[allow(dead_code)]
level: CompressionLevel,
}
#[derive(Clone, Copy, Default)]
pub enum CompressionLevel {
Fast,
#[default]
Default,
Best,
}
impl Default for Compress {
fn default() -> Self {
Self {
level: CompressionLevel::Default,
}
}
}
impl Compress {
pub fn new(level: CompressionLevel) -> Self {
Self { level }
}
}
impl Middleware for Compress {
fn call(
&self,
req: HttpRequest,
next: Next,
) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>> {
Box::pin(async move {
let mut response = next(req).await?;
response
.headers
.insert("Vary".to_string(), "Accept-Encoding".to_string());
Ok(response)
})
}
}
async fn run_server(app: Arc<BuiltApp>, addr: std::net::SocketAddr) -> std::io::Result<()> {
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
let listener = TcpListener::bind(addr).await?;
tracing::info!("Micro-framework server listening on http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let app = app.clone();
tokio::spawn(async move {
let service = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
let app = app.clone();
async move {
let method = req.method().to_string();
let path = req
.uri()
.path_and_query()
.map(|pq| pq.to_string())
.unwrap_or_else(|| "/".to_string());
let mut http_req = HttpRequest::new(method, path);
for (name, value) in req.headers() {
if let Ok(v) = value.to_str() {
http_req.headers.insert(name.to_string(), v.to_string());
}
}
use http_body_util::BodyExt;
let body_bytes = req
.collect()
.await
.map(|b| b.to_bytes().to_vec())
.unwrap_or_default();
http_req.body = body_bytes;
let response = app.handle(http_req).await;
match response {
Ok(resp) => {
let mut builder = hyper::Response::builder().status(resp.status);
for (name, value) in &resp.headers {
builder = builder.header(name.as_str(), value.as_str());
}
Ok::<_, std::convert::Infallible>(
builder
.body(http_body_util::Full::new(bytes::Bytes::from(resp.body)))
.unwrap(),
)
}
Err(e) => {
let status = match &e {
Error::RouteNotFound(_) => 404,
Error::Validation(_) => 400,
Error::Unauthorized(_) => 401,
Error::Forbidden(_) => 403,
_ => 500,
};
Ok(hyper::Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(http_body_util::Full::new(bytes::Bytes::from(format!(
r#"{{"error":"{}"}}"#,
e
))))
.unwrap())
}
}
}
});
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
tracing::error!("Connection error: {}", err);
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
async fn test_handler(_req: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::ok())
}
#[test]
fn test_app_builder() {
let app = App::new()
.route("/", get(test_handler))
.route("/users", get(test_handler).post(test_handler))
.build();
assert_eq!(app.router.routes.len(), 3);
}
#[test]
fn test_scope() {
let scope = scope("/api")
.route("/users", get(test_handler))
.route("/posts", get(test_handler).post(test_handler));
assert_eq!(scope.routes.len(), 3);
}
#[test]
fn test_data() {
let data = Data::new(42i32);
assert_eq!(*data, 42);
}
#[tokio::test]
async fn test_built_app_handle() {
let app = App::new().route("/test", get(test_handler)).build();
let req = HttpRequest::new("GET".to_string(), "/test".to_string());
let response = app.handle(req).await.unwrap();
assert_eq!(response.status, 200);
}
}