Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - BrotherSam66/salvo: Salvo is a powerful and simplest web server framework in Rust world · GitHub
Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - BrotherSam66/salvo: Salvo is a powerful and simplest web server framework in Rust world · GitHub
Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - BrotherSam66/salvo: Salvo is a powerful and simplest web server framework in Rust world · GitHub
Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - BrotherSam66/salvo: Salvo is a powerful and simplest web server framework in Rust world · GitHub
Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - BrotherSam66/salvo: Salvo is a powerful and simplest web server framework in Rust world · GitHub
Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - BrotherSam66/salvo: Salvo is a powerful and simplest web server framework in Rust world · GitHub
Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - BrotherSam66/salvo: Salvo is a powerful and simplest web server framework in Rust world · GitHub
Skip to content

Repository files navigation

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.

🎯 Features

  • Built with Hyper and Tokio;
  • Http1, Http2 and Http3;
  • Unified middleware and handle interface;
  • Limitless routers nesting;
  • Every router can have one or many middlewares;
  • Integrated Multipart form processing;
  • Support WebSocket;
  • Acme support, automatically get TLS certificate from let's encrypt.

⚡️ Quick start

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]asyncfnhello(res:&mutResponse){
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]asyncfnhello() -> &'staticstr{"Hello World"}#[tokio::main]asyncfnmain(){let router = Router::new().get(hello);let acceptor = TcpListener::new("127.0.0.1:7878").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 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]asyncfnadd_header(res:&mutResponse){
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.

Chainable tree routing system

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

File upload

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

#[handler]asyncfnupload(req:&mutRequest,res:&mutResponse){let file = req.file("file").await;ifletSome(file) = file {let dest = format!("temp/{}", file.name().unwrap_or_else(|| "file".into()));ifletErr(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);}}

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:

#[derive(Serialize,Deserialize,Extractible,Debug)]/// Get the data field value from the body by default.#[extract(default_source(from = "body"))]structGoodMan<'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:&'astr,first_name:String,last_name:String,}

Then in Handler you can get the data like this:

#[handler]asyncfnedit(req:&mutRequest){let good_man:GoodMan<'_> = req.extract().await.unwrap();}

You can even pass the type directly to the function as a parameter, like this:

#[handler]asyncfnedit<'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"))]structGoodMan<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,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"))]structNested<'a>{#[extract(source(from = "param"))]id:i64,#[extract(source(from = "query"))]username:&'astr,first_name:String,last_name:String,#[extract(rename = "lovers")]#[serde(default)]pets:Vec<String>,}

View full source code

More Examples

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.

🚀 Performance

Benchmark testing result can be found from here:

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r21techempower

🎇 Deployment

You can deploy your salvo projects through shuttle.rs, it is very easy, you can read shuttle's offical document here.

🩸 Contributing

Contributions are absolutely, positively welcome and encouraged! Contributions come in many forms. You could:

  • Submit a feature request or bug report as an issue;
  • Comment on issues that require feedback;
  • Contribute code via pull requests;
  • Publish Salvo-related technical articles on blogs or technical platforms。

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.

☕ Supporters

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

AlipayWeixin

⚠️ License

Salvo is licensed under either of

About

Salvo is a powerful and simplest web server framework in Rust world

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages