diff --git a/Cargo.toml b/Cargo.toml index 22d7e57..50bce8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.1" edition = "2024" [dependencies] -tokio = { version = "1.49.0", features = ["macros", "rt", "rt-multi-thread", "signal", "net"] } +tokio = { version = "1.50.0", features = ["macros", "rt", "rt-multi-thread", "signal", "net"] } tokio-util = "0.7.18" http = "1.4.0" diff --git a/src/builder.rs b/src/builder.rs index 2400f04..4bebb08 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,12 +5,34 @@ use crate::{ }; use std::sync::Arc; -pub struct ServerBuilder { +pub struct ServerBuilder { pub config: ServerConfig, pub middlewares: Vec>, + pub data: Option, } -impl ServerBuilder { +impl ServerBuilder<()> { + pub fn new() -> Self { + Self { + config: ServerConfig::default(), + middlewares: vec![], + data: None, + } + } + + pub fn data(self, data: NewD) -> ServerBuilder + where + NewD: Clone + Send + Sync + 'static, + { + ServerBuilder { + config: self.config, + middlewares: self.middlewares, + data: Some(data), + } + } +} + +impl ServerBuilder { pub fn address(mut self, ip: &str, port: u16) -> Self { self.config.ip = ip.to_string(); self.config.port = port; @@ -45,11 +67,6 @@ impl ServerBuilder { self } - pub fn api_key(mut self, api_key: &str) -> Self { - self.config.api_key = Some(api_key.to_string()); - self - } - pub fn middleware(mut self, middleware: M) -> Self where M: Middleware + 'static, @@ -58,10 +75,11 @@ impl ServerBuilder { self } - pub fn build(self) -> Server { + pub fn build(self) -> Server { Server { config: Arc::new(self.config), middlewares: Arc::new(self.middlewares), + data: self.data.map(Arc::new), } } } diff --git a/src/config.rs b/src/config.rs index 86ce88a..042ef2d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,6 @@ pub struct ServerConfig { pub ip: String, pub port: u16, - - // Request filtering - pub api_key: Option, } impl Default for ServerConfig { @@ -11,8 +8,6 @@ impl Default for ServerConfig { ServerConfig { ip: "127.0.0.1".to_string(), port: 8080, - - api_key: None, } } } diff --git a/src/main.rs b/src/main.rs index 17bb486..d0f8144 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use http_body_util::Full; use hyper::{ - body::{Bytes, Incoming}, Request, Response, + body::{Bytes, Incoming}, }; use servme::{Responder, Server}; use std::convert::Infallible; @@ -16,5 +16,5 @@ async fn main() { } async fn handler(req: Request) -> Result>, Infallible> { - Responder::text(&format!("Hello World! {}", req.uri())) + Responder::ok(format!("Hello World! {}", req.uri())) } diff --git a/src/middleware/api_key.rs b/src/middleware/api_key.rs index 751686c..83631f6 100644 --- a/src/middleware/api_key.rs +++ b/src/middleware/api_key.rs @@ -1,6 +1,6 @@ use crate::{ - middleware::{Middleware, MiddlewareFuture, MiddlewareResult}, Responder, + middleware::{Middleware, MiddlewareFuture, MiddlewareResult}, }; use http::Request; use hyper::body::Incoming; diff --git a/src/middleware/jwt.rs b/src/middleware/jwt.rs index 7755381..8fd1939 100644 --- a/src/middleware/jwt.rs +++ b/src/middleware/jwt.rs @@ -1,10 +1,10 @@ use crate::{ - middleware::{auth_types::Claims, Middleware, MiddlewareFuture, MiddlewareResult}, Responder, + middleware::{Middleware, MiddlewareFuture, MiddlewareResult, auth_types::Claims}, }; use http::Request; use hyper::body::Incoming; -use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode}; use log::error; pub struct JwtMiddleware { diff --git a/src/responder.rs b/src/responder.rs index 035f078..953e2db 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,4 +1,7 @@ -use http::{header::CONTENT_TYPE, Response, StatusCode}; +use http::{ + Response, StatusCode, + header::{CONTENT_TYPE, LOCATION}, +}; use http_body_util::Full; use hyper::body::Bytes; use serde::Serialize; @@ -7,53 +10,60 @@ use std::convert::Infallible; pub struct Responder; impl Responder { - pub fn not_found() -> Result>, Infallible> { - Self::text_using_status(StatusCode::NOT_FOUND.as_u16(), "Not Found") + pub fn ok>(body: B) -> Result>, Infallible> { + Self::with_status(StatusCode::OK, body) } - pub fn unauthorized() -> Result>, Infallible> { - Self::text_using_status(StatusCode::UNAUTHORIZED.as_u16(), "Unauthorized") + pub fn html>(body: B) -> Result>, Infallible> { + Ok(Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "text/html; charset=utf-8") + .body(Full::new(body.into())) + .unwrap()) } - pub fn text(response: &str) -> Result>, Infallible> { - Self::text_using_status(StatusCode::OK.as_u16(), response) - } - - pub fn json(json: &T) -> Result>, Infallible> { - Self::json_using_status(StatusCode::OK.as_u16(), json) - } - - pub fn text_using_status( - status: u16, - response: &str, - ) -> Result>, Infallible> { - let builder = Response::builder().status(status); - Self::build_response(builder, response.to_string().into()) - } - - pub fn json_using_status( - status: u16, - json: &T, - ) -> Result>, Infallible> { - let builder = Response::builder() - .status(status) - .header(CONTENT_TYPE, "application/json"); - - match serde_json::to_string(json) { - Ok(body) => Self::build_response(builder, body.into()), - Err(e) => Self::text_using_status(500, &format!("JSON Error: {}", e)), + pub fn json(value: &T) -> Result>, Infallible> { + match serde_json::to_vec(value) { + Ok(bytes) => Ok(Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "application/json") + .body(Full::new(Bytes::from(bytes))) + .unwrap()), + Err(e) => Self::internal_error(format!("JSON Serialization Error: {}", e)), } } - // Método privado interno para centralizar la construcción - fn build_response( - builder: http::response::Builder, - body: Bytes, + pub fn redirect(url: &str) -> Result>, Infallible> { + Ok(Response::builder() + .status(StatusCode::SEE_OTHER) + .header(LOCATION, url) + .body(Full::new(Bytes::new())) + .unwrap()) + } + + pub fn not_found() -> Result>, Infallible> { + Self::with_status(StatusCode::NOT_FOUND, "Not Found") + } + + pub fn unauthorized() -> Result>, Infallible> { + Self::with_status(StatusCode::UNAUTHORIZED, "Unauthorized") + } + + pub fn forbidden() -> Result>, Infallible> { + Self::with_status(StatusCode::FORBIDDEN, "Forbidden") + } + + pub fn internal_error>(body: B) -> Result>, Infallible> { + Self::with_status(StatusCode::INTERNAL_SERVER_ERROR, body) + } + + pub fn with_status>( + status: StatusCode, + body: B, ) -> Result>, Infallible> { - // En un servidor web real, un error de construcción aquí es casi imposible, - // pero manejarlo formalmente es mejor que hacer unwrap() - Ok(builder - .body(Full::new(body)) - .unwrap_or_else(|_| Response::new(Full::new(Bytes::from("Internal Server Error"))))) + Ok(Response::builder() + .status(status) + .body(Full::new(body.into())) + .unwrap()) } } diff --git a/src/server.rs b/src/server.rs index bb33a59..c84a346 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,28 +3,32 @@ use crate::{ config::ServerConfig, middleware::{Middleware, MiddlewareResult}, }; -use http1::Builder; use http_body_util::Full; -use hyper::{body::Incoming, server::conn::http1, service::service_fn, Request, Response}; +use http1::Builder; +use hyper::{Request, Response, body::Incoming, server::conn::http1, service::service_fn}; use hyper_util::rt::TokioIo; use log::error; use std::{convert::Infallible, future::Future, net::SocketAddr, sync::Arc}; use tokio::{net::TcpListener, spawn}; use tokio_util::bytes::Bytes; -pub struct Server { +pub struct Server { pub config: Arc, pub middlewares: Arc>>, + pub data: Option>, } impl Server { - pub fn builder() -> ServerBuilder { + pub fn builder() -> ServerBuilder<()> { ServerBuilder { config: ServerConfig::default(), middlewares: vec![], + data: None, } } +} +impl Server { pub async fn run(self, handler: F) where F: Fn(Request) -> Fut + Send + Sync + 'static, @@ -51,6 +55,7 @@ impl Server { let io = TokioIo::new(tcp); + let data_to_inject = self.data.clone(); let mws = Arc::clone(&shared_middlewares); let h = Arc::clone(&handler); let client_ip = client_addr.ip(); @@ -62,6 +67,10 @@ impl Server { let mws = Arc::clone(&mws); let h = Arc::clone(&h); + if let Some(ref d) = data_to_inject { + req.extensions_mut().insert(Arc::clone(d)); + } + async move { req.extensions_mut().insert(client_ip);