Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/salvo-rs/salvo
A powerful web framework built with a simplified design.
https://github.com/salvo-rs/salvo
async framework http-server rust salvo web
Last synced: 4 days ago
JSON representation
A powerful web framework built with a simplified design.
- Host: GitHub
- URL: https://github.com/salvo-rs/salvo
- Owner: salvo-rs
- License: apache-2.0
- Created: 2019-11-07T08:18:29.000Z (about 5 years ago)
- Default Branch: main
- Last Pushed: 2024-10-28T22:58:44.000Z (about 2 months ago)
- Last Synced: 2024-10-29T15:04:44.518Z (about 1 month ago)
- Topics: async, framework, http-server, rust, salvo, web
- Language: Rust
- Homepage: https://salvo.rs
- Size: 15.2 MB
- Stars: 3,380
- Watchers: 46
- Forks: 205
- Open Issues: 19
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE-APACHE
- Security: SECURITY.md
Awesome Lists containing this project
- awesome-rust - Salvo - rs/salvo/workflows/CI%20(Linux)/badge.svg?branch=master&event=push)](https://github.com/salvo-rs/salvo/actions) (Libraries / Web programming)
- awesome-rust-cn - Salvo
- StarryDivineSky - salvo-rs/salvo
- awesome-rust-list - Salvo - rs/salvo?style=social"/> : [Salvo](https://salvo.rs/) is a powerful and simplest web server framework in Rust world. [salvo.rs](https://salvo.rs/) (Web Framework)
- awesome-rust-list - Salvo - rs/salvo?style=social"/> : [Salvo](https://salvo.rs/) is a powerful and simplest web server framework in Rust world. [salvo.rs](https://salvo.rs/) (Web Framework)
README
Salvo is an extremely simple and powerful Rust web backend framework. Only basic Rust knowledge is required to develop backend services.
## 🎯 Features
- Built with [Hyper 1](https://crates.io/crates/hyper) and [Tokio](https://crates.io/crates/tokio);
- HTTP1, HTTP2 and **HTTP3**;
- Unified middleware and handle interface;
- Router can be nested infinitely, and multiple middlewares can be attached to any router;
- Integrated Multipart form processing;
- Support WebSocket, WebTransport;
- Support OpenAPI, generate OpenAPI data automatic;
- Support Acme, automatically get TLS certificate from [let's encrypt](https://letsencrypt.org/);
- Support Tower Service and Layer;## ⚡️ Quick Start
You can view samples [here](https://github.com/salvo-rs/salvo/tree/main/examples), or view [official website](https://salvo.rs).
### Hello World with ACME and HTTP3
**It only takes a few lines of code to implement a server that supports ACME to automatically obtain certificates and
supports HTTP1, HTTP2, and HTTP3 protocols.**```rust
use salvo::prelude::*;#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello World"));
}#[tokio::main]
async fn main() {
let mut router = Router::new().get(hello);
let listener = TcpListener::new("0.0.0.0:443")
.acme()
.add_domain("test.salvo.rs") // Replace this domain name with your own.
.http01_challenge(&mut router).quinn("0.0.0.0:443");
let acceptor = listener.join(TcpListener::new("0.0.0.0:80")).bind().await;
Server::new(acceptor).serve(router).await;
}
```### Middleware
There is no difference between Handler and Middleware, Middleware is just Handler. **So you can write middlewares
without to know concepts like associated type, generic type. You can write middleware if you can write function!!!**```rust
use salvo::http::header::{self, HeaderValue};
use salvo::prelude::*;#[handler]
async fn add_header(res: &mut Response) {
res.headers_mut()
.insert(header::SERVER, HeaderValue::from_static("Salvo"));
}
```Then add it to router:
```rust
Router::new().hoop(add_header).get(hello)
```This is a very simple middleware, it adds `Header` to `Response`, view [full source code](https://github.com/salvo-rs/salvo/blob/main/examples/middleware-add-header/src/main.rs).
### Chainable tree routing system
Normally we write routing like this:
```rust
Router::with_path("articles").get(list_articles).post(create_article);
Router::with_path("articles/")
.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::with_path("articles")
.get(list_articles)
.push(Router::with_path("").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::with_path("articles")
.hoop(auth_check)
.push(Router::with_path("").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::with_path("articles")
.get(list_articles)
.push(Router::with_path("").get(show_article)),
)
.push(
Router::with_path("articles")
.hoop(auth_check)
.push(Router::with_path("").patch(edit_article).delete(delete_article)),
);
````` 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""`.
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>`.Some regular expressions for matching paths need to be used frequently, and it can be registered in advance, such as GUID:
```rust
PathFilter::register_wisp_regex(
"guid",
Regex::new("[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}").unwrap(),
);
```This makes it more concise when path matching is required:
```rust
Router::with_path("").get(index)
```View [full source code](https://github.com/salvo-rs/salvo/blob/main/examples/routing-guid/src/main.rs)
### File upload
We can get file async by the function `file` in `Request`:
```rust
#[handler]
async fn upload(req: &mut Request, res: &mut Response) {
let file = req.file("file").await;
if let Some(file) = file {
let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));
if let Err(e) = tokio::fs::copy(&file.path, Path::new(&dest)).await {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
} else {
res.render("Ok");
}
} else {
res.status_code(StatusCode::BAD_REQUEST);
}
}
```### Extract data from request
You can easily get data from multiple different data sources and assemble it into the type you want. You can define a custom type first, for example:
```rust
#[derive(Serialize, Deserialize, Extractible, Debug)]
/// Get the data field value from the body by default.
#[salvo(extract(default_source(from = "body")))]
struct GoodMan<'a> {
/// The id number is obtained from the request path parameter, and the data is automatically parsed as i64 type.
#[salvo(extract(source(from = "param")))]
id: i64,
/// Reference types can be used to avoid memory copying.
username: &'a str,
first_name: String,
last_name: String,
}
```Then in `Handler` you can get the data like this:
```rust
#[handler]
async fn edit(req: &mut Request) {
let good_man: GoodMan<'_> = req.extract().await.unwrap();
}
```You can even pass the type directly to the function as a parameter, like this:
```rust
#[handler]
async fn edit<'a>(good_man: GoodMan<'a>) {
res.render(Json(good_man));
}
```View [full source code](https://github.com/salvo-rs/salvo/blob/main/examples/extract-nested/src/main.rs)
### OpenAPI Supported
Perfect support for OpenAPI can be achieved without making significant changes to the project.
```rust
#[derive(Serialize, Deserialize, ToSchema, Debug)]
struct MyObject {
value: T,
}#[endpoint]
async fn use_string(body: JsonBody>) -> String {
format!("{:?}", body)
}
#[endpoint]
async fn use_i32(body: JsonBody>) -> String {
format!("{:?}", body)
}
#[endpoint]
async fn use_u64(body: JsonBody>) -> String {
format!("{:?}", body)
}#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();let router = Router::new()
.push(Router::with_path("i32").post(use_i32))
.push(Router::with_path("u64").post(use_u64))
.push(Router::with_path("string").post(use_string));let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);
let router = router
.push(doc.into_router("/api-doc/openapi.json"))
.push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
```### 🛠️ Salvo CLI
Salvo CLI is a command-line tool that simplifies the creation of new Salvo projects, supporting templates for web APIs, websites, databases (including SQLite, PostgreSQL, and MySQL via SQLx, SeaORM, Diesel, Rbatis), and basic middleware.
You can use [salvo-cli](https://github.com/salvo-rs/salvo-cli) to create a new Salvo project:#### install
```bash
cargo install salvo-cli
```#### create a new Salvo project
```bash
salvo new project_name
```___
### More Examples
Your can find more examples in [examples](./examples/) folder. You can run these examples with the following command:
```bash
cd examples
cargo run --bin example-basic-auth
```You can use any example name you want to run instead of `basic-auth` here.
## 🚀 Performance
Benchmark testing result can be found from here:
[https://web-frameworks-benchmark.netlify.app/result?l=rust](https://web-frameworks-benchmark.netlify.app/result?l=rust)
[https://www.techempower.com/benchmarks/#section=data-r22](https://www.techempower.com/benchmarks/#section=data-r22)
## 🩸 Contributors
## ☕ Donate
Salvo is an open source project. If you want to support Salvo, you can ☕ [**buy me a coffee here**](https://ko-fi.com/chrislearn).
## ⚠️ License
Salvo is licensed under either of
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)).
- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)).