962103353062fd09f70c6f6fd230f4e749adb18d
Servme
Un framework web HTTP de alto rendimiento escrito en Rust, construido sobre Hyper.
Primeros pasos
Instalación
Añade servme a tu Cargo.toml:
[dependencies]
servme = "0.1"
tokio = { version = "1", features = ["full"] }
Tu primer servidor
use http_body_util::Full;
use hyper::{body::Bytes, Request, Response};
use servme::{Responder, Server, ServerError};
#[tokio::main]
async fn main() {
Server::builder()
.address("127.0.0.1", 8080)
.build()
.run(handler)
.await;
}
async fn handler(req: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, ServerError> {
Responder::ok(format!("Hola! Path: {}", req.uri()))
}
Para usuarios nuevos: Ejecuta
cargo runy visitahttp://127.0.0.1:8080en tu navegador.
Conceptos básicos
El Handler
El handler es una función asíncrona que recibe un Request y retorna una Response:
use http_body_util::Full;
use hyper::{body::Bytes, Request, Response};
async fn handler(req: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, ServerError> {
// Tu lógica aquí
Responder::ok("Respuesta")
}
Responder
Responder proporciona métodos auxiliares para crear respuestas HTTP comunes:
use servme::Responder;
// Respuestas de éxito
Responder::ok("Mensaje")?; // 200 OK
Responder::html("<h1>Título</h1>")?; // 200 con Content-Type: text/html
Responder::json(&datos)?; // 200 con Content-Type: application/json
Responder::redirect("/nueva-ruta")?; // 302 Redirect
Responder::no_content()?; // 204 No Content
// Respuestas de error
Responder::not_found()?; // 404
Responder::unauthorized()?; // 401
Responder::forbidden()?; // 403
Responder::bad_request("Datos inválidos")?; // 400
Responder::internal_error("Algo salió mal")?; // 500
Extraer datos del Request
use servme::Requester;
// Extraer body JSON
let data: MyStruct = Requester::extract_body(req).await?;
// Extraer como texto
let body_str: String = Requester::extract_body_str(req).await?;
// Extraer como bytes (más eficiente)
let body_bytes: Bytes = Requester::extract_body_bytes(req).await?;
// Extraer parámetros de URL
let url = UrlExtract::new(req.uri());
let name = url.param_str("nombre"); // ?nombre=Juan
let age: Option<i64> = url.param_i64("edad"); // ?edad=25
Middleware
Los middleware se ejecutan antes del handler y pueden authnticar, filtrar o modificar requests.
API Key
use servme::{Server, ServerBuilder};
Server::builder()
.address("127.0.0.1", 8080)
.add_api_key_middleware("mi-clave-secreta")
.build()
.run(handler)
.await;
Envía la clave en el header X-API-Key.
JWT Authentication
use servme::{Server, ServerBuilder};
Server::builder()
.address("0.0.0.0", 8080)
.add_jwt_middleware(
rsa_public_key_pem, // Clave pública RSA en formato PEM
vec!["/health".to_string()], // Rutas públicas (sin auth)
)
.build()
.run(handler)
.await;
El JWT se valida desde:
- Header
Authorization: Bearer <token> - Cookie
access_token
Filtrado por IP
use servme::ServerBuilder;
Server::builder()
.address("127.0.0.1", 8080)
.add_ip_filter_middleware(
vec!["192.168.1.100".to_string(), "10.0.0.1".to_string()],
true, // allow_private: también permitir IPs privadas (192.168.x.x, 10.x.x.x)
)
.build()
.run(handler)
.await;
Múltiples middlewares
Server::builder()
.address("0.0.0.0", 8080)
.add_ip_filter_middleware(vec![], true) // Permitir todas las IPs privadas
.add_jwt_middleware(pub_key, vec!["/static".to_string(), "/health".to_string()])
.build()
.run(handler)
.await;
Estado compartido
Puedes compartir datos entre requests usando .data():
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: Database,
}
Server::builder()
.address("127.0.0.1", 8080)
.data(AppState { db: Database::new() })
.build()
.run(|req| async move {
// Accede al estado desde las extensions del request
let state = req.extensions().get::<Arc<AppState>>().unwrap();
// Usa state.db...
Responder::ok("ok")
})
.await;
Estructura del proyecto
src/
├── lib.rs # Exports públicos
├── main.rs # Binario de ejemplo
├── builder.rs # ServerBuilder - configuración del servidor
├── config.rs # ServerConfig - estructura de configuración
├── server.rs # Servidor HTTP con graceful shutdown
├── error.rs # ServerError - tipos de errores
├── constants.rs # Constantes del framework
├── responder.rs # Helpers para construir respuestas
├── requester.rs # Helpers para extraer datos del request
├── url_extract.rs # Parsing de URLs y query params
└── middleware/
├── mod.rs # Traits y tipos comunes
├── api_key.rs # Autenticación por API Key
├── jwt.rs # Autenticación JWT (RS256)
├── ip_filter.rs # Filtrado por dirección IP
└── auth_types.rs # Tipos para autenticación (Claims)
Construcción y testing
# Compilar
cargo build
# Ejecutar tests
cargo test
# Ejecutar con logs de debug
RUST_LOG=debug cargo run
# Ver documentation
cargo doc --open
Errores comunes
"Failed to bind to address"
El puerto está en uso. Prueba con otro puerto:
Server::builder()
.address("127.0.0.1", 8081) // Cambia el puerto
.build()
.run(handler)
.await;
"JWT validation failed"
- Verifica que la clave pública RSA sea válida
- Asegúrate de que el token no esté expirado (
expclaim) - El token debe contener el claim
sub(subject)
Constantes útiles
use servme::{
DEFAULT_HOST, // "127.0.0.1"
DEFAULT_PORT, // 8080
JWT_COOKIE_NAME, // "access_token"
BEARER_PREFIX, // "Bearer "
MAX_ALLOWED_IPS, // 1000
};
use servme::constants::FILE_EXTENSIONS; // Extensiones de archivos estáticos
License
MIT
Description
Languages
Rust
100%