50 releases
new 0.4.0-beta.1 | Dec 3, 2024 |
---|---|
0.3.0-beta.3 | Oct 4, 2024 |
0.3.0-beta.2 | Feb 14, 2024 |
0.2.0-beta.18 | Oct 6, 2023 |
0.2.0-alpha.0 | Mar 29, 2021 |
#90 in HTTP server
32 downloads per month
Used in 2 crates
175KB
3K
SLoC
Cataclysm ⛈. A simple rust http server
Work in progress: The work is now partially usable in its current state, but updates will keep comming until a full HTTP/1.1 server is functional. This legend will be removed when such a state is reached.
Cataclysm is an http framework influenced by actix-web, and built over tokio. A minimal working example is the following
use cataclysm::{Server, Branch, http::{Response, Method}};
async fn hello() -> Response {
Response::ok().body("hello")
}
#[tokio::main]
async fn main() {
let server = Server::builder(
Branch::<()>::new("/hello").with(Method::Get.to(hello))
).build().unwrap();
server.run("localhost:8000").await.unwrap();
}
Closures as callbacks
Until async closures
become stable, the option to pass closures as a path handler is with a closure that returns an async block
use cataclysm::{Server, Branch, http::{Response, Method}};
#[tokio::main]
async fn main() {
let server = Server::builder(
Branch::<()>::new("/data").with(Method::Post.to(|| async {
Response::ok().body("worked!")
}))
).build().unwrap();
server.run("localhost:8000").await.unwrap();
}
Extractors
Some data can be retrieved from an http request by just adding arguments to the callback, with types that implement the Extractor
trait. The default implementation list is the following
String
: Tries to extract the body as a valid utf-8 string. Returns Bad-Request if the operation failsVec<u8>
: Returns the content of thehttp
call as a stream of bytesRequest
: Returns the request for a bit more control within the callbackPath<T>
: Returns the parameters from the path. T must be a tuple.Shared<T>
: Returns the shared data provided to the server (if any).
Sharing data to the functions from the server
Data can be shared accross the server calls through the share
method from the ServerBuilder
structure, and with the help of the Shared
structure.
use cataclysm::{Server, Branch, Shared, http::{Response, Method, Path}};
// Receives a string, and concatenates the shared suffix
async fn index(path: Path<(String,)>, shared: Shared<String>) -> Response {
let (prefix,) = path.into_inner();
let suffix = shared.into_inner();
Response::ok().body(format!("{}{}", prefix, suffix))
}
#[tokio::main]
async fn main() {
// We create our tree structure
let branch = Branch::new("/{:value}").with(Method::Get.to(index));
// We create a server with the given tree structure
let server = Server::builder(branch).share("!!!".into()).build().unwrap();
// And we launch it on the following address
server.run("127.0.0.1:8000").await.unwrap();
}
If you want to share mutable data, then use rust's Mutex
structure (as the Shared
structure already provides an Arc
wrapper).
SPA, and static file serving
The branch structure has both the file
method and the defaults_to_file
to create a simple SPA server, one allows all paths that contain an extention to be looked for in the provided folder path, and the other to serve a file as specified by the provided path in any other match that has no extension, respectively.
use cataclysm::{Server, Branch, http::{Response, Method}};
async fn salute() -> Response {
Response::ok().body("api salute endpoint?")
}
#[tokio::main]
async fn main() {
let branch: Branch<()> = Branch::new("/").files("./static").defaults_to_file("./static/index.html")
.nest(Branch::new("/api/v1/salute").with(Method::Get.to(salute)));
let server = Server::builder(branch).build().unwrap();
server.run("127.0.0.1:8000").await.unwrap();
}
Layers
Cataclysm allows for layer handling, a.k.a. middleware.
use cataclysm::{Server, Branch, Additional, Pipeline, http::{Response, Request, Method}};
use std::sync::Arc;
use futures::future::FutureExt;
#[tokio::main]
async fn main() {
let branch = Branch::new("/").with(Method::Get.to(|| async {Response::ok()}))
.layer(|req: Request, pipeline: Box<Pipeline<()>>, ad: Arc<Additional<()>>| async {
// Example of timing layer
println!("Time measuring begins");
let now = std::time::Instant::now();
let request = pipeline.execute(req, ad).await;
let elapsed = now.elapsed().as_nanos();
println!("Process time: {} ns", elapsed);
request
}.boxed());
let server = Server::builder(branch).build().unwrap();
server.run("localhost:8000").await.unwrap();
}
As seen in the example, layer functions receive a Request
and a boxed Pipeline
enum. The Pipeline
enum contains a nested structure of futures (the layers + the core handler), and has the execute
to simplify things a bit. This function must return a Pin<Box<_>>
future, so either use the boxed
method from the FutureExt
trait from the futures
crate, or wrap it manually.
Full log feature
If the full_log
feature is activated, log function through the levels Debug
and Trace
will be provided. This might come in handy for debugging, but it is left optional as it might have a strong toll in performance.
TODO
- Regex with / cause problems in branch creation (fix with queue implementation for "{", "}" detection)
lib.rs
:
Cataclysm: A simple http framework
Cataclysm is a small personal project, an http framework influenced by actix-web
, and built over tokio
. A minimal working example is the following
extern crate cataclysm;
use cataclysm::{Server, Branch, http::{Response, Method}};
async fn index() -> Response {
Response::ok().body("Hello, World!")
}
#[tokio::main]
async fn main() {
let server = Server::builder(
Branch::<()>::new("/").with(Method::Get.to(index))
).build().unwrap();
server.run("localhost:8000").await.unwrap();
}
Dependencies
~16–28MB
~504K SLoC