A lunatic web framework for the Rust language.
Submillisecond is a backend web framework around the Rust language, WebAssembly's security and the lunatic scheduler.
This is an early stage project, probably has bugs and the API is still changing. It's also important to point out that many Rust crates don't compile to WebAssembly yet and can't be used with submillisecond.
If you would like to ask for help or just follow the discussions around Lunatic & submillisecond, join our discord server.
- Fast compilation times
- async-free - All preemption and scheduling is done by lunatic
- strong security - Each request is handled in a separate lunatic process
- Batteries included
- Cookies
- Json
- Logging
- Websockets
- Submillisecond LiveView - Frontend web framework
use submillisecond::{router, Application};
fn index() -> &'static str {
"Hello :)"
}
fn main() -> std::io::Result<()> {
Application::new(router! {
GET "/" => index
})
.serve("0.0.0.0:3000")
}
To run the example you will first need to download the lunatic runtime by following the installation steps in this repository. The runtime is just a single executable and runs on Windows, macOS and Linux. If you have already Rust installed, you can get it with:
cargo install lunatic-runtime
Lunatic applications need to be compiled to WebAssembly before they can be executed by
the runtime. Rust has great support for WebAssembly and you can build a lunatic compatible
application just by passing the --target=wasm32-wasi
flag to cargo, e.g:
# Add the WebAssembly target
rustup target add wasm32-wasi
# Build the app
cargo build --release --target=wasm32-wasi
This will generate a .wasm file in the target/wasm32-wasi/release/
folder inside your project.
You can now run your application by passing the generated .wasm file to Lunatic, e.g:
lunatic target/wasm32-wasi/release/<name>.wasm
To simplify developing, testing and running lunatic applications with cargo, you can add a
.cargo/config.toml
file to your project with the following content:
[build]
target = "wasm32-wasi"
[target.wasm32-wasi]
runner = "lunatic"
Now you can just use the commands you are already familiar with, such as cargo run
, cargo test
and cargo is going to automatically build your project as a WebAssembly module and run it inside
lunatic
.
Add it as a dependency
submillisecond = "0.3.0"
Handlers are functions which return a response which implements IntoResponse
.
They can have any number of arguments, where each argument is an extractor.
fn index(body: Vec<u8>, cookies: Cookies) -> String {
// ...
}
Submillisecond provides a router!
macro for defining routes in your app.
#[derive(NamedParam)]
struct User {
first_name: String,
last_name: String,
}
fn hi(user: User) -> String {
format!("Hi {} {}!", user.first_name, user.last_name)
}
fn main() -> std::io::Result<()> {
Application::new(router! {
GET "/hi/:first_name/:last_name" => hi
POST "/update_data" => update_age
})
.serve("0.0.0.0:3000")
}
The router macro supports:
Routes can be nested.
router! {
"/foo" => {
GET "/bar" => bar
}
}
Uri parameters can be captured with the Path extractor.
router! {
GET "/users/:first/:last/:age" => greet
}
fn greet(Path((first, last, age)): Path<(String, String, u32)>) -> String {
format!("Welcome {first} {last}. You are {age} years old.")
}
You can use the NamedParam derive macro to define named parameters.
router! {
GET "/users/:first/:last/:age" => greet
}
#[derive(NamedParam)]
struct GreetInfo {
first: String,
last: String,
age: u32,
}
fn greet(GreetInfo { first, last, age }: GreetInfo) -> String {
format!("Welcome {first} {last}. You are {age} years old.")
}
Alternatively, you can access the params directly with the Params extractor.
The _
syntax can be used to catch-all routes.
router! {
"/foo" => {
GET "/bar" => bar
_ => matches_foo_but_not_bar
}
_ => not_found
}
Routes can be protected by guards.
struct ContentLengthLimit(u64);
impl Guard for ContentLengthLimit {
fn check(&self, req: &RequestContext) -> bool {
// ...
}
}
router! {
"/short_requests" if ContentLengthGuard(128) => {
POST "/super" if ContentLengthGuard(64) => super_short
POST "/" => short
}
}
Guards can be chained with the &&
and ||
syntax.
Middleware is any handler which calls next_handler
on the request context.
Like handlers, it can use extractors.
fn logger(req: RequestContext) -> Response {
println!("Before");
let result = req.next_handler();
println!("After");
result
}
fn main() -> std::io::Result<()> {
Application::new(router! {
with logger;
GET "/" => hi
})
.serve("0.0.0.0:3000")
}
Middleware can be chained together, and placed within sub-routes.
router! {
with [mid1, mid2];
"/foo" => {
with [foo_mid1, foo_mid2];
}
}
They can also be specific to a single route.
router! {
GET "/" with mid1 => home
}
Lunatic provides a macro #[lunatic::test]
to turn your tests into processes. Check out the
tests
folder for examples.
Licensed under either of
- Apache License, Version 2.0, (http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (http://opensource.org/licenses/MIT)
at your option.