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.10" 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

Normally we write routing like this:

rust Router::new().path("articles").get(list_articles).post(create_article); Router::new() .path("articles/<id>") .get(show_article) .patch(edit_article) .delete(delete_article);

Often viewing articles and article lists does not require user login, but creating, editing, deleting articles, etc. require user login authentication permissions. The tree-like routing system in Salvo can meet this demand. We can write routers without user login together:

rust Router::new() .path("articles") .get(list_articles) .push(Router::new().path("<id>").get(show_article));

Then write the routers that require the user to login together, and use the corresponding middleware to verify whether the user is logged in: rust Router::new() .path("articles") .before(auth_check) .post(list_articles) .push(Router::new().path("<id>").patch(edit_article).delete(delete_article));

Although these two routes have the same path("articles"), they can still be added to the same parent route at the same time, so the final route looks like this:

rust Router::new() .push( Router::new() .path("articles") .get(list_articles) .push(Router::new().path("<id>").get(show_article)), ) .push( Router::new() .path("articles") .before(auth_check) .post(list_articles) .push(Router::new().path("<id>").patch(edit_article).delete(delete_article)), );

<id> matches a fragment in the path, under normal circumstances, the article id is just a number, which we can use regular expressions to restrict id matching rules, r"<id:/\d+/>".

For numeric characters there is an easier way to use <id:num>, the specific writing is: - <id:num>, matches any number of numeric characters; - <id:num[10]>, only matches a certain number of numeric characters, where 10 means that the match only matches 10 numeric characters; -<id:num(..10)> means matching 1 to 9 numeric characters; - <id:num(3..10)> means matching 3 to 9 numeric characters; - <id:num(..=10)> means matching 1 to 10 numeric characters; - <id:num(3..=10)> means match 3 to 10 numeric characters; - <id:num(10..)> means to match at least 10 numeric characters.

You can also use <*> or <**> to match all remaining path fragments. In order to make the code more readable, you can also add appropriate name to make the path semantics more clear, for example: <**file_path>.

It is allowed to combine multiple expressions to match the same path segment, such as /articles/article_<id:num>/.

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 - sizelimiter.rs - ssechat.rs - sse.rs - tls.rs - todos.rs - unixsocket.rs - ws_chat.rs - ws.rs

Some code and examples port 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)