Savlo

[![build status](https://github.com/salvo-rs/salvo/workflows/CI%20(Linux)/badge.svg?branch=master&event=push)](https://github.com/salvo-rs/salvo/actions) [![build status](https://github.com/salvo-rs/salvo//workflows/CI%20(macOS)/badge.svg?branch=master&event=push)](https://github.com/salvo-rs/salvo/actions) [![build status](https://github.com/salvo-rs/salvo/workflows/CI%20(Windows)/badge.svg?branch=master&event=push)](https://github.com/salvo-rs/salvo/actions)
[![codecov](https://codecov.io/gh/salvo-rs/salvo/branch/master/graph/badge.svg)](https://codecov.io/gh/salvo-rs/salvo) [![crates.io](https://img.shields.io/crates/v/salvo)](https://crates.io/crates/salvo) [![Download](https://img.shields.io/crates/d/salvo.svg)](https://crates.io/crates/salvo) ![License](https://img.shields.io/crates/l/salvo.svg)

Salvo is a web server framework written in Rust.

🎯 Features

⚡️ Quick start

You can view samples here or read docs here.

Create a new rust project:

bash cargo new hello_salvo --bin

Add this to Cargo.toml

toml [dependencies] salvo = "0.9" tokio = { version = "1", features = ["full"] }

Create a simple function handler in the main.rs file, we call it hello_world, this function just render plain text "Hello World".

```rust use salvo::prelude::*;

[fn_handler]

async fn helloworld(req: &mut Request, depot: &mut Depot, res: &mut Response) { res.renderplain_text("Hello World"); } ```

There are many ways to write function handler. - You can omit function arguments if they do not used, like _req, _depot in this example:

``` rust
#[fn_handler]
async fn hello_world(res: &mut Response) {
    res.render_plain_text("Hello World");
}
```

In the main function, we need to create a root Router first, and then create a server and call it's bind function:

```rust use salvo::prelude::*;

[fn_handler]

async fn hello_world() -> &'static str { "Hello World" }

[tokio::main]

async fn main() { let router = Router::new().get(hello_world); let server = Server::new(router); server.bind(([0, 0, 0, 0], 7878)).await; } ```

Middleware

There is no difference between Handler and Middleware, Middleware is just Handler.

Tree-like routing system

Router supports nested, and you can add middleware in router. In this example, there are two routers, both of them has same path router "user", and both of them added to the same parent router, to do that because we want to add middleware to them and let them has different access control:

```rust use salvo::prelude::*;

[tokio::main]

async fn main() { let router = Router::new() .get(index) .push( Router::new() .path("users") .before(auth) .post(createuser) .push(Router::new().path(r"").post(updateuser).delete(deleteuser)), ) .push( Router::new() .path("users") .get(listusers) .push(Router::new().path(r"").get(show_user)), );

Server::new(router).bind(([0, 0, 0, 0], 7878)).await;

}

[fn_handler]

async fn index() -> &'static str { "Hello world!" }

[fn_handler]

async fn auth() -> &'static str { "user has authed\n\n" }

[fn_handler]

async fn list_users() -> &'static str { "list users" }

[fn_handler]

async fn show_user() -> &'static str { "show user" }

[fn_handler]

async fn create_user() -> &'static str { "user created" }

[fn_handler]

async fn update_user() -> &'static str { "user updated" }

[fn_handler]

async fn delete_user() -> &'static str { "user deleted" } ```

File upload

We can get file async by the function get_file in Request:

```rust

[fn_handler]

async fn upload(req: &mut Request, res: &mut Response) { let file = req.getfile("file").await; if let Some(file) = file { let dest = format!("temp/{}", file.filename().unwraporelse(|| "file".into())); if let Err(e) = std::fs::copy(&file.path, Path::new(&dest)) { res.setstatuscode(StatusCode::INTERNALSERVERERROR); } else { res.renderplaintext("Ok"); } } else { res.setstatuscode(StatusCode::BADREQUEST); } } ```

Multiple files also very simple:

```rust

[fn_handler]

async fn upload(req: &mut Request, res: &mut Response) { let files = req.getfiles("files").await; if let Some(files) = files { let mut msgs = Vec::withcapacity(files.len()); for file in files { let dest = format!("temp/{}", file.filename().unwraporelse(|| "file".into())); if let Err(e) = std::fs::copy(&file.path, Path::new(&dest)) { res.setstatuscode(StatusCode::INTERNALSERVERERROR); res.renderplaintext(&format!("file not found in request: {}", e.tostring())); } else { msgs.push(dest); } } res.renderplaintext(&format!("Files uploaded:\n\n{}", msgs.join("\n"))); } else { res.setstatuscode(StatusCode::BADREQUEST); res.renderplaintext("file not found in request"); } } ```

More Examples

Your can find more examples in examples folder: - basicauth.rs - compression.rs - filelist.rs - proxy.rs - remoteaddr.rs - routing.rs - ssechat.rs - sse.rs - tls.rs - todos.rs - unixsocket.rs - wschat.rs - ws.rs

Some code and examples port from from warp and multipart-async.

☕ Supporters

Salvo is an open source project. If you want to support Salvo, you can ☕ buy a coffee here.

⚠️ License

Salvo is licensed under either of * Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) * MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)