2 releases

0.0.2 Oct 19, 2023
0.0.1 Aug 16, 2023

#339 in HTTP server

31 downloads per month

MIT license

120KB
2.5K SLoC

YAHF

Yet Another HTTP Framework

Warning: YAHF works only on nightly until RPITIT is stable

The goal of YAHF is both to provide a good developer experience and to be easy to extend.

Table of Contents

Features

  • Macro free Routing API
  • Predictable error handling
  • Native serialization and deserialization built into the handler
  • Friendly syntax

Example

The Hello world of YAHF is:

use yahf::server::Server;

#[tokio::main]
async fn main() {
 let server = Server::new().get(
     "/",
     || async { "Hello world".to_string() },
     &(),
     &String::with_capacity(0),
 );

 server
     .listen(([127, 0, 0, 1], 8000).into())
     .await
     .unwrap();
}

Routing

Router is used to bind handlers to paths.\

use yahf::router::Router;

// Router
let router = Router::new()
 .get("/", root_get, &(), &())
 .get("/foo", foo_get, &(), &())
 .post("/foo", foo_post, &(), &())
 .delete("/foo/bar", bar_delete, &(), &());

// calls respectively each of these handlers

async fn root_get() {}
async fn foo_get() {}
async fn foo_post() {}
async fn bar_delete() {}

Server shares these features from Router

Handlers

On YAHF, a handler is a async function that is used to handle a Route. An acceptable handler implements the trait Runner. By default, these signatures are supported:

async fn handler1() -> ResponseBody  {todo!()}
async fn handler2() -> Response<ResponseBody>  {todo!()}
async fn handler3(req: RequestBody) -> ResponseBody  {todo!()}
async fn handler4(req: Request<RequestBody>) -> ResponseBody {todo!()}
async fn handler5(req: RequestBody) -> Response<ResponseBody> {todo!()}
async fn handler6(req: Request<RequestBody>) -> Response<ResponseBody> {todo!()}
async fn handler7() -> Result<ResponseBody> {todo!()}
async fn handler8() -> Result<Response<ResponseBody>> {todo!()}
async fn handler9(req: Result<RequestBody>) -> Result<ResponseBody> {todo!()}
async fn handler10(req: Result<Request<RequestBody>>) -> Result<ResponseBody> {todo!()}
async fn handler11(req: Result<RequestBody>) -> Result<Response<ResponseBody>> {todo!()}
async fn handler12(req: Result<Request<RequestBody>>) -> Result<Response<ResponseBody>> {todo!()}

All these signatures comes from the implementations of RunnerInput and RunnerOutput.

Extensability

YAHF handlers are modular by design. A handler is decomposed into four modules: a body deserializer, a body serializer, arguments, and a response. These modules are glued together using the Runner trait. Adding new functionality to the handlers is just a matter of implementing one of these traits. For more details, check the trait docs

Middleware

Middleware are async functions that will run previously or after a handler. These can really useful when combined with a Router or a Server to reuse logic and create "pipelines".

Example ```rust use serde::Deserialize; use serde::Serialize; use yahf::handler::Json; use yahf::request::Request; use yahf::result::Result; use yahf::response::Response; use yahf::router::Router; use yahf::server::Server;

use std::time; use std::time::UNIX_EPOCH; #[derive(Debug, Deserialize, Serialize)] struct ComputationBody { value: u32, }

// Print the time, the method, and the path from the Request async fn log_middleware(req: Result<Request>) -> Result<Request> { match req.into_inner() { Ok(req) => { println!( "{} - {} - {}", time::SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Negative time") .as_millis(), req.method().as_str(), req.uri().path() );

        Ok(req).into()
    }
    Err(err) => Err(err).into(),
}

}

// Handle any possible errors async fn log_error(res: Result<Response>) -> Result<Response> { match res.into_inner() { Err(err) => { println!( "{} - {}", time::SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Negative time") .as_millis(), err.code(), ); Err(err).into() } ok => ok.into(), } }

// Compute something using the ComputationBody async fn some_computation(req: ComputationBody) -> ComputationBody { ComputationBody { value: req.value + 1, } }

// Set a Router with both Middlewares. // The route / will become: log_middleware -> some_computation -> log_middleware let router = Router::new() .pre(log_middleware) .after(log_error) .get("/", some_computation, &Json::new(), &Json::new()); ``` More of this example here

Examples

The repo includes illustrative examples demonstrating the integration of all the components

Goals for v1.0.0

YAHF follows the SemVer.

The objective for v1.0.0 is to have a stable project that can deal with real-world problems with good developer experience and the possibility to extend the project to suit any need.

The goal features for this version are:

  • Composable routing system;
  • Middleware functions;
  • HTTP/1.1 with or without security.

Dependencies

~14–27MB
~468K SLoC