同步操作将从 salvo-rs/salvo 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
Salvo is an extremely simple and powerful Rust web backend framework. Only basic Rust knowledge is required to develop backend services.
Note: salvo's main branch is currently preparing breaking changes. For the most recently released code, look to the 0.37.x branch.
You can view samples here, or view offical website.
Create a new rust project:
cargo new hello_salvo --bin
Add this to Cargo.toml
[dependencies]
salvo = "*"
tokio = { version = "1", features = ["macros"] }
Create a simple function handler in the main.rs file, we call it hello
, this function just render plain text "Hello World"
.
use salvo::prelude::*;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("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:
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
let router = Router::new().get(hello);
let acceptor = TcpListener::new("127.0.0.1:7878").bind().await;
Server::new(acceptor).serve(router).await;
}
There is no difference between Handler and Middleware, Middleware is just Handler. So you can write middlewares without to know concpets like associated type, generic type. You can write middleware if you can write function!!!
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:
Router::new().hoop(add_header).get(hello)
This is a very simple middleware, it add Header
to Response
, view full source code.
Normally we write routing like this:
Router::with_path("articles").get(list_articles).post(create_article);
Router::with_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:
Router::with_path("articles")
.get(list_articles)
.push(Router::with_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:
Router::with_path("articles")
.hoop(auth_check)
.post(list_articles)
.push(Router::with_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:
Router::new()
.push(
Router::with_path("articles")
.get(list_articles)
.push(Router::with_path("<id>").get(show_article)),
)
.push(
Router::with_path("articles")
.hoop(auth_check)
.post(list_articles)
.push(Router::with_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+/>"
.
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:
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:
Router::with_path("<id:guid>").get(index)
View full source code
We can get file async by the function file
in Request
:
#[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.set_status_code(StatusCode::INTERNAL_SERVER_ERROR);
} else {
res.render("Ok");
}
} else {
res.set_status_code(StatusCode::BAD_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:
#[derive(Serialize, Deserialize, Extractible, Debug)]
/// Get the data field value from the body by default.
#[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.
#[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:
#[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:
#[handler]
async fn edit<'a>(good_man: GoodMan<'a>) {
res.render(Json(good_man));
}
There is considerable flexibility in the definition of data types, and can even be resolved into nested structures as needed:
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[extract(default_source(from = "body", format = "json"))]
struct GoodMan<'a> {
#[extract(source(from = "param"))]
id: i64,
#[extract(source(from = "query"))]
username: &'a str,
first_name: String,
last_name: String,
lovers: Vec<String>,
/// The nested field is completely reparsed from Request.
#[extract(source(from = "request"))]
nested: Nested<'a>,
}
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[extract(default_source(from = "body", format = "json"))]
struct Nested<'a> {
#[extract(source(from = "param"))]
id: i64,
#[extract(source(from = "query"))]
username: &'a str,
first_name: String,
last_name: String,
#[extract(rename = "lovers")]
#[serde(default)]
pets: Vec<String>,
}
View full source code
Your can find more examples in examples folder. You can run these examples with the following command:
cargo run --bin example-basic-auth
You can use any example name you want to run instead of basic-auth
here.
Benchmark testing result can be found from here:
https://web-frameworks-benchmark.netlify.app/result?l=rust
https://www.techempower.com/benchmarks/#section=data-r21
You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.
Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:
All pull requests are code reviewed and tested by the CI. Note that unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Salvo by you shall be dual licensed under the MIT License, without any additional terms or conditions.
Salvo is an open source project. If you want to support Salvo, you can ☕ buy a coffee here.
Salvo is licensed under either of
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。