---concurrency-limiter---
---concurrency-limiter/Cargo.mdx---
[package]
name = "example-concurrency-limiter"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["concurrency-limiter"]}
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---concurrency-limiter/src---
---concurrency-limiter/src/main.mdx---
use std::fs::create_dir_all;
use std::path::Path;
use salvo::prelude::*;
// Handler for serving the index page with upload forms
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
// Handler for processing file uploads with a simulated delay
#[handler]
async fn upload(req: &mut Request, res: &mut Response) {
// Extract file from the multipart form data
let file = req.file("file").await;
// Simulate a long-running operation (10 seconds)
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
if let Some(file) = file {
// Generate destination path for the uploaded file
let dest = format!("temp/{}", file.name().unwrap_or("file"));
tracing::debug!(dest = %dest, "upload file");
// Copy file to destination
if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Plain(format!("file not found in request: {e}")));
} else {
res.render(Text::Plain(format!("File uploaded to {dest}")));
}
} else {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Text::Plain("file not found in request"));
}
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Create temporary directory for file uploads
create_dir_all("temp").unwrap();
// Configure router with two upload endpoints:
// - /limited: Only allows one concurrent upload (with concurrency limiter)
// - /unlimit: Allows unlimited concurrent uploads
let router = Router::new()
.get(index)
.push(
Router::new()
.hoop(max_concurrency(1)) // Limit concurrent requests to 1
.path("limited")
.post(upload),
)
.push(Router::with_path("unlimit").post(upload));
// Bind server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
// HTML template for the upload forms page
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Upload file</title>
</head>
<body>
<h1>Upload file</h1>
<form action="/unlimit" method="post" enctype="multipart/form-data">
<h3>Unlimit</h3>
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
<form action="/limited" method="post" enctype="multipart/form-data">
<h3>Limited</h3>
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
</body>
</html>
"#;
---csrf-session-store---
---csrf-session-store/Cargo.mdx---
[package]
name = "example-csrf-session-store"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["csrf", "session"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
serde = { version = "1", features = ["derive"] }
---csrf-session-store/src---
---csrf-session-store/src/main.mdx---
use salvo::csrf::*;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
// Handler for serving the home page with links to different CSRF protection methods
#[handler]
pub async fn home(res: &mut Response) {
let html = r#"
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Csrf SessionStore</title></head>
<body>
<h2>Csrf Exampe: SessionStore</h2>
<ul>
<li><a href="/bcrypt/">Bcrypt</a></li>
<li><a href="/hmac/">Hmac</a></li>
<li><a href="/aes_gcm/">Aes Gcm</a></li>
<li><a href="/ccp/">chacha20poly1305</a></li>
</ul>
</body>"#;
res.render(Text::Html(html));
}
// Handler for GET requests that displays a form with CSRF token
#[handler]
pub async fn get_page(depot: &mut Depot, res: &mut Response) {
let new_token = depot.csrf_token().unwrap_or_default();
res.render(Text::Html(get_page_html(new_token, "")));
}
// Handler for POST requests that processes form submission with CSRF validation
#[handler]
pub async fn post_page(req: &mut Request, depot: &mut Depot, res: &mut Response) {
// Define data structure for form submission
#[derive(Deserialize, Serialize, Debug)]
struct Data {
csrf_token: String,
message: String,
}
// Parse the submitted form data into the Data struct
let data = req.parse_form::<Data>().await.unwrap();
// Log the received form data for debugging
tracing::info!("posted data: {:?}", data);
// Generate a new CSRF token for the next request
let new_token = depot.csrf_token().unwrap_or_default();
// Generate HTML response with the new token and display the submitted data
let html = get_page_html(new_token, &format!("{data:#?}"));
// Send the HTML response back to the client
res.render(Text::Html(html));
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Configure CSRF token finder in form data
let form_finder = FormFinder::new("csrf_token");
// Initialize different CSRF protection methods using session store
let bcrypt_csrf = bcrypt_session_csrf(form_finder.clone());
let hmac_csrf = hmac_session_csrf(*b"01234567012345670123456701234567", form_finder.clone());
let aes_gcm_session_csrf =
aes_gcm_session_csrf(*b"01234567012345670123456701234567", form_finder.clone());
let ccp_session_csrf =
ccp_session_csrf(*b"01234567012345670123456701234567", form_finder.clone());
// Configure session handler with memory store and secret key
let session_handler = salvo::session::SessionHandler::builder(
salvo::session::MemoryStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
// Configure router with session handler and different CSRF protection endpoints
let router = Router::new()
.get(home)
.hoop(session_handler)
// Bcrypt-based CSRF protection
.push(
Router::with_hoop(bcrypt_csrf)
.path("bcrypt")
.get(get_page)
.post(post_page),
)
// HMAC-based CSRF protection
.push(
Router::with_hoop(hmac_csrf)
.path("hmac")
.get(get_page)
.post(post_page),
)
// AES-GCM-based CSRF protection
.push(
Router::with_hoop(aes_gcm_session_csrf)
.path("aes_gcm")
.get(get_page)
.post(post_page),
)
// ChaCha20Poly1305-based CSRF protection
.push(
Router::with_hoop(ccp_session_csrf)
.path("ccp")
.get(get_page)
.post(post_page),
);
// Start server on port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
// Helper function to generate HTML page with CSRF token and message
fn get_page_html(csrf_token: &str, msg: &str) -> String {
format!(
r#"
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Csrf SessionStore</title></head>
<body>
<h2>Csrf Exampe: SessionStore</h2>
<ul>
<li><a href="/bcrypt/">Bcrypt</a></li>
<li><a href="/hmac/">Hmac</a></li>
<li><a href="/aes_gcm/">Aes Gcm</a></li>
<li><a href="/ccp/">chacha20poly1305</a></li>
</ul>
<form action="./" method="post">
<input type="hidden" name="csrf_token" value="{csrf_token}" />
<div>
<label>Message:<input type="text" name="message" /></label>
</div>
<button type="submit">Send</button>
</form>
<pre>{msg}</pre>
</body>
</html>
"#
)
}
---webtransport---
---webtransport/certs---
---webtransport/static---
---webtransport/static/client.mdx---
<!doctype html>
<html lang="en">
<title>WebTransport over HTTP/3 client</title>
<meta charset="utf-8">
<!-- WebTransport origin trial token. See https://developer.chrome.com/origintrials/#/view_trial/793759434324049921 -->
<meta http-equiv="origin-trial" content="AkSQvBVsfMTgBtlakApX94hWGyBPQJXerRc2Aq8g/sKTMF+yG62+bFUB2yIxaK1furrNH3KNNeJV00UZSZHicw4AAABceyJvcmlnaW4iOiJodHRwczovL2dvb2dsZWNocm9tZS5naXRodWIuaW86NDQzIiwiZmVhdHVyZSI6IldlYlRyYW5zcG9ydCIsImV4cGlyeSI6MTY0Mzc1OTk5OX0=">
<script src="client.js"></script>
<link rel="stylesheet" href="client.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<div id="top">
<div id="explanation">
This tool can be used to connect to an arbitrary WebTransport server.
It has several limitations:
<ul>
<li>It can only send an entirety of a stream at once. Once the stream
is opened, all of the data is immediately sent, and the write side of
the steam is closed.</li>
<li>This tool does not listen to server-initiated bidirectional
streams.</li>
<li>Stream IDs are different from the one used by QUIC on the wire, as
the on-the-wire IDs are not exposed via the Web API.</li>
<li>The <code>WebTransport</code> object can be accessed using the developer console via <code>currentTransport</code>.</li>
</ul>
</div>
<div id="tool">
<h1>WebTransport over HTTP/3 client</h1>
<div>
<h2>Establish WebTransport connection</h2>
<div class="input-line">
<label for="url">URL:</label>
<input type="text" name="url" id="url"
value="https://0.0.0.0:5800/counter">
<input type="button" id="connect" value="Connect" onclick="connect()">
</div>
</div>
<div>
<h2>Send data over WebTransport</h2>
<form name="sending">
<textarea name="data" id="data"></textarea>
<div>
<input type="radio" name="sendtype" value="datagram"
id="datagram" checked>
<label for="datagram">Send a datagram</label>
</div>
<div>
<input type="radio" name="sendtype" value="unidi" id="unidi-stream">
<label for="unidi-stream">Open a unidirectional stream</label>
</div>
<div>
<input type="radio" name="sendtype" value="bidi" id="bidi-stream">
<label for="bidi-stream">Open a bidirectional stream</label>
</div>
<input type="button" id="send" name="send" value="Send data"
disabled onclick="sendData()">
</form>
</div>
<div>
<h2>Event log</h2>
<ul id="event-log">
</ul>
</div>
</div>
</div>
</body>
</html>
---webtransport/Cargo.mdx---
[package]
name = "example-webtransport"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
futures-util = "0.3"
salvo = { version = "0.77.1", features = ["quinn", "anyhow", "serve-static"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
serde = "1"
serde_json = "1"
bytes = "1"
---webtransport/src---
---webtransport/src/main.mdx---
use std::time::Duration;
use anyhow::{Context, Result};
use bytes::{BufMut, Bytes, BytesMut};
use salvo::conn::rustls::{Keycert, RustlsConfig};
use salvo::prelude::*;
use salvo::proto::webtransport;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::pin;
macro_rules! log_result {
($expr:expr) => {
if let Err(err) = $expr {
tracing::error!("{err:?}");
}
};
}
async fn echo_stream<T, R>(send: T, recv: R) -> anyhow::Result<()>
where
T: AsyncWrite,
R: AsyncRead,
{
pin!(send);
pin!(recv);
tracing::info!("Got stream");
let mut buf = Vec::new();
recv.read_to_end(&mut buf).await?;
let message = Bytes::from(buf);
send_chunked(send, message).await?;
Ok(())
}
// Used to test that all chunks arrive properly as it is easy to write an impl which only reads and
// writes the first chunk.
async fn send_chunked(mut send: impl AsyncWrite + Unpin, data: Bytes) -> anyhow::Result<()> {
for chunk in data.chunks(4) {
tokio::time::sleep(Duration::from_millis(100)).await;
tracing::info!("Sending {chunk:?}");
send.write_all(chunk).await?;
}
Ok(())
}
#[handler]
async fn connect(req: &mut Request) -> Result<(), salvo::Error> {
let session = req.web_transport_mut().await.unwrap();
let session_id = session.session_id();
// This will open a bidirectional stream and send a message to the client right after connecting!
let stream = session.open_bi(session_id).await?;
tokio::spawn(async move {
log_result!(open_bidi_test(stream).await);
});
loop {
tokio::select! {
datagram = session.accept_datagram() => {
let datagram = datagram?;
if let Some((_, datagram)) = datagram {
tracing::info!("Responding with {datagram:?}");
// Put something before to make sure encoding and decoding works and don't just
// pass through
let mut resp = BytesMut::from(&b"Response: "[..]);
resp.put(datagram);
session.send_datagram(resp.freeze())?;
tracing::info!("Finished sending datagram");
}
}
uni_stream = session.accept_uni() => {
let (id, stream) = uni_stream?.unwrap();
let send = session.open_uni(id).await?;
tokio::spawn( async move { log_result!(echo_stream(send, stream).await); });
}
stream = session.accept_bi() => {
if let Some(webtransport::server::AcceptedBi::BidiStream(_, stream)) = stream? {
let (send, recv) = salvo::proto::quic::BidiStream::split(stream);
tokio::spawn( async move { log_result!(echo_stream(send, recv).await); });
}
}
else => {
break
}
}
}
tracing::info!("Finished handling session");
Ok(())
}
async fn open_bidi_test<S>(mut stream: S) -> anyhow::Result<()>
where
S: Unpin + AsyncRead + AsyncWrite,
{
tracing::info!("Opening bidirectional stream");
stream
.write_all(b"Hello from a server initiated bidi stream")
.await
.context("Failed to respond")?;
let mut resp = Vec::new();
stream.shutdown().await?;
stream.read_to_end(&mut resp).await?;
tracing::info!("Got response from client: {resp:?}");
Ok(())
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let cert = include_bytes!("../certs/cert.pem").to_vec();
let key = include_bytes!("../certs/key.pem").to_vec();
let router = Router::new()
.push(Router::with_path("counter").goal(connect))
.push(
Router::with_path("{*path}")
.get(StaticDir::new(["webtransport/static", "./static"]).defaults("client.html")),
);
let config = RustlsConfig::new(Keycert::new().cert(cert.as_slice()).key(key.as_slice()));
let listener = TcpListener::new(("0.0.0.0", 5800)).rustls(config.clone());
let acceptor = QuinnListener::new(config, ("0.0.0.0", 5800))
.join(listener)
.bind()
.await;
Server::new(acceptor).serve(router).await;
}
---catch-error---
---catch-error/Cargo.mdx---
[package]
name = "example-catch-error"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
eyre = "0.6"
salvo = { version = "0.77.1", features = ["anyhow", "eyre"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---catch-error/src---
---catch-error/src/main.mdx---
use salvo::prelude::*;
// Custom error type for demonstration
struct CustomError;
// Implement Writer trait for CustomError to customize error response
#[async_trait]
impl Writer for CustomError {
async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
// Set response status code to 500 and custom error message
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render("custom error");
}
}
// Handler that returns an anyhow error for testing error handling
#[handler]
async fn handle_anyhow() -> Result<(), anyhow::Error> {
Err(anyhow::anyhow!("handled anyhow error"))
}
// Handler that returns an eyre error for testing error handling
#[handler]
async fn handle_eyre() -> eyre::Result<()> {
Err(eyre::Report::msg("handled eyre error"))
}
// Handler that returns our custom error type
#[handler]
async fn handle_custom() -> Result<(), CustomError> {
Err(CustomError)
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Set up router with three error handling endpoints:
// - /anyhow : demonstrates anyhow error handling
// - /eyre : demonstrates eyre error handling
// - /custom : demonstrates custom error handling
let router = Router::new()
.push(Router::with_path("anyhow").get(handle_anyhow))
.push(Router::with_path("eyre").get(handle_eyre))
.push(Router::with_path("custom").get(handle_custom));
// Bind server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---join-listeners---
---join-listeners/Cargo.mdx---
[package]
name = "example-join-listeners"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---join-listeners/src---
---join-listeners/src/main.mdx---
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let acceptor = TcpListener::new("0.0.0.0:5800")
.join(TcpListener::new("0.0.0.0:5801"))
.bind()
.await;
Server::new(acceptor).serve(router).await;
}
---oapi-todos---
---oapi-todos/Cargo.mdx---
[package]
name = "example-oapi-todos"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["oapi"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros"] }
compact_str = { version = "0.7", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---oapi-todos/src---
---oapi-todos/src/main.mdx---
use std::sync::LazyLock;
use salvo::oapi::{ToSchema, extract::*};
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
static STORE: LazyLock<Db> = LazyLock::new(new_store);
pub type Db = Mutex<Vec<Todo>>;
pub fn new_store() -> Db {
Mutex::new(Vec::new())
}
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)]
pub struct Todo {
#[salvo(schema(example = 1))]
pub id: u64,
#[salvo(schema(example = "Buy coffee"))]
pub text: String,
pub completed: bool,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(index).push(
Router::with_path("api").push(
Router::with_path("todos")
.get(list_todos)
.post(create_todo)
.push(
Router::with_path("{id}")
.patch(update_todo)
.delete(delete_todo),
),
),
);
let doc = OpenApi::new("todos api", "0.0.1").merge_router(&router);
let router = router
.unshift(doc.into_router("/api-doc/openapi.json"))
.unshift(
SwaggerUi::new("/api-doc/openapi.json")
.title("Todos - SwaggerUI")
.into_router("/swagger-ui"),
)
.unshift(
Scalar::new("/api-doc/openapi.json")
.title("Todos - Scalar")
.into_router("/scalar"),
)
.unshift(
RapiDoc::new("/api-doc/openapi.json")
.title("Todos - RapiDoc")
.into_router("/rapidoc"),
)
.unshift(
ReDoc::new("/api-doc/openapi.json")
.title("Todos - ReDoc")
.into_router("/redoc"),
);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
#[handler]
pub async fn index() -> Text<&'static str> {
Text::Html(INDEX_HTML)
}
/// List todos.
#[endpoint(
tags("todos"),
parameters(
("offset", description = "Offset is an optional query paramter."),
)
)]
pub async fn list_todos(
offset: QueryParam<usize, false>,
limit: QueryParam<usize, false>,
) -> Json<Vec<Todo>> {
let todos = STORE.lock().await;
let todos: Vec<Todo> = todos
.clone()
.into_iter()
.skip(offset.into_inner().unwrap_or(0))
.take(limit.into_inner().unwrap_or(usize::MAX))
.collect();
Json(todos)
}
/// Create new todo.
#[endpoint(tags("todos"), status_codes(201, 409))]
pub async fn create_todo(req: JsonBody<Todo>) -> Result<StatusCode, StatusError> {
tracing::debug!(todo = ?req, "create todo");
let mut vec = STORE.lock().await;
for todo in vec.iter() {
if todo.id == req.id {
tracing::debug!(id = ?req.id, "todo already exists");
return Err(StatusError::bad_request().brief("todo already exists"));
}
}
vec.push(req.into_inner());
Ok(StatusCode::CREATED)
}
/// Update existing todo.
#[endpoint(tags("todos"), status_codes(200, 404))]
pub async fn update_todo(
id: PathParam<u64>,
updated: JsonBody<Todo>,
) -> Result<StatusCode, StatusError> {
tracing::debug!(todo = ?updated, id = ?id, "update todo");
let mut vec = STORE.lock().await;
for todo in vec.iter_mut() {
if todo.id == *id {
*todo = (*updated).clone();
return Ok(StatusCode::OK);
}
}
tracing::debug!(?id, "todo is not found");
Err(StatusError::not_found())
}
/// Delete todo.
#[endpoint(tags("todos"), status_codes(200, 401, 404))]
pub async fn delete_todo(id: PathParam<u64>) -> Result<StatusCode, StatusError> {
tracing::debug!(?id, "delete todo");
let mut vec = STORE.lock().await;
let len = vec.len();
vec.retain(|todo| todo.id != *id);
let deleted = vec.len() != len;
if deleted {
Ok(StatusCode::NO_CONTENT)
} else {
tracing::debug!(?id, "todo is not found");
Err(StatusError::not_found())
}
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Oapi todos</title>
</head>
<body>
<ul>
<li><a href="swagger-ui" target="_blank">swagger-ui</a></li>
<li><a href="scalar" target="_blank">scalar</a></li>
<li><a href="rapidoc" target="_blank">rapidoc</a></li>
<li><a href="redoc" target="_blank">redoc</a></li>
</ul>
</body>
</html>
"#;
---db-mongodb---
---db-mongodb/Cargo.mdx---
[package]
name = "example-db-mongodb"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
futures.workspace = true
serde = { version = "1", features = ["derive"] }
serde_json = "1"
mongodb = "2"
thiserror = "1"
---db-mongodb/src---
---db-mongodb/src/main.mdx---
use std::sync::OnceLock;
use futures::stream::TryStreamExt;
use mongodb::{
Client, Collection, IndexModel, bson::Document, bson::doc, bson::oid::ObjectId,
options::IndexOptions,
};
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
// Database and collection names
const DB_NAME: &str = "myApp";
const COLL_NAME: &str = "users";
use thiserror::Error;
// Custom error type for MongoDB operations
#[derive(Error, Debug)]
pub enum Error {
#[error("MongoDB Error")]
ErrorMongo(#[from] mongodb::error::Error),
}
pub type AppResult<T> = Result<T, Error>;
// Implement error response writer for our custom error type
#[async_trait]
impl Writer for Error {
async fn write(self, _req: &mut Request, _depot: &mut Depot, _res: &mut Response) {}
}
// User model representing the document structure in MongoDB
#[derive(Debug, Deserialize, Serialize)]
struct User {
_id: Option<ObjectId>,
first_name: String,
last_name: String,
username: String,
email: String,
}
// Global MongoDB client instance
static MONGODB_CLIENT: OnceLock<Client> = OnceLock::new();
// Helper function to get the MongoDB client instance
#[inline]
pub fn get_mongodb_client() -> &'static Client {
MONGODB_CLIENT.get().unwrap()
}
// Handler for adding a new user to the database
#[handler]
async fn add_user(req: &mut Request, res: &mut Response) {
let client = get_mongodb_client();
let coll_users = client.database(DB_NAME).collection::<Document>(COLL_NAME);
let new_user = req.parse_json::<User>().await.unwrap();
// Create BSON document from user data
let user = doc! {
"first_name": new_user.first_name,
"last_name": new_user.last_name,
"username": new_user.username,
"email": new_user.email,
};
// Insert user document into MongoDB
let result = coll_users.insert_one(user, None).await;
match result {
Ok(id) => res.render(format!("user added with ID {:?}", id.inserted_id)),
Err(e) => res.render(format!("error {e:?}")),
}
}
// Handler for retrieving all users from the database
#[handler]
async fn get_users(res: &mut Response) -> AppResult<()> {
let client = get_mongodb_client();
let coll_users = client.database(DB_NAME).collection::<User>(COLL_NAME);
// Find all users and convert cursor to vector
let mut cursor = coll_users.find(None, None).await?;
let mut vec_users: Vec<User> = Vec::new();
while let Some(user) = cursor.try_next().await? {
vec_users.push(user);
}
res.render(Json(vec_users));
Ok(())
}
// Handler for retrieving a single user by username
#[handler]
async fn get_user(req: &mut Request, res: &mut Response) {
let client = get_mongodb_client();
let coll_users: Collection<User> = client.database(DB_NAME).collection(COLL_NAME);
let username = req.param::<String>("username").unwrap();
// Find user by username
match coll_users
.find_one(doc! { "username": &username }, None)
.await
{
Ok(Some(user)) => res.render(Json(user)),
Ok(None) => res.render(format!("No user found with username {username}")),
Err(e) => res.render(format!("error {e:?}")),
}
}
// Create a unique index on the username field
async fn create_username_index(client: &Client) {
let options = IndexOptions::builder().unique(true).build();
let model = IndexModel::builder()
.keys(doc! { "username": 1 })
.options(options)
.build();
client
.database(DB_NAME)
.collection::<User>(COLL_NAME)
.create_index(model, None)
.await
.expect("creating an index should succeed");
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Get MongoDB connection URI from environment or use default
let mongodb_uri =
std::env::var("MONGODB_URI").unwrap_or_else(|_| "mongodb://10.1.1.80:27017".into());
// Connect to MongoDB and create unique index
let client = Client::with_uri_str(mongodb_uri)
.await
.expect("failed to connect");
create_username_index(&client).await;
// Store MongoDB client in global state
MONGODB_CLIENT.set(client).unwrap();
// Configure router with user management endpoints:
// - GET /users : List all users
// - POST /users : Create new user
// - GET /users/{username} : Get user by username
let router = Router::with_path("users")
.get(get_users)
.post(add_user)
.push(Router::with_path("{username}").get(get_user));
// Start server on port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---websocket---
---websocket/Cargo.mdx---
[package]
name = "example-websocket"
version = "0.1.0"
edition = "2024"
[dependencies]
futures-util = "0.3"
salvo = { version = "0.77.1", features = ["websocket"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
serde = "1"
serde_json = "1"
---websocket/src---
---websocket/src/main.mdx---
use salvo::prelude::*;
use salvo::websocket::WebSocketUpgrade;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
struct User {
id: usize,
name: String,
}
#[handler]
async fn connect(req: &mut Request, res: &mut Response) -> Result<(), StatusError> {
let user = req.parse_queries::<User>();
WebSocketUpgrade::new()
.upgrade(req, res, |mut ws| async move {
println!("{user:#?} ");
while let Some(msg) = ws.recv().await {
let msg = if let Ok(msg) = msg {
msg
} else {
// client disconnected
return;
};
if ws.send(msg).await.is_err() {
// client disconnected
return;
}
}
})
.await
}
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new()
.get(index)
.push(Router::with_path("ws").goal(connect));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>WS</title>
</head>
<body>
<h1>WS</h1>
<div id="status">
<p><em>Connecting...</em></p>
</div>
<script>
const status = document.getElementById('status');
const msg = document.getElementById('msg');
const submit = document.getElementById('submit');
const ws = new WebSocket(`ws://${location.host}/ws?id=123&name=chris`);
ws.onopen = function() {
status.innerHTML = '<p><em>Connected!</em></p>';
};
</script>
</body>
</html>
"#;
---sse---
---sse/Cargo.mdx---
[package]
name = "example-sse"
version = "0.1.0"
edition = "2024"
[dependencies]
futures-util = "0.3"
salvo = { version = "0.77.1", features = ["sse"] }
tokio = { version = "1", features = ["macros"] }
tokio-stream = { version = "0.1", features = ["net"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---sse/src---
---sse/src/main.mdx---
// Copyright (c) 2018-2020 Sean McArthur
// Licensed under the MIT license http://opensource.org/licenses/MIT
//port from https://github.com/seanmonstar/warp/blob/master/examples/sse.rs
use std::convert::Infallible;
use std::time::Duration;
use futures_util::StreamExt;
use salvo::prelude::*;
use salvo::sse::{self, SseEvent};
use tokio::time::interval;
use tokio_stream::wrappers::IntervalStream;
// create server-sent event
fn sse_counter(counter: u64) -> Result<SseEvent, Infallible> {
Ok(SseEvent::default().text(counter.to_string()))
}
#[handler]
async fn handle_tick(res: &mut Response) {
let event_stream = {
let mut counter: u64 = 0;
let interval = interval(Duration::from_secs(1));
let stream = IntervalStream::new(interval);
stream.map(move |_| {
counter += 1;
sse_counter(counter)
})
};
sse::stream(res, event_stream);
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("ticks").get(handle_tick);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---oapi-hello---
---oapi-hello/Cargo.mdx---
[package]
name = "example-oapi-hello"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["oapi"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---oapi-hello/src---
---oapi-hello/src/main.mdx---
use salvo::oapi::extract::*;
use salvo::prelude::*;
#[endpoint]
async fn hello(name: QueryParam<String, false>) -> String {
format!("Hello, {}!", name.as_deref().unwrap_or("World"))
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().push(Router::with_path("hello").get(hello));
let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);
let router = router
.unshift(doc.into_router("/api-doc/openapi.json"))
.unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---static-embed-file---
---static-embed-file/static---
---static-embed-file/static/test2.mdx---
copy2
---static-embed-file/static/test1.mdx---
copy1
---static-embed-file/Cargo.mdx---
[package]
name = "example-static-embed-file"
version = "0.1.0"
edition = "2024"
[dependencies]
rust-embed = { version = ">= 6, <= 9" }
salvo = { version = "0.77.1", features = ["serve-static"]}
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---static-embed-file/src---
---static-embed-file/src/main.mdx---
use rust_embed::RustEmbed;
use salvo::prelude::*;
use salvo::serve_static::EmbeddedFileExt;
#[derive(RustEmbed)]
#[folder = "static"]
struct Assets;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{**rest}").get(serve_file);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
#[handler]
async fn serve_file(req: &mut Request, res: &mut Response) {
let path = req.param::<String>("rest").unwrap();
if let Some(file) = Assets::get(&path) {
file.render(req, res);
} else {
res.status_code(StatusCode::NOT_FOUND);
}
}
---tls-rustls-reload---
---tls-rustls-reload/certs---
---tls-rustls-reload/Cargo.mdx---
[package]
name = "example-tls-rustls-reload"
version = "0.1.0"
edition = "2024"
[dependencies]
async-stream.workspace = true
salvo = { version = "0.77.1", features = ["rustls"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---tls-rustls-reload/src---
---tls-rustls-reload/src/main.mdx---
use salvo::conn::rustls::{Keycert, RustlsConfig};
use salvo::prelude::*;
use tokio::time::Duration;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello World"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let acceptor = TcpListener::new("0.0.0.0:5800")
.rustls(async_stream::stream! {
loop {
yield load_config();
tokio::time::sleep(Duration::from_secs(60)).await;
}
})
.bind()
.await;
Server::new(acceptor).serve(router).await;
}
fn load_config() -> RustlsConfig {
RustlsConfig::new(
Keycert::new()
.cert(include_bytes!("../certs/cert.pem").as_ref())
.key(include_bytes!("../certs/key.pem").as_ref()),
)
}
---craft---
---craft/Cargo.mdx---
[package]
name = "example-craft"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["craft", "oapi"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---craft/src---
---craft/src/main.mdx---
use salvo::oapi::extract::*;
use salvo::prelude::*;
use std::sync::Arc;
// Options struct holding a state value for calculations
#[derive(Clone)]
pub struct Opts {
state: i64,
}
// Implement methods for Opts using the craft macro for API generation
#[craft]
impl Opts {
// Constructor for Opts
fn new(state: i64) -> Self {
Self { state }
}
// Handler method that adds state value to two query parameters
#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(self.state + *left + *right).to_string()
}
// Endpoint method using Arc for shared state
#[craft(endpoint)]
pub(crate) fn add2(
self: ::std::sync::Arc<Self>,
left: QueryParam<i64>,
right: QueryParam<i64>,
) -> String {
(self.state + *left + *right).to_string()
}
// Static endpoint method with custom error response
#[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(*left + *right).to_string()
}
}
#[tokio::main]
async fn main() {
// Create shared state with initial value 1
let opts = Arc::new(Opts::new(1));
// Configure router with three endpoints:
// - /add1: Uses instance method with state
// - /add2: Uses Arc-wrapped instance method
// - /add3: Uses static method without state
let router = Router::new()
.push(Router::with_path("add1").get(opts.add1()))
.push(Router::with_path("add2").get(opts.add2()))
.push(Router::with_path("add3").get(Opts::add3()));
// Generate OpenAPI documentation
let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);
// Add OpenAPI documentation and Swagger UI routes
let router = router
.push(doc.into_router("/api-doc/openapi.json"))
.push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));
// Start server on localhost:5800
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---affix-state---
---affix-state/Cargo.mdx---
[package]
name = "example-affix-state"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["affix-state"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---affix-state/src---
---affix-state/src/main.mdx---
use std::sync::Arc;
use std::sync::Mutex;
use salvo::prelude::*;
// Configuration structure with username and password
#[allow(dead_code)]
#[derive(Default, Clone, Debug)]
struct Config {
username: String,
password: String,
}
// State structure to hold a list of fail messages
#[derive(Default, Debug)]
struct State {
fails: Mutex<Vec<String>>,
}
#[handler]
async fn hello(depot: &mut Depot) -> String {
// Obtain the Config instance from the depot
let config = depot.obtain::<Config>().unwrap();
// Get custom data from the depot
let custom_data = depot.get::<&str>("custom_data").unwrap();
// Obtain the shared State instance from the depot
let state = depot.obtain::<Arc<State>>().unwrap();
// Lock the fails vector and add a new fail message
let mut fails_ref = state.fails.lock().unwrap();
fails_ref.push("fail message".into());
// Format and return the response string
format!("Hello World\nConfig: {config:#?}\nFails: {fails_ref:#?}\nCustom Data: {custom_data}")
}
#[tokio::main]
async fn main() {
// Initialize the tracing subscriber for logging
tracing_subscriber::fmt().init();
// Create a Config instance with default username and password
let config = Config {
username: "root".to_string(),
password: "pwd".to_string(),
};
// Set up the router with state injection and custom data
let router = Router::new()
// Use hoop to inject middleware and data into the request context
.hoop(
affix_state::inject(config)
// Inject a shared State instance into the request context
.inject(Arc::new(State {
fails: Mutex::new(Vec::new()),
}))
// Insert custom data into the request context
.insert("custom_data", "I love this world!"),
)
// Register the hello handler for the root path
.get(hello)
// Add an additional route for the path "/hello" with the same handler
.push(Router::with_path("hello").get(hello));
// Bind the server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---size-limiter---
---size-limiter/Cargo.mdx---
[package]
name = "example-size-limiter"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["size-limiter"]}
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---size-limiter/src---
---size-limiter/src/main.mdx---
use std::fs::create_dir_all;
use std::path::Path;
use salvo::prelude::*;
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
#[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("file"));
tracing::debug!(dest, "upload file");
if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Plain(format!("file not found in request: {e}")));
} else {
res.render(Text::Plain(format!("File uploaded to {dest}")));
}
} else {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Text::Plain("file not found in request"));
}
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
create_dir_all("temp").unwrap();
let router = Router::new()
.get(index)
.push(
Router::new()
.hoop(max_size(1024 * 1024 * 10))
.path("limited")
.post(upload),
)
.push(Router::with_path("unlimit").post(upload));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Upload file</title>
</head>
<body>
<h1>Upload file</h1>
<form action="/unlimit" method="post" enctype="multipart/form-data">
<h3>Unlimit</h3>
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
<form action="/limited" method="post" enctype="multipart/form-data">
<h3>Limited 10MiB</h3>
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
</body>
</html>
"#;
---tls-openssl---
---tls-openssl/certs---
---tls-openssl/Cargo.mdx---
[package]
name = "example-tls-openssl"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["openssl"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---tls-openssl/src---
---tls-openssl/src/main.mdx---
use salvo::conn::openssl::{Keycert, OpensslConfig};
use salvo::prelude::*;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello World"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let config = OpensslConfig::new(
Keycert::new()
.with_cert(include_bytes!("../certs/cert.pem").as_ref())
.with_key(include_bytes!("../certs/key.pem").as_ref()),
);
let acceptor = TcpListener::new("0.0.0.0:5800")
.openssl(config)
.bind()
.await;
Server::new(acceptor).serve(router).await;
}
---webtransport-acme-http01---
---webtransport-acme-http01/static---
---webtransport-acme-http01/static/client.mdx---
<!doctype html>
<html lang="en">
<title>WebTransport over HTTP/3 client</title>
<meta charset="utf-8">
<!-- WebTransport origin trial token. See https://developer.chrome.com/origintrials/#/view_trial/793759434324049921 -->
<meta http-equiv="origin-trial" content="AkSQvBVsfMTgBtlakApX94hWGyBPQJXerRc2Aq8g/sKTMF+yG62+bFUB2yIxaK1furrNH3KNNeJV00UZSZHicw4AAABceyJvcmlnaW4iOiJodHRwczovL2dvb2dsZWNocm9tZS5naXRodWIuaW86NDQzIiwiZmVhdHVyZSI6IldlYlRyYW5zcG9ydCIsImV4cGlyeSI6MTY0Mzc1OTk5OX0=">
<script src="client.js"></script>
<link rel="stylesheet" href="client.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<div id="top">
<div id="explanation">
This tool can be used to connect to an arbitrary WebTransport server.
It has several limitations:
<ul>
<li>It can only send an entirety of a stream at once. Once the stream
is opened, all of the data is immediately sent, and the write side of
the steam is closed.</li>
<li>This tool does not listen to server-initiated bidirectional
streams.</li>
<li>Stream IDs are different from the one used by QUIC on the wire, as
the on-the-wire IDs are not exposed via the Web API.</li>
<li>The <code>WebTransport</code> object can be accessed using the developer console via <code>currentTransport</code>.</li>
</ul>
</div>
<div id="tool">
<h1>WebTransport over HTTP/3 client</h1>
<div>
<h2>Establish WebTransport connection</h2>
<div class="input-line">
<label for="url">URL:</label>
<input type="text" name="url" id="url"
value="https://0.0.0.0:5800/counter">
<input type="button" id="connect" value="Connect" onclick="connect()">
</div>
</div>
<div>
<h2>Send data over WebTransport</h2>
<form name="sending">
<textarea name="data" id="data"></textarea>
<div>
<input type="radio" name="sendtype" value="datagram"
id="datagram" checked>
<label for="datagram">Send a datagram</label>
</div>
<div>
<input type="radio" name="sendtype" value="unidi" id="unidi-stream">
<label for="unidi-stream">Open a unidirectional stream</label>
</div>
<div>
<input type="radio" name="sendtype" value="bidi" id="bidi-stream">
<label for="bidi-stream">Open a bidirectional stream</label>
</div>
<input type="button" id="send" name="send" value="Send data"
disabled onclick="sendData()">
</form>
</div>
<div>
<h2>Event log</h2>
<ul id="event-log">
</ul>
</div>
</div>
</div>
</body>
</html>
---webtransport-acme-http01/Cargo.mdx---
[package]
name = "example-webtransport-acme-http01"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
futures-util = "0.3"
salvo = { version = "0.77.1", features = ["acme", "quinn", "anyhow", "serve-static"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
serde = "1"
serde_json = "1"
bytes = "1"
---webtransport-acme-http01/src---
---webtransport-acme-http01/src/main.mdx---
use std::time::Duration;
use anyhow::{Context, Result};
use bytes::{BufMut, Bytes, BytesMut};
use salvo::prelude::*;
use salvo::proto::webtransport;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::pin;
macro_rules! log_result {
($expr:expr) => {
if let Err(err) = $expr {
tracing::error!("{err:?}");
}
};
}
async fn echo_stream<T, R>(send: T, recv: R) -> anyhow::Result<()>
where
T: AsyncWrite,
R: AsyncRead,
{
pin!(send);
pin!(recv);
tracing::info!("Got stream");
let mut buf = Vec::new();
recv.read_to_end(&mut buf).await?;
let message = Bytes::from(buf);
send_chunked(send, message).await?;
Ok(())
}
// Used to test that all chunks arrive properly as it is easy to write an impl which only reads and
// writes the first chunk.
async fn send_chunked(mut send: impl AsyncWrite + Unpin, data: Bytes) -> anyhow::Result<()> {
for chunk in data.chunks(4) {
tokio::time::sleep(Duration::from_millis(100)).await;
tracing::info!("Sending {chunk:?}");
send.write_all(chunk).await?;
}
Ok(())
}
#[handler]
async fn connect(req: &mut Request) -> Result<(), salvo::Error> {
let session = req.web_transport_mut().await.unwrap();
let session_id = session.session_id();
// This will open a bidirectional stream and send a message to the client right after connecting!
let stream = session.open_bi(session_id).await?;
tokio::spawn(async move {
log_result!(open_bidi_test(stream).await);
});
loop {
tokio::select! {
datagram = session.accept_datagram() => {
let datagram = datagram?;
if let Some((_, datagram)) = datagram {
tracing::info!("Responding with {datagram:?}");
// Put something before to make sure encoding and decoding works and don't just
// pass through
let mut resp = BytesMut::from(&b"Response: "[..]);
resp.put(datagram);
session.send_datagram(resp.freeze())?;
tracing::info!("Finished sending datagram");
}
}
uni_stream = session.accept_uni() => {
let (id, stream) = uni_stream?.unwrap();
let send = session.open_uni(id).await?;
tokio::spawn( async move { log_result!(echo_stream(send, stream).await); });
}
stream = session.accept_bi() => {
if let Some(webtransport::server::AcceptedBi::BidiStream(_, stream)) = stream? {
let (send, recv) = salvo::proto::quic::BidiStream::split(stream);
tokio::spawn( async move { log_result!(echo_stream(send, recv).await); });
}
}
else => {
break
}
}
}
tracing::info!("Finished handling session");
Ok(())
}
async fn open_bidi_test<S>(mut stream: S) -> anyhow::Result<()>
where
S: Unpin + AsyncRead + AsyncWrite,
{
tracing::info!("Opening bidirectional stream");
stream
.write_all(b"Hello from a server initiated bidi stream")
.await
.context("Failed to respond")?;
let mut resp = Vec::new();
stream.shutdown().await?;
stream.read_to_end(&mut resp).await?;
tracing::info!("Got response from client: {resp:?}");
Ok(())
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let mut router = Router::new()
.push(Router::with_path("counter").goal(connect))
.push(
Router::with_path("{*path}")
.get(StaticDir::new(["webtransport/static", "./static"]).defaults("client.html")),
);
let listener = TcpListener::new("0.0.0.0:443")
.acme()
.cache_path("temp/letsencrypt")
.add_domain("test.salvo.rs")
.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;
}
---acme-http01-quinn---
---acme-http01-quinn/Cargo.mdx---
[package]
name = "example-acme-http01-quinn"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["acme", "quinn"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---acme-http01-quinn/src---
---acme-http01-quinn/src/main.mdx---
use salvo::prelude::*;
// Handler function that returns "Hello World" for any request
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
// Initialize logging
tracing_subscriber::fmt().init();
// Create a router and register the hello handler
let mut router = Router::new().get(hello);
// Set up a TCP listener on port 443 for HTTPS
let listener = TcpListener::new("0.0.0.0:443")
.acme() // Enable ACME for automatic SSL certificate management
.cache_path("temp/letsencrypt") // Path to store the certificate cache
.add_domain("test.salvo.rs") // replace with your domain
.http01_challenge(&mut router) // Add routes to handle ACME challenge requests
.quinn("0.0.0.0:443"); // Enable QUIC/HTTP3 support on the same port
// Create an acceptor that listens on both port 80 (HTTP) and port 443 (HTTPS)
let acceptor = listener.join(TcpListener::new("0.0.0.0:80")).bind().await;
// Start the server with the configured acceptor and router
Server::new(acceptor).serve(router).await;
}
---caching-headers---
---caching-headers/Cargo.mdx---
[package]
name = "example-caching-headers"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["caching-headers", "compression"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---caching-headers/src---
---caching-headers/src/main.mdx---
use salvo::prelude::*;
// Handler that returns a simple "Hello World" response
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Set up router with caching headers and compression middleware
// CachingHeader must be before Compression to properly set cache control headers
let router = Router::with_hoop(CachingHeaders::new())
.hoop(Compression::new().min_length(0)) // Enable compression for all responses
.get(hello);
// Bind server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---basic-auth---
---basic-auth/Cargo.mdx---
[package]
name = "example-basic-auth"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["basic-auth", "test"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---basic-auth/src---
---basic-auth/src/main.mdx---
use salvo::basic_auth::{BasicAuth, BasicAuthValidator};
use salvo::prelude::*;
// Custom validator implementing BasicAuthValidator trait
struct Validator;
impl BasicAuthValidator for Validator {
// Validate username and password combination
async fn validate(&self, username: &str, password: &str, _depot: &mut Depot) -> bool {
username == "root" && password == "pwd"
}
}
// Simple handler that returns "Hello" for authenticated requests
#[handler]
async fn hello() -> &'static str {
"Hello"
}
// Create router with basic authentication middleware
fn route() -> Router {
// Initialize basic authentication handler with our validator
let auth_handler = BasicAuth::new(Validator);
// Apply authentication middleware to the router
Router::with_hoop(auth_handler).goal(hello)
}
#[tokio::main]
async fn main() {
// Initialize logging
tracing_subscriber::fmt().init();
// Bind server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(route()).await;
}
#[cfg(test)]
mod tests {
use salvo::prelude::*;
use salvo::test::{ResponseExt, TestClient};
#[tokio::test]
async fn test_basic_auth() {
// Create a service instance from our router for testing purposes
let service = Service::new(super::route());
// Test case 1: Verify successful authentication with valid credentials
let content = TestClient::get("http://0.0.0.0:5800/")
.basic_auth("root", Some("pwd")) // Use correct username/password
.send(&service) // Send the request to the service
.await
.take_string() // Extract response body as string
.await
.unwrap();
// Verify response contains expected "Hello" message
assert!(content.contains("Hello"));
// Test case 2: Verify authentication failure with invalid password
let content = TestClient::get("http://0.0.0.0:5800/")
.basic_auth("root", Some("pwd2")) // Use incorrect password
.send(&service) // Send the request to the service
.await
.take_string() // Extract response body as string
.await
.unwrap();
// Verify response contains "Unauthorized" error
assert!(content.contains("Unauthorized"));
}
}
---rate-limiter-dynamic---
---rate-limiter-dynamic/Cargo.mdx---
[package]
name = "example-rate-limiter-dynamic"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["rate-limiter"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---rate-limiter-dynamic/src---
---rate-limiter-dynamic/src/main.mdx---
use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::LazyLock;
use salvo::Error;
use salvo::prelude::*;
use salvo::rate_limiter::{
CelledQuota, MokaStore, QuotaGetter, RateIssuer, RateLimiter, SlidingGuard,
};
static USER_QUOTAS: LazyLock<HashMap<String, CelledQuota>> = LazyLock::new(|| {
let mut map = HashMap::new();
map.insert("user1".into(), CelledQuota::per_second(1, 1));
map.insert("user2".into(), CelledQuota::set_seconds(1, 1, 5));
map.insert("user3".into(), CelledQuota::set_seconds(1, 1, 10));
map
});
struct UserIssuer;
impl RateIssuer for UserIssuer {
type Key = String;
async fn issue(&self, req: &mut Request, _depot: &Depot) -> Option<Self::Key> {
req.query::<Self::Key>("user")
}
}
struct CustomQuotaGetter;
impl QuotaGetter<String> for CustomQuotaGetter {
type Quota = CelledQuota;
type Error = Error;
async fn get<Q>(&self, key: &Q) -> Result<Self::Quota, Self::Error>
where
String: Borrow<Q>,
Q: Hash + Eq + Sync,
{
USER_QUOTAS
.get(key)
.cloned()
.ok_or_else(|| Error::other("user not found"))
}
}
#[handler]
async fn limited() -> &'static str {
"Limited page"
}
#[handler]
async fn home() -> Text<&'static str> {
Text::Html(HOME_HTML)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let limiter = RateLimiter::new(
SlidingGuard::new(),
MokaStore::new(),
UserIssuer,
CustomQuotaGetter,
);
let router = Router::new()
.get(home)
.push(Router::with_path("limited").hoop(limiter).get(limited));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static HOME_HTML: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>Rate Limiter Dynmaic</title>
</head>
<body>
<h2>Rate Limiter Dynamic</h2>
<p>
This example shows how to set limit for different users.
</p>
<p>
<a href="/limited?user=user1" target="_blank">Limited page for user1: 1/second</a>
</p>
<p>
<a href="/limited?user=user2" target="_blank">Limited page for user2: 1/5seconds</a>
</p>
<p>
<a href="/limited?user=user3" target="_blank">Limited page for user3: 1/10seconds</a>
</p>
</body>
</html>
"#;
---oapi-generics---
---oapi-generics/Cargo.mdx---
[package]
name = "example-oapi-generics"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["oapi"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---oapi-generics/src---
---oapi-generics/src/main.mdx---
use salvo::oapi::extract::*;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, ToSchema, Debug)]
#[salvo(schema(aliases(MyI32 = MyObject<i32>, MyStr = MyObject<String>)))]
struct MyObject<T: ToSchema + std::fmt::Debug + 'static> {
value: T,
}
/// Use string type, this will add to openapi doc.
#[endpoint]
async fn use_string(body: JsonBody<MyObject<String>>) -> String {
format!("{:?}", body)
}
/// Use i32 type, this will add to openapi doc.
#[endpoint]
async fn use_i32(body: JsonBody<MyObject<i32>>) -> String {
format!("{:?}", body)
}
/// Use u64 type, this will add to openapi doc.
#[endpoint]
async fn use_u64(body: JsonBody<MyObject<u64>>) -> String {
format!("{:?}", body)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
// Custom your OpenApi naming style. You should set it before using OpenApi.
salvo::oapi::naming::set_namer(
salvo::oapi::naming::FlexNamer::new()
.short_mode(true)
.generic_delimiter('_', '_'),
);
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
.unshift(doc.into_router("/api-doc/openapi.json"))
.unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---upload-files---
---upload-files/Cargo.mdx---
[package]
name = "example-upload-files"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---upload-files/src---
---upload-files/src/main.mdx---
use std::fs::create_dir_all;
use std::path::Path;
use salvo::prelude::*;
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
#[handler]
async fn upload(req: &mut Request, res: &mut Response) {
let files = req.files("files").await;
if let Some(files) = files {
let mut msgs = Vec::with_capacity(files.len());
for file in files {
let dest = format!("temp/{}", file.name().unwrap_or("file"));
if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Plain(format!("file not found in request: {e}")));
} else {
msgs.push(dest);
}
}
res.render(Text::Plain(format!(
"Files uploaded:\n\n{}",
msgs.join("\n")
)));
} else {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Text::Plain("file not found in request"));
}
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
create_dir_all("temp").unwrap();
let router = Router::new().get(index).post(upload);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Upload files</title>
</head>
<body>
<h1>Upload files</h1>
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple/>
<input type="submit" value="upload" />
</form>
</body>
</html>
"#;
---todos-utoipa---
---todos-utoipa/Cargo.mdx---
[package]
name = "example-todos-utoipa"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["affix-state", "size-limiter"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
utoipa = "4"
utoipa-swagger-ui = "*"
---todos-utoipa/src---
---todos-utoipa/src/main.mdx---
use std::sync::{Arc, LazyLock};
use salvo::affix_state;
use salvo::http::header::{self, HeaderValue};
use salvo::http::response::ResBody;
use salvo::prelude::*;
use salvo::size_limiter;
use self::models::*;
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
use utoipa::{Modify, OpenApi};
use utoipa_swagger_ui::Config;
static STORE: LazyLock<Db> = LazyLock::new(new_store);
#[handler]
async fn hello(res: &mut Response) {
res.render("Hello");
}
#[derive(OpenApi)]
#[openapi(
paths(
list_todos,
create_todo,
delete_todo,
update_todo,
),
components(
schemas(models::Todo, models::TodoError)
),
modifiers(&SecurityAddon),
tags(
(name = "todo", description = "Todo items management endpoints.")
)
)]
struct ApiDoc;
struct SecurityAddon;
impl Modify for SecurityAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
let components = openapi.components.as_mut().unwrap(); // we can unwrap safely since there already is components registered.
components.add_security_scheme(
"api_key",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("todo_apikey"))),
)
}
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(route()).await;
}
pub(crate) fn route() -> Router {
let config = Arc::new(Config::from("/api-doc/openapi.json"));
Router::new()
.get(hello)
.push(
Router::with_path("api").push(
Router::with_path("todos")
.hoop(size_limiter::max_size(1024 * 16))
.get(list_todos)
.post(create_todo)
.push(
Router::with_path("{id}")
.put(update_todo)
.delete(delete_todo),
),
),
)
.push(Router::with_path("/api-doc/openapi.json").get(openapi_json))
.push(
Router::with_path("/swagger-ui/{**}")
.hoop(affix_state::inject(config))
.get(serve_swagger),
)
}
#[handler]
pub async fn openapi_json(res: &mut Response) {
res.render(Json(ApiDoc::openapi()))
}
#[handler]
pub async fn serve_swagger(req: &mut Request, depot: &mut Depot, res: &mut Response) {
let config = depot.obtain::<Arc<Config>>().unwrap();
let path = req.uri().path();
let tail = path.strip_prefix("/swagger-ui/").unwrap();
match utoipa_swagger_ui::serve(tail, config.clone()) {
Ok(swagger_file) => swagger_file
.map(|file| {
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_str(&file.content_type).unwrap(),
);
res.body(ResBody::Once(file.bytes.to_vec().into()));
})
.unwrap_or_else(|| {
res.status_code(StatusCode::NOT_FOUND);
}),
Err(_error) => {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
}
}
}
#[utoipa::path(
get,
path = "/api/todos",
responses(
(status = 200, description = "List all todos successfully", body = [Todo])
)
)]
#[handler]
pub async fn list_todos(req: &mut Request, res: &mut Response) {
let opts = req.parse_body::<ListOptions>().await.unwrap_or_default();
let todos = STORE.lock().await;
let todos: Vec<Todo> = todos
.clone()
.into_iter()
.skip(opts.offset.unwrap_or(0))
.take(opts.limit.unwrap_or(usize::MAX))
.collect();
res.render(Json(todos));
}
#[utoipa::path(
post,
path = "/api/todos",
request_body = Todo,
responses(
(status = 201, description = "Todo created successfully", body = Todo),
(status = 409, description = "Todo already exists", body = TodoError, example = json!(TodoError::Config(String::from("id = 1"))))
)
)]
#[handler]
pub async fn create_todo(req: &mut Request, res: &mut Response) {
let new_todo = req.parse_body::<Todo>().await.unwrap();
tracing::debug!(todo = ?new_todo, "create todo");
let mut vec = STORE.lock().await;
for todo in vec.iter() {
if todo.id == new_todo.id {
tracing::debug!(id = ?new_todo.id, "todo already exists");
res.status_code(StatusCode::BAD_REQUEST);
return;
}
}
vec.push(new_todo);
res.status_code(StatusCode::CREATED);
}
#[utoipa::path(
put,
path = "/api/todos/{id}",
responses(
(status = 200, description = "Todo modified successfully"),
(status = 404, description = "Todo not found", body = TodoError, example = json!(TodoError::NotFound(String::from("id = 1"))))
),
params(
("id" = i32, Path, description = "Id of todo item to modify")
)
)]
#[handler]
pub async fn update_todo(req: &mut Request, res: &mut Response) {
let id = req.param::<u64>("id").unwrap();
let updated_todo = req.parse_body::<Todo>().await.unwrap();
tracing::debug!(todo = ?updated_todo, id = ?id, "update todo");
let mut vec = STORE.lock().await;
for todo in vec.iter_mut() {
if todo.id == id {
*todo = updated_todo;
res.status_code(StatusCode::OK);
return;
}
}
tracing::debug!(id = ?id, "todo is not found");
res.status_code(StatusCode::NOT_FOUND);
}
#[utoipa::path(
delete,
path = "/api/todos/{id}",
responses(
(status = 200, description = "Todo deleted successfully"),
(status = 401, description = "Unauthorized to delete Todo"),
(status = 404, description = "Todo not found", body = TodoError, example = json!(TodoError::NotFound(String::from("id = 1"))))
),
params(
("id" = i32, Path, description = "Id of todo item to delete")
),
security(
("api_key" = [])
)
)]
#[handler]
pub async fn delete_todo(req: &mut Request, res: &mut Response) {
let id = req.param::<u64>("id").unwrap();
tracing::debug!(id = ?id, "delete todo");
let mut vec = STORE.lock().await;
let len = vec.len();
vec.retain(|todo| todo.id != id);
let deleted = vec.len() != len;
if deleted {
res.status_code(StatusCode::NO_CONTENT);
} else {
tracing::debug!(id = ?id, "todo is not found");
res.status_code(StatusCode::NOT_FOUND);
}
}
mod models {
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use utoipa::ToSchema;
pub type Db = Mutex<Vec<Todo>>;
pub fn new_store() -> Db {
Mutex::new(Vec::new())
}
#[derive(Serialize, Deserialize, ToSchema)]
pub(super) enum TodoError {
/// Happens when Todo item already exists
Config(String),
/// Todo not found from storage
NotFound(String),
}
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)]
pub struct Todo {
#[schema(example = 1)]
pub id: u64,
#[schema(example = "Buy coffee")]
pub text: String,
pub completed: bool,
}
#[derive(Deserialize, Debug, Default)]
pub struct ListOptions {
pub offset: Option<usize>,
pub limit: Option<usize>,
}
}
#[cfg(test)]
mod tests {
use salvo::http::StatusCode;
use salvo::test::TestClient;
use super::models::Todo;
#[tokio::test]
async fn test_todo_create() {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let res = TestClient::post("http://0.0.0.0:5800/api/todos")
.json(&test_todo())
.send(super::route())
.await;
assert_eq!(res.status_code.unwrap(), StatusCode::CREATED);
let res = TestClient::post("http://0.0.0.0:5800/api/todos")
.json(&test_todo())
.send(super::route())
.await;
assert_eq!(res.status_code.unwrap(), StatusCode::BAD_REQUEST);
}
fn test_todo() -> Todo {
Todo {
id: 1,
text: "test todo".into(),
completed: false,
}
}
}
---catch-panic---
---catch-panic/Cargo.mdx---
[package]
name = "example-catch-panic"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
salvo = { version = "0.77.1", features=["catch-panic"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---catch-panic/src---
---catch-panic/src/main.mdx---
use salvo::prelude::*;
// Handler that deliberately panics to demonstrate panic catching
#[handler]
async fn hello() {
panic!("panic error!");
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Set up router with CatchPanic middleware to handle panics gracefully
// This prevents the server from crashing when a panic occurs in a handler
let router = Router::new().hoop(CatchPanic::new()).get(hello);
// Bind server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---proxy-react-app---
---proxy-react-app/react-app---
---proxy-react-app/react-app/public---
---proxy-react-app/react-app/public/index.mdx---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#000000">
<meta
name="description"
content="Web site created using create-react-app"
>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
---proxy-react-app/react-app/public/robots.mdx---
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
---proxy-react-app/react-app/src---
---proxy-react-app/react-app/src/App.test.mdx---
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
---proxy-react-app/react-app/src/setupTests.mdx---
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
---proxy-react-app/react-app/src/index.mdx---
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
---proxy-react-app/react-app/src/App.mdx---
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
---proxy-react-app/react-app/src/reportWebVitals.mdx---
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
---proxy-react-app/Cargo.mdx---
[package]
name = "example-proxy-react-app"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["proxy"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---proxy-react-app/src---
---proxy-react-app/src/main.mdx---
use salvo::prelude::*;
use salvo::proxy::HyperClient;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{**rest}").goal(Proxy::new(
vec!["http://localhost:3000"],
HyperClient::default(),
));
println!("{:?}", router);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---proxy-websocket---
---proxy-websocket/Cargo.mdx---
[package]
name = "example-proxy-websocket"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["proxy"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---proxy-websocket/src---
---proxy-websocket/src/main.mdx---
use salvo::prelude::*;
use salvo::proxy::HyperClient;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{**rest}").goal(Proxy::new(
vec!["http://localhost:5800"],
HyperClient::default(),
));
println!("{:?}", router);
tracing::info!("Run `cargo run --bin example-websocket-chat` to start websocket chat server");
let acceptor = TcpListener::new("0.0.0.0:8888").bind().await;
Server::new(acceptor).serve(router).await;
}
---timeout---
---timeout/Cargo.mdx---
[package]
name = "example-timeout"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["timeout"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---timeout/src---
---timeout/src/main.mdx---
use std::time::Duration;
use salvo::prelude::*;
#[handler]
async fn fast() -> &'static str {
"hello"
}
#[handler]
async fn slow() -> &'static str {
tokio::time::sleep(Duration::from_secs(6)).await;
"hello"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
let router = Router::new()
.hoop(Timeout::new(Duration::from_secs(5)))
.push(Router::with_path("slow").get(slow))
.push(Router::with_path("fast").get(fast));
Server::new(acceptor).serve(router).await;
}
---tls-openssl-reload---
---tls-openssl-reload/certs---
---tls-openssl-reload/Cargo.mdx---
[package]
name = "example-tls-openssl-reload"
version = "0.1.0"
edition = "2024"
[dependencies]
async-stream.workspace = true
salvo = { version = "0.77.1", features = ["openssl"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---tls-openssl-reload/src---
---tls-openssl-reload/src/main.mdx---
use salvo::conn::openssl::{Keycert, OpensslConfig};
use salvo::prelude::*;
use tokio::time::Duration;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello World"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let acceptor = TcpListener::new("0.0.0.0:5800")
.openssl(async_stream::stream! {
loop {
yield load_config();
tokio::time::sleep(Duration::from_secs(60)).await;
}
})
.bind()
.await;
Server::new(acceptor).serve(router).await;
}
fn load_config() -> OpensslConfig {
OpensslConfig::new(
Keycert::new()
.with_cert(include_bytes!("../certs/cert.pem").as_ref())
.with_key(include_bytes!("../certs/key.pem").as_ref()),
)
}
---proxy-simple---
---proxy-simple/Cargo.mdx---
[package]
name = "example-proxy-simple"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["proxy"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---proxy-simple/src---
---proxy-simple/src/main.mdx---
use salvo::prelude::*;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
// In this example, if the requested URL begins with <http://127.0.0.1:5800/>, the proxy goes to
// <https://www.rust-lang.org>; if the requested URL begins with <http://localhost:5800/>, the proxy
// goes to <https://crates.io>.
let router = Router::new()
.push(
Router::new()
.host("127.0.0.1")
.path("{**rest}")
.goal(Proxy::use_hyper_client("https://www.rust-lang.org")),
)
.push(
Router::new()
.host("localhost")
.path("{**rest}")
.goal(Proxy::use_hyper_client("https://crates.io")),
);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---middleware-add-header---
---middleware-add-header/Cargo.mdx---
[package]
name = "example-middleware-add-header"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---middleware-add-header/src---
---middleware-add-header/src/main.mdx---
use salvo::http::header::{self, HeaderValue};
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[handler]
async fn add_header(res: &mut Response) {
res.headers_mut()
.insert(header::SERVER, HeaderValue::from_static("Salvo"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().hoop(add_header).get(hello);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---custom-error-page---
---custom-error-page/Cargo.mdx---
[package]
name = "example-custom-error-page"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---custom-error-page/src---
---custom-error-page/src/main.mdx---
use salvo::catcher::Catcher;
use salvo::prelude::*;
// Handler that returns a simple "Hello World" response
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
// Handler that deliberately returns a 500 Internal Server Error
#[handler]
async fn error500(res: &mut Response) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Create and start server with custom error handling
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(create_service()).await;
}
// Create service with custom error handling
fn create_service() -> Service {
// Set up router with two endpoints:
// - / : Returns "Hello World"
// - /500 : Triggers a 500 error
let router = Router::new()
.get(hello)
.push(Router::with_path("500").get(error500));
// Add custom error catcher for 404 Not Found errors
Service::new(router).catcher(Catcher::default().hoop(handle404))
}
// Custom handler for 404 Not Found errors
#[handler]
async fn handle404(&self, _req: &Request, _depot: &Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
// Check if the error is a 404 Not Found
if StatusCode::NOT_FOUND == res.status_code.unwrap_or(StatusCode::NOT_FOUND) {
// Return custom error page
res.render("Custom 404 Error Page");
// Skip remaining error handlers
ctrl.skip_rest();
}
}
---hello-h3---
---hello-h3/certs---
---hello-h3/Cargo.mdx---
[package]
name = "example-hello-h3"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["quinn"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---hello-h3/src---
---hello-h3/src/main.mdx---
use salvo::conn::rustls::{Keycert, RustlsConfig};
use salvo::prelude::*;
// Handler function responding with "Hello World" for HTTP/3 requests
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Load TLS certificate and private key from embedded PEM files
let cert = include_bytes!("../certs/cert.pem").to_vec();
let key = include_bytes!("../certs/key.pem").to_vec();
// Create router with single endpoint
let router = Router::new().get(hello);
// Configure TLS settings using Rustls
let config = RustlsConfig::new(Keycert::new().cert(cert.as_slice()).key(key.as_slice()));
// Create TCP listener with TLS encryption on port 5800
let listener = TcpListener::new(("0.0.0.0", 5800)).rustls(config.clone());
// Create QUIC listener and combine with TCP listener
let acceptor = QuinnListener::new(config.build_quinn_config().unwrap(), ("0.0.0.0", 5800))
.join(listener)
.bind()
.await;
// Start server supporting both HTTP/3 (QUIC) and HTTPS (TCP)
Server::new(acceptor).serve(router).await;
}
---with-sentry---
---with-sentry/Cargo.mdx---
[package]
name = "example-with-sentry"
version = "0.1.0"
edition = "2024"
[dependencies]
sentry = { version = "0.34.0", features = ["tower"] }
sentry-tower = { version = "0.34.0", features = ["http"] }
sentry-tracing = "0.34.0"
tokio = { version = "1", features = ["macros"] }
salvo = { version = "0.77.1", features = ["tower-compat"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---with-sentry/src---
---with-sentry/src/main.mdx---
use salvo::prelude::*;
use tracing_subscriber::prelude::*;
fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(sentry_tracing::layer())
.init();
let _sentry;
if let Ok(sentry_dsn) = std::env::var("SENTRY_DSN") {
_sentry = sentry::init((
sentry_dsn,
sentry::ClientOptions {
release: sentry::release_name!(),
traces_sample_rate: 1.0,
..Default::default()
},
));
}
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(http());
}
async fn http() {
let router = Router::new()
.hoop(sentry_tower::NewSentryLayer::new_from_top().compat())
.hoop(sentry_tower::SentryHttpLayer::with_transaction().compat());
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
---flash-cookie-store---
---flash-cookie-store/Cargo.mdx---
[package]
name = "example-flash-cookie-store"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["flash"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---flash-cookie-store/src---
---flash-cookie-store/src/main.mdx---
use std::fmt::Write;
use salvo::flash::{CookieStore, FlashDepotExt};
use salvo::prelude::*;
#[handler]
pub async fn set_flash(depot: &mut Depot, res: &mut Response) {
let flash = depot.outgoing_flash_mut();
flash.info("Hey there!").debug("How is it going?");
res.render(Redirect::other("/get"));
}
#[handler]
pub async fn get_flash(depot: &mut Depot, _res: &mut Response) -> String {
let mut body = String::new();
if let Some(flash) = depot.incoming_flash() {
for message in flash.iter() {
writeln!(body, "{} - {}", message.value, message.level).unwrap();
}
}
body
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new()
.hoop(CookieStore::new().into_handler())
.push(Router::with_path("get").get(get_flash))
.push(Router::with_path("set").get(set_flash));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---template-askama---
---template-askama/templates---
---template-askama/templates/hello.mdx---
Hello, {{ name }}!
---template-askama/Cargo.mdx---
[package]
name = "example-template-askama"
version = "0.1.0"
edition = "2024"
[dependencies]
askama = "0.11"
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---template-askama/src---
---template-askama/src/main.mdx---
use askama::Template;
use salvo::prelude::*;
#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate<'a> {
name: &'a str,
}
#[handler]
async fn hello(req: &mut Request, res: &mut Response) {
let hello_tmpl = HelloTemplate {
name: req.param::<&str>("name").unwrap_or("World"),
};
res.render(Text::Html(hello_tmpl.render().unwrap()));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{name}").get(hello);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---extract-data---
---extract-data/Cargo.mdx---
[package]
name = "example-extract-data"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---extract-data/src---
---extract-data/src/main.mdx---
use salvo::macros::Extractible;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
#[handler]
async fn show(req: &mut Request, res: &mut Response) {
let content = format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>Parse data</title>
</head>
<body>
<h1>Hello, fill your profile</h1>
<form action="/{}?username=jobs" method="post">
<label>First Name:</label><input type="text" name="first_name" />
<label>Last Name:</label><input type="text" name="last_name" />
<legend>What is Your Favorite Pet?</legend>
<input type="checkbox" name="lovers" value="Cats">Cats<br>
<input type="checkbox" name="lovers" value="Dogs">Dogs<br>
<input type="checkbox" name="lovers" value="Birds">Birds<br>
<input type="submit" value="Submit" />
</form>
</body>
</html>
"#,
req.params().get("id").unwrap()
);
res.render(Text::Html(content));
}
#[handler]
async fn edit(req: &mut Request) -> String {
let bad_man: BadMan = req.extract().await.unwrap();
let bad_man = format!("Bad Man: {bad_man:#?}");
let good_man: GoodMan = req.extract().await.unwrap();
let good_man = format!("Good Man: {good_man:#?}");
format!("{bad_man}\r\n\r\n\r\n{good_man}")
}
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(
default_source(from = "query"),
default_source(from = "param"),
default_source(from = "body")
))]
struct BadMan<'a> {
#[serde(default)]
id: i64,
username: &'a str,
first_name: String,
last_name: &'a str,
lovers: Vec<String>,
}
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(
default_source(from = "query"),
default_source(from = "param"),
default_source(from = "body"),
))]
struct GoodMan<'a> {
#[serde(default)]
id: i64,
#[serde(default)]
username: &'a str,
first_name: String,
last_name: &'a str,
#[salvo(extract(alias = "lovers"))]
lover: &'a str,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{id}").get(show).post(edit);
println!("Example url: http://0.0.0.0:5800/95");
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---with-listenfd---
---with-listenfd/Cargo.mdx---
[package]
name = "example-with-listenfd"
version = "0.1.0"
edition = "2024"
[dependencies]
listenfd = "*"
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---with-listenfd/src---
---with-listenfd/src/main.mdx---
use std::net::SocketAddr;
use listenfd::ListenFd;
use salvo::conn::tcp::TcpAcceptor;
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() -> Result<(), salvo::Error> {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let mut listenfd = ListenFd::from_env();
let (addr, listener) = if let Some(listener) = listenfd.take_tcp_listener(0)? {
listener.set_nonblocking(true)?;
(
listener.local_addr()?,
tokio::net::TcpListener::from_std(listener).unwrap(),
)
} else {
let addr: SocketAddr = format!(
"{}:{}",
std::env::var("HOST").unwrap_or("0.0.0.0".into()),
std::env::var("PORT").unwrap_or("8080".into())
)
.parse()
.unwrap();
(addr, tokio::net::TcpListener::bind(addr).await.unwrap())
};
tracing::info!("Listening on {}", addr);
let acceptor = TcpAcceptor::try_from(listener).unwrap();
Server::new(acceptor).serve(router).await;
Ok(())
}
---multi-servers---
---multi-servers/Cargo.mdx---
[package]
name = "example-multi-servers"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---multi-servers/src---
---multi-servers/src/main.mdx---
use salvo::prelude::*;
#[handler]
async fn hello1() -> &'static str {
"Server1: Hello World"
}
#[handler]
async fn hello2() -> &'static str {
"Server2: Hello World"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router1 = Router::new().get(hello1);
let router2 = Router::new().get(hello2);
tokio::try_join!(
Server::new(TcpListener::new("0.0.0.0:5800").bind().await).try_serve(router1),
Server::new(TcpListener::new("0.0.0.0:5801").bind().await).try_serve(router2),
)
.unwrap();
}
---fuse-attack---
---fuse-attack/Cargo.mdx---
[package]
name = "example-fuse-attack"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---fuse-attack/src---
---fuse-attack/src/main.mdx---
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[handler]
async fn hello_zh() -> Result<&'static str, ()> {
Ok("你好,世界!")
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
let router = Router::new()
.get(hello)
.push(Router::with_path("你好").get(hello_zh));
println!("{:?}", router);
Server::new(acceptor)
.fuse_factory(salvo::fuse::flex::FlexFactory::new())
.serve(router)
.await;
}
---session-login---
---session-login/Cargo.mdx---
[package]
name = "example-session-login"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features=["session"] }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---session-login/src---
---session-login/src/main.mdx---
use salvo::prelude::*;
use salvo::session::{CookieStore, Session, SessionDepotExt, SessionHandler};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
let router = Router::new()
.hoop(session_handler)
.get(home)
.push(Router::with_path("login").get(login).post(login))
.push(Router::with_path("logout").get(logout));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
#[handler]
pub async fn login(req: &mut Request, depot: &mut Depot, res: &mut Response) {
if req.method() == salvo::http::Method::POST {
let mut session = Session::new();
session
.insert("username", req.form::<String>("username").await.unwrap())
.unwrap();
depot.set_session(session);
res.render(Redirect::other("/"));
} else {
res.render(Text::Html(LOGIN_HTML));
}
}
#[handler]
pub async fn logout(depot: &mut Depot, res: &mut Response) {
if let Some(session) = depot.session_mut() {
session.remove("username");
}
res.render(Redirect::other("/"));
}
#[handler]
pub async fn home(depot: &mut Depot, res: &mut Response) {
let mut content = r#"<a href="login">Login</h1>"#.into();
if let Some(session) = depot.session_mut() {
if let Some(username) = session.get::<String>("username") {
content = format!(r#"Hello, {username}. <br><a href="logout">Logout</h1>"#);
}
}
res.render(Text::Html(content));
}
static LOGIN_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
<h1>Login</h1>
<input type="text" name="username" />
<button type="submit" id="submit">Submit</button>
</form>
</body>
</html>
"#;
---acme-http01---
---acme-http01/Cargo.mdx---
[package]
name = "example-acme-http01"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["acme"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---acme-http01/src---
---acme-http01/src/main.mdx---
use salvo::prelude::*;
// This handler function responds with "Hello World" to any incoming request
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
// Initialize the tracing subscriber for logging
tracing_subscriber::fmt().init();
// Create a new router and register the hello handler
let mut router = Router::new().get(hello);
// Set up a TCP listener on port 443 for HTTPS
let listener = TcpListener::new("0.0.0.0:443")
.acme() // Enable ACME for automatic SSL certificate management
// Use Let's Encrypt production server by default
// Uncomment the following line to use the staging server for testing purposes
// .directory("letsencrypt", salvo::conn::acme::LETS_ENCRYPT_STAGING)
.cache_path("/temp/letsencrypt") // Specify the path to store the certificate cache
.add_domain("test.salvo.rs") // replace with your domain
.http01_challenge(&mut router); // Add routes to handle ACME challenge requests
// Create an acceptor that listens on both port 80 (HTTP) and port 443 (HTTPS)
let acceptor = listener.join(TcpListener::new("0.0.0.0:80")).bind().await;
// Start the server with the configured acceptor and router
Server::new(acceptor).serve(router).await;
}
---sse-chat---
---sse-chat/Cargo.mdx---
[package]
name = "example-sse-chat"
version = "0.1.0"
edition = "2024"
[dependencies]
futures-util = "0.3"
parking_lot = "0.12"
salvo = { version = "0.77.1", features = ["sse"] }
tokio = { version = "1", features = ["macros"] }
tokio-stream = { version = "0.1", features = ["net"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---sse-chat/src---
---sse-chat/src/main.mdx---
// Copyright (c) 2018-2020 Sean McArthur
// Licensed under the MIT license http://opensource.org/licenses/MIT
//
// port from https://github.com/seanmonstar/warp/blob/master/examples/sse_chat.rs
use std::collections::HashMap;
use std::sync::LazyLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use futures_util::StreamExt;
use parking_lot::Mutex;
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use salvo::prelude::*;
use salvo::sse::{SseEvent, SseKeepAlive};
type Users = Mutex<HashMap<usize, mpsc::UnboundedSender<Message>>>;
static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1);
static ONLINE_USERS: LazyLock<Users> = LazyLock::new(Users::default);
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().goal(index).push(
Router::with_path("chat")
.get(user_connected)
.push(Router::with_path("{id}").post(chat_send)),
);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
#[derive(Debug)]
enum Message {
UserId(usize),
Reply(String),
}
#[handler]
async fn chat_send(req: &mut Request, res: &mut Response) {
let my_id = req.param::<usize>("id").unwrap();
let msg = std::str::from_utf8(req.payload().await.unwrap()).unwrap();
user_message(my_id, msg);
res.status_code(StatusCode::OK);
}
#[handler]
async fn user_connected(res: &mut Response) {
// Use a counter to assign a new unique ID for this user.
let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed);
tracing::info!("new chat user: {}", my_id);
// Use an unbounded channel to handle buffering and flushing of messages
// to the event source...
let (tx, rx) = mpsc::unbounded_channel();
let rx = UnboundedReceiverStream::new(rx);
tx.send(Message::UserId(my_id))
// rx is right above, so this cannot fail
.unwrap();
// Save the sender in our list of connected users.
ONLINE_USERS.lock().insert(my_id, tx);
// Convert messages into Server-Sent Events and returns resulting stream.
let stream = rx.map(|msg| match msg {
Message::UserId(my_id) => {
Ok::<_, salvo::Error>(SseEvent::default().name("user").text(my_id.to_string()))
}
Message::Reply(reply) => Ok(SseEvent::default().text(reply)),
});
SseKeepAlive::new(stream).stream(res);
}
fn user_message(my_id: usize, msg: &str) {
let new_msg = format!("<User#{my_id}>: {msg}");
// New message from this user, send it to everyone else (except same uid)...
//
// We use `retain` instead of a for loop so that we can reap any user that
// appears to have disconnected.
ONLINE_USERS.lock().retain(|uid, tx| {
if my_id == *uid {
// don't send to same user, but do retain
true
} else {
// If not `is_ok`, the SSE stream is gone, and so don't retain
tx.send(Message::Reply(new_msg.clone())).is_ok()
}
});
}
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
static INDEX_HTML: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>SSE Chat</title>
</head>
<body>
<h1>SSE Chat</h1>
<div id="chat">
<p><em>Connecting...</em></p>
</div>
<input type="text" id="msg" />
<button type="button" id="submit">Send</button>
<script>
const chat = document.getElementById('chat');
const msg = document.getElementById('msg');
const submit = document.getElementById('submit');
let sse = new EventSource(`http://${location.host}/chat`);
sse.onopen = function() {
chat.innerHTML = "<p><em>Connected!</em></p>";
}
let userId;
sse.addEventListener("user", function(msg) {
userId = msg.data;
});
sse.onmessage = function(msg) {
showMessage(msg.data);
};
document.getElementById('submit').onclick = function() {
var txt = msg.value;
var xhr = new XMLHttpRequest();
xhr.open("POST", `http://${window.location.host}/chat/${userId}`, true);
xhr.send(txt);
msg.value = '';
showMessage('<You>: ' + txt);
};
function showMessage(data) {
const line = document.createElement('p');
line.innerText = data;
chat.appendChild(line);
}
</script>
</body>
</html>
"#;
---db-postgres-sqlx---
---db-postgres-sqlx/Cargo.mdx---
[package]
name = "example-db-postgres-sqlx"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
sqlx = { workspace = true, features = [ "runtime-tokio-native-tls" , "postgres" ] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
---db-postgres-sqlx/src---
---db-postgres-sqlx/src/main.mdx---
use std::sync::OnceLock;
use salvo::prelude::*;
use serde::Serialize;
use sqlx::{FromRow, PgPool};
// Global PostgreSQL connection pool instance
static POSTGRES: OnceLock<PgPool> = OnceLock::new();
// Helper function to get the PostgreSQL connection pool
#[inline]
pub fn get_postgres() -> &'static PgPool {
POSTGRES.get().unwrap()
}
// User model representing the database table structure
// Implements FromRow for SQL query results and Serialize for JSON responses
#[derive(FromRow, Serialize, Debug)]
pub struct User {
pub id: i64,
pub username: String,
pub password: String,
}
// Handler for retrieving a user by ID from the database
#[handler]
pub async fn get_user(req: &mut Request, res: &mut Response) {
// Extract user ID from query parameters
let uid = req.query::<i64>("uid").unwrap();
// Execute SQL query to fetch user by ID
let data = sqlx::query_as::<_, User>("select * from users where id = $1")
.bind(uid)
.fetch_one(get_postgres())
.await
.unwrap();
// Return user data as JSON response
res.render(serde_json::to_string(&data).unwrap());
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Configure PostgreSQL connection
let postgres_uri = "postgres://postgres:password@localhost/test";
// Create and initialize connection pool
let pool = PgPool::connect(postgres_uri).await.unwrap();
// Store pool in global state
POSTGRES.set(pool).unwrap();
// Configure router with user endpoint:
// - GET /users?uid={id} : Get user by ID
let router = Router::with_path("users").get(get_user);
// Start server on port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---db-sea-orm---
---db-sea-orm/static---
---db-sea-orm/static/css---
---db-sea-orm/static/images---
---db-sea-orm/templates---
---db-sea-orm/templates/error---
---db-sea-orm/Cargo.mdx---
[package]
name = "example-db-sea-orm"
version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
salvo = { version = "0.77.1", features = ["affix-state", "serve-static"] }
tracing .workspace = true
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
serde = { version = "1", features = ["derive"] }
tera .workspace = true
sea-orm = { workspace = true, features = [
"debug-print",
"runtime-tokio-native-tls",
"sqlx-sqlite",
# "sqlx-postgres",
# "sqlx-mysql",
] }
async-std = { workspace = true, features = ["attributes", "tokio1"] }
sea-orm-migration .workspace = true
---db-sea-orm/src---
---db-sea-orm/src/entity---
---db-sea-orm/src/entity/post.mdx---
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
// Blog post model representing the database table structure
// Implements various traits for ORM functionality and serialization
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)]
#[sea_orm(table_name = "posts")]
pub struct Model {
// Primary key field with auto-increment
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
// Post title field
pub title: String,
// Post content field using Text type for longer content
#[sea_orm(column_type = "Text")]
pub text: String,
}
// Define possible relations with other entities
// Currently empty as this is a standalone model
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
// Implement default active model behavior
// This enables standard CRUD operations
impl ActiveModelBehavior for ActiveModel {}
---db-sea-orm/src/entity/mod.mdx---
pub mod post;
---db-sea-orm/src/main.mdx---
use std::env;
use entity::post;
use migration::{Migrator, MigratorTrait};
use salvo::prelude::*;
use salvo::serve_static::StaticDir;
use salvo::writing::Text;
use sea_orm::{DatabaseConnection, entity::*, query::*};
use tera::Tera;
mod entity;
mod migration;
// Default number of posts to display per page
const DEFAULT_POSTS_PER_PAGE: u64 = 5;
type Result<T> = std::result::Result<T, StatusError>;
// Application state containing database connection and template engine
#[derive(Clone, Debug)]
struct AppState {
templates: tera::Tera,
conn: DatabaseConnection,
}
// Handler for creating a new blog post
#[handler]
async fn create(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> {
let state = depot
.obtain::<AppState>()
.map_err(|_| StatusError::internal_server_error())?;
// Parse form data into post model
let form = req
.parse_form::<post::Model>()
.await
.map_err(|_| StatusError::bad_request())?;
// Create new post in database
post::ActiveModel {
title: Set(form.title.to_owned()),
text: Set(form.text.to_owned()),
..Default::default()
}
.save(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?;
res.render(Redirect::found("/"));
Ok(())
}
// Handler for listing blog posts with pagination
#[handler]
async fn list(req: &mut Request, depot: &mut Depot) -> Result<Text<String>> {
let state = depot
.obtain::<AppState>()
.map_err(|_| StatusError::internal_server_error())?;
// Get pagination parameters from query
let page = req.query("page").unwrap_or(1);
let posts_per_page = req
.query("posts_per_page")
.unwrap_or(DEFAULT_POSTS_PER_PAGE);
// Create paginator for posts
let paginator = post::Entity::find()
.order_by_asc(post::Column::Id)
.paginate(&state.conn, posts_per_page);
// Get total number of pages
let num_pages = paginator
.num_pages()
.await
.map_err(|_| StatusError::bad_request())?;
// Get posts for current page
let posts = paginator
.fetch_page(page - 1)
.await
.map_err(|_| StatusError::internal_server_error())?;
// Render template with posts and pagination data
let mut ctx = tera::Context::new();
ctx.insert("posts", &posts);
ctx.insert("page", &page);
ctx.insert("posts_per_page", &posts_per_page);
ctx.insert("num_pages", &num_pages);
let body = state
.templates
.render("index.html.tera", &ctx)
.map_err(|_| StatusError::internal_server_error())?;
Ok(Text::Html(body))
}
// Handler for displaying new post form
#[handler]
async fn new(depot: &mut Depot) -> Result<Text<String>> {
let state = depot
.obtain::<AppState>()
.map_err(|_| StatusError::internal_server_error())?;
let ctx = tera::Context::new();
let body = state
.templates
.render("new.html.tera", &ctx)
.map_err(|_| StatusError::internal_server_error())?;
Ok(Text::Html(body))
}
// Handler for displaying edit post form
#[handler]
async fn edit(req: &mut Request, depot: &mut Depot) -> Result<Text<String>> {
let state = depot
.obtain::<AppState>()
.map_err(|_| StatusError::internal_server_error())?;
// Get post ID from path parameters
let id = req.param::<i32>("id").unwrap_or_default();
// Find post in database
let post: post::Model = post::Entity::find_by_id(id)
.one(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?
.ok_or_else(StatusError::not_found)?;
// Render edit form with post data
let mut ctx = tera::Context::new();
ctx.insert("post", &post);
let body = state
.templates
.render("edit.html.tera", &ctx)
.map_err(|_| StatusError::internal_server_error())?;
Ok(Text::Html(body))
}
// Handler for updating an existing post
#[handler]
async fn update(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> {
let state = depot
.obtain::<AppState>()
.map_err(|_| StatusError::internal_server_error())?;
// Get post ID and form data
let id = req.param::<i32>("id").unwrap_or_default();
let form = req
.parse_form::<post::Model>()
.await
.map_err(|_| StatusError::bad_request())?;
// Update post in database
post::ActiveModel {
id: Set(id),
title: Set(form.title.to_owned()),
text: Set(form.text.to_owned()),
}
.save(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?;
res.render(Redirect::found("/"));
Ok(())
}
// Handler for deleting a post
#[handler]
async fn delete(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> {
let state = depot
.obtain::<AppState>()
.map_err(|_| StatusError::internal_server_error())?;
// Get post ID and find post
let id = req.param::<i32>("id").unwrap_or_default();
let post: post::ActiveModel = post::Entity::find_by_id(id)
.one(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?
.ok_or_else(StatusError::not_found)?
.into();
// Delete post from database
post.delete(&state.conn)
.await
.map_err(|_| StatusError::internal_server_error())?;
res.render(Redirect::found("/"));
Ok(())
}
#[tokio::main]
async fn main() {
// Initialize logging
tracing_subscriber::fmt::init();
// Database and server configuration
let db_url = "sqlite::memory:";
let server_url = "0.0.0.0:5800";
// create post table if not exists
let conn = sea_orm::Database::connect(db_url).await.unwrap();
Migrator::up(&conn, None).await.unwrap();
// Initialize template engine
let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
let state = AppState { templates, conn };
println!("Starting server at {server_url}");
// Configure router with all handlers
let router = Router::new()
.hoop(affix_state::inject(state))
.post(create)
.get(list)
.push(Router::with_path("new").get(new))
.push(Router::with_path("{id}").get(edit).post(update))
.push(Router::with_path("delete/{id}").post(delete))
.push(Router::with_path("static/{**}").get(StaticDir::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static"
))));
// Start server
let acceptor = TcpListener::new(&server_url).bind().await;
Server::new(acceptor).serve(router).await;
}
---db-sea-orm/src/migration---
---db-sea-orm/src/migration/m20220120_000001_create_post_table.mdx---
use sea_orm_migration::prelude::*;
// Migration struct for creating posts table
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
// Up migration: Creates the posts table
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Posts::Table)
.if_not_exists()
// Define id column as auto-incrementing primary key
.col(
ColumnDef::new(Posts::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
// Define title column as non-null string
.col(ColumnDef::new(Posts::Title).string().not_null())
// Define text column as non-null string
.col(ColumnDef::new(Posts::Text).string().not_null())
.to_owned(),
)
.await
}
// Down migration: Drops the posts table
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Posts::Table).to_owned())
.await
}
}
// Enum representing table and column names
// Used for type-safe table/column name references
#[derive(Iden)]
enum Posts {
Table, // Represents the table name
Id, // Represents the id column
Title, // Represents the title column
Text, // Represents the text column
}
---db-sea-orm/src/migration/main.mdx---
use sea_orm_migration::prelude::*;
// Entry point for running database migrations via CLI
#[async_std::main]
async fn main() {
// Run the migration CLI with our Migrator
cli::run_cli(migration::Migrator).await;
}
---db-sea-orm/src/migration/mod.mdx---
pub use sea_orm_migration::prelude::*;
// Import the migration for creating posts table
mod m20220120_000001_create_post_table;
// Main migrator struct that manages all migrations
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
// Returns a list of all migrations in order
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220120_000001_create_post_table::Migration)]
}
}
---with-tower---
---with-tower/Cargo.mdx---
[package]
name = "example-with-tower"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["tower-compat"] }
tokio = { version = "1", features = ["macros"] }
tower = { version = "0.4", features = ["limit"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---with-tower/src---
---with-tower/src/main.mdx---
use salvo::prelude::*;
use tokio::time::Duration;
use tower::limit::RateLimitLayer;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let limit = RateLimitLayer::new(5, Duration::from_secs(30)).compat();
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
let router = Router::new().hoop(limit).get(hello);
Server::new(acceptor).serve(router).await;
}
---upload-file---
---upload-file/Cargo.mdx---
[package]
name = "example-upload-file"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---upload-file/src---
---upload-file/src/main.mdx---
use std::fs::create_dir_all;
use std::path::Path;
use salvo::prelude::*;
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
#[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("file"));
println!("{dest}");
let info = if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
format!("file not found in request: {e}")
} else {
format!("File uploaded to {dest}")
};
res.render(Text::Plain(info));
} else {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Text::Plain("file not found in request"));
};
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
create_dir_all("temp").unwrap();
let router = Router::new().get(index).post(upload);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Upload file</title>
</head>
<body>
<h1>Upload file</h1>
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
</body>
</html>
"#;
---compression---
---compression/static---
---compression/static/ws_chat.mdx---
The Great Tomato Heist
Once upon a time in the quaint town of Veggieville, known for its lush gardens and eccentric residents, a peculiar event unfolded that would go down in the town's history as the "Great Tomato Heist."
It all started when Old Man Jenkins, the town's most prolific tomato grower, discovered one morning that his entire crop of heirloom tomatoes had vanished overnight. The town was abuzz with the news, as Jenkins' tomatoes were legendary for their size, flavor, and the annual tomato-eating contest they inspired.
The mayor, a portly man by the name of Mayor McFlabbergast, called an emergency town meeting to address the issue. "Folks," he began, his voice echoing through the town square, "We are faced with a crisis of the reddest kind. Our tomatoes, the pride of Veggieville, have been stolen!"
The townspeople gasped in unison. Who could have committed such an act? Suspicion fell on everyone from the mischievous Johnson twins to the reclusive Miss Pickle, who was rumored to have a secret stash of pickle recipes that could use a few juicy tomatoes.
Enter Detective Basil, Veggieville's only detective, known for his green thumb and even greener trench coat. He took the stage with a flourish, a sprig of rosemary behind his ear. "Fear not, dear Veggievillians," he proclaimed. "I shall solve this mystery and restore our town's tomato treasure."
For days, Detective Basil interviewed the townsfolk, examined the crime scene, and even consulted with the local wildlife (after all, maybe a raccoon with a taste for tomatoes was the culprit). But the case remained as elusive as a ghost pepper in a snowstorm.
One evening, as the sun set over the town's rooftops, Detective Basil had an epiphany. He rushed to the town's library, dusting off old tomes and maps. "Ah-ha!" he exclaimed, pointing at a faded map that showed a network of underground tunnels beneath Veggieville.
Armed with a flashlight and a sense of adventure, he ventured into the tunnels, following the faint scent of tomato vines. The tunnels led him to a hidden chamber, where he found a sight that would baffle even the most seasoned of detectives: an entire underground greenhouse filled with tomatoes.
In the center of the greenhouse stood a figure, tending to the plants with loving care. It was none other than Old Man Jenkins himself!
"Jenkins?" Detective Basil asked, his voice echoing in the cavernous space. "What on earth—or rather, under it—is going on here?"
Old Man Jenkins chuckled, a twinkle in his eye. "Ah, Detective, I see you've discovered my secret. You see, I've been developing a new variety of tomato, one that's juicier, more flavorful, and... well, let's just say, it's a bit more 'spirited' than the usual."
It turned out that Jenkins had been secretly crossbreeding tomatoes with a rare type of chili pepper, creating a tomato so potent that it could only be grown in the perfect conditions of the underground greenhouse. The "theft" was actually a clever ruse to protect his crop until it was ready to be unveiled.
The townspeople were both relieved and delighted by the news. The Great Tomato Heist was not a crime but a grand experiment in tomato innovation!
And so, the following year, Veggieville held its most epic tomato-eating contest yet, with the new "Spicy Surprise" tomatoes taking center stage. The event was a hit, and the town's legend grew, with tales of the day the tomatoes were thought to be stolen, only to be found thriving in a secret underground world.
And as for Detective Basil? He became the honorary head judge of the tomato-eating contest, forever remembered as the detective who solved the case that wasn't a case at all.
---compression/static/todos.mdx---
The Unbreakable Vase
In the small town of Giggleswick, a local legend was born when Mrs. Pimpleton, known for her clumsiness, found a vase that seemed indestructible. It was said to have survived every accident, from toppling bookcases to a charging bull in a china shop.
One day, the town's mayor, Mr. Fuddlebee, challenged Mrs. Pimpleton to break the vase during the annual Giggleswick Fair. The whole town gathered, betting on whether the vase would finally meet its match.
With a dramatic flourish, Mrs. Pimpleton attempted to smash the vase with a sledgehammer. To everyone's astonishment, the hammer bent, but the vase remained unscathed. The crowd gasped, and laughter erupted as the mayor's face turned as red as the roses in the vase.
It turned out that the vase was made of a rare, experimental material that could withstand almost anything. The inventor, who had been watching from the crowd, stepped forward to claim his creation. The town hailed Mrs. Pimpleton and the unbreakable vase as symbols of Giggleswick's resilience and good humor.
And so, the vase found a permanent home in the town hall, a reminder that sometimes, even in the face of the clumsiest of hands, some things are destined to remain whole.
---compression/static/sse_chat.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---compression/Cargo.mdx---
[package]
name = "example-compression"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["compression", "serve-static"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---compression/src---
---compression/src/main.mdx---
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Print current working directory for debugging
println!("current_dir: {:?}", std::env::current_dir().unwrap());
// Set up base directory for static files
let base_dir = std::env::current_dir()
.unwrap()
.join("compression/static")
.canonicalize()
.unwrap();
println!("Base Dir: {base_dir:?}");
// Configure router with different compression settings for different paths
let router = Router::new()
// WebSocket chat with forced compression priority
.push(
Router::with_hoop(Compression::new().force_priority(true))
.path("ws_chat")
.get(StaticFile::new(base_dir.join("ws_chat.txt"))),
)
// SSE chat with Brotli compression
.push(
Router::with_hoop(Compression::new().enable_brotli(CompressionLevel::Fastest))
.path("sse_chat")
.get(StaticFile::new(base_dir.join("sse_chat.txt"))),
)
// Todos with Zstd compression
.push(
Router::with_hoop(Compression::new().enable_zstd(CompressionLevel::Fastest))
.path("todos")
.get(StaticFile::new(base_dir.join("todos.txt"))),
)
// All other paths with Gzip compression
.push(
Router::with_hoop(Compression::new().enable_gzip(CompressionLevel::Fastest))
.path("{*path}")
.get(StaticDir::new(base_dir)),
);
// Bind server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---cors---
---cors/Cargo.mdx---
[package]
name = "example-cors"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features=["cors"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---cors/src---
---cors/src/main.mdx---
use salvo::cors::Cors;
use salvo::http::Method;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Start both backend and frontend servers concurrently
tokio::join!(backend_server(), frontend_server());
}
async fn backend_server() {
// Handler that returns a simple message for CORS demonstration
#[handler]
async fn hello() -> &'static str {
"hello, I am content from remote server."
}
// Configure CORS middleware with specific settings:
// - Allow requests from localhost:5800
// - Allow specific HTTP methods
// - Allow authorization header
let cors = Cors::new()
.allow_origin(["http://127.0.0.1:5800", "http://localhost:5800"])
.allow_methods(vec![Method::GET, Method::POST, Method::DELETE])
.allow_headers("authorization")
.into_handler();
// Set up backend router with CORS protection
let router = Router::with_path("hello").post(hello);
let service = Service::new(router).hoop(cors);
// Start backend server on port 5600
let acceptor = TcpListener::new("0.0.0.0:5600").bind().await;
Server::new(acceptor).serve(service).await;
}
async fn frontend_server() {
// Handler that serves the HTML page with CORS test
#[handler]
async fn index() -> Text<&'static str> {
Text::Html(HTML_DATA)
}
// Set up frontend router to serve the test page
let router = Router::new().get(index);
// Start frontend server on port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
// HTML template with JavaScript code to test CORS
// Contains a button that triggers a POST request to the backend server
const HTML_DATA: &str = r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Salvo Cors</title>
</head>
<body>
<button id="btn">Load Content</button>
<div id="content"></div>
<script>
document.getElementById("btn").addEventListener("click", function() {
fetch("http://127.0.0.1:5600/hello", {method: "POST", headers: {authorization: "abcdef"}}).then(function(response) {
return response.text();
}).then(function(data) {
document.getElementById("content").innerHTML = data;
});
});
</script>
</body>
</html>
"#;
---redirect---
---redirect/Cargo.mdx---
[package]
name = "example-redirect"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---redirect/src---
---redirect/src/main.mdx---
use salvo::prelude::*;
#[handler]
async fn hello(res: &mut Response) {
res.render(Redirect::found("https://www.rust-lang.org/"))
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
let router = Router::new().get(hello);
Server::new(acceptor).serve(router).await;
}
---jwt-oidc-clerk---
---jwt-oidc-clerk/app---
---jwt-oidc-clerk/app/index.mdx---
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>Clerk + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.jsx"></script>
</body>
</html>
---jwt-oidc-clerk/app/styles---
---jwt-oidc-clerk/app/components---
---jwt-oidc-clerk/app/static---
---jwt-oidc-clerk/app/static/robots.mdx---
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
---jwt-oidc-clerk/vite.config.mdx---
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
root: 'app',
publicDir: 'static',
server: {
port: 5801,
strictPort : true,
},
plugins: [react()],
});
---jwt-oidc-clerk/Cargo.mdx---
[package]
name = "example-jwt-oidc-clerk"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
dotenv = "0.15.0"
jsonwebtoken = "9"
salvo = { version = "0.77.1", features = ["anyhow", "jwt-auth", "proxy"] }
serde = "1"
time = "0.3"
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---jwt-oidc-clerk/src---
---jwt-oidc-clerk/src/main.mdx---
use salvo::http::StatusError;
use salvo::jwt_auth::{HeaderFinder, OidcDecoder};
use salvo::prelude::*;
use salvo::proxy::HyperClient;
use serde::{Deserialize, Serialize};
const ISSUER_URL: &str = "https://coherent-gopher-0.clerk.accounts.dev";
#[derive(Debug, Serialize, Deserialize)]
pub struct JwtClaims {
sid: String,
sub: String,
exp: i64,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let decoder = OidcDecoder::new(ISSUER_URL.to_owned()).await.unwrap();
let auth_handler: JwtAuth<JwtClaims, OidcDecoder> = JwtAuth::new(decoder)
.finders(vec![Box::new(HeaderFinder::new())])
.force_passed(true);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
let router = Router::new()
.push(Router::with_hoop(auth_handler).path("welcome").get(welcome))
.push(Router::with_path("{**rest}").goal(Proxy::new(
vec!["http://localhost:5801"],
HyperClient::default(),
)));
Server::new(acceptor).serve(router).await;
}
#[handler]
async fn welcome(depot: &mut Depot) -> Result<String, StatusError> {
match depot.jwt_auth_state() {
JwtAuthState::Authorized => {
let data = depot.jwt_auth_data::<JwtClaims>().unwrap();
Ok(format!(
"Hi {}, have logged in successfully!",
data.claims.sub
))
}
JwtAuthState::Unauthorized => Err(StatusError::unauthorized()),
_ => Err(StatusError::forbidden()),
}
}
---tls-rustls---
---tls-rustls/certs---
---tls-rustls/Cargo.mdx---
[package]
name = "example-tls-rustls"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["rustls"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---tls-rustls/src---
---tls-rustls/src/main.mdx---
use salvo::conn::rustls::{Keycert, RustlsConfig};
use salvo::prelude::*;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello World"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let config = RustlsConfig::new(
Keycert::new()
.cert(include_bytes!("../certs/cert.pem").as_ref())
.key(include_bytes!("../certs/key.pem").as_ref()),
);
let acceptor = TcpListener::new("0.0.0.0:5800").rustls(config).bind().await;
Server::new(acceptor).serve(router).await;
}
---cache-simple---
---cache-simple/Cargo.mdx---
[package]
name = "example-cache-simple"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["cache"] }
tokio = { version = "1", features = ["macros"] }
time = "0.3"
tracing = "0.1"
tracing-subscriber = "0.3"
---cache-simple/src---
---cache-simple/src/main.mdx---
use std::time::Duration;
use salvo::cache::{Cache, MokaStore, RequestIssuer};
use salvo::prelude::*;
use salvo::writing::Text;
use time::OffsetDateTime;
// Handler for serving the home page with HTML content
#[handler]
async fn home() -> Text<&'static str> {
Text::Html(HOME_HTML)
}
// Handler for short-lived cached response (5 seconds)
#[handler]
async fn short() -> String {
format!(
"Hello World, my birth time is {}",
OffsetDateTime::now_utc()
)
}
// Handler for long-lived cached response (1 minute)
#[handler]
async fn long() -> String {
format!(
"Hello World, my birth time is {}",
OffsetDateTime::now_utc()
)
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Create cache middleware for short-lived responses (5 seconds TTL)
let short_cache = Cache::new(
MokaStore::builder()
.time_to_live(Duration::from_secs(5))
.build(),
RequestIssuer::default(),
);
// Create cache middleware for long-lived responses (60 seconds TTL)
let long_cache = Cache::new(
MokaStore::builder()
.time_to_live(Duration::from_secs(60))
.build(),
RequestIssuer::default(),
);
// Set up router with three endpoints:
// - / : Home page
// - /short : Response cached for 5 seconds
// - /long : Response cached for 1 minute
let router = Router::new()
.get(home)
.push(Router::with_path("short").hoop(short_cache).get(short))
.push(Router::with_path("long").hoop(long_cache).get(long));
// Bind server to port 5800 and start serving
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
// HTML template for the home page with links to cached endpoints
static HOME_HTML: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>Cache Example</title>
</head>
<body>
<h2>Cache Example</h2>
<p>
This examples shows how to use cache middleware.
</p>
<p>
<a href="/short" target="_blank">Cache 5 seconds</a>
</p>
<p>
<a href="/long" target="_blank">Cache 1 minute</a>
</p>
</body>
</html>
"#;
---extract-nested---
---extract-nested/Cargo.mdx---
[package]
name = "example-extract-nested"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---extract-nested/src---
---extract-nested/src/main.mdx---
use salvo::macros::Extractible;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
#[handler]
async fn show(req: &mut Request, res: &mut Response) {
let content = format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>Parse data</title>
</head>
<body>
<h1>Hello, fill your profile</h1>
<div id="result"></div>
<form id="form" method="post">
<label>First Name:</label><input type="text" name="first_name" />
<label>Last Name:</label><input type="text" name="last_name" />
<legend>What is Your Favorite Pet?</legend>
<input type="checkbox" name="lovers" value="Cats">Cats<br>
<input type="checkbox" name="lovers" value="Dogs">Dogs<br>
<input type="checkbox" name="lovers" value="Birds">Birds<br>
<input type="submit" value="Submit" />
</form>
<script>
let form = document.getElementById("form");
form.addEventListener("submit", async (e) => {{
e.preventDefault();
let response = await fetch('/{}?username=jobs', {{
method: 'POST',
headers: {{
'Content-Type': 'application/json',
}},
body: JSON.stringify({{
first_name: form.querySelector("input[name='first_name']").value,
last_name: form.querySelector("input[name='last_name']").value,
lovers: Array.from(form.querySelectorAll("input[name='lovers']:checked")).map(el => el.value),
}}),
}});
let text = await response.text();
document.getElementById("result").innerHTML = text;
}});
</script>
</body>
</html>
"#,
req.params().get("id").unwrap()
);
res.render(Text::Html(content));
}
#[handler]
async fn edit<'a>(good_man: GoodMan<'a>, res: &mut Response) {
res.render(Json(good_man));
}
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(default_source(from = "body")))]
struct GoodMan<'a> {
#[salvo(extract(source(from = "param")))]
#[serde(default)]
id: i64,
#[salvo(extract(source(from = "query")))]
#[serde(default)]
username: &'a str,
first_name: String,
last_name: String,
lovers: Vec<String>,
#[salvo(extract(flatten))]
nested: Nested<'a>,
}
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(default_source(from = "body")))]
struct Nested<'a> {
#[salvo(extract(source(from = "param")))]
#[serde(default)]
id: i64,
#[salvo(extract(source(from = "query")))]
#[serde(default)]
username: &'a str,
first_name: String,
last_name: String,
#[salvo(rename = "lovers")]
#[serde(default)]
pets: Vec<String>,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{id}").get(show).post(edit);
println!("Example url: http://0.0.0.0:5800/95");
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---csrf-cookie-store---
---csrf-cookie-store/Cargo.mdx---
[package]
name = "example-csrf-cookie-store"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["csrf"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
serde = { version = "1", features = ["derive"] }
---csrf-cookie-store/src---
---csrf-cookie-store/src/main.mdx---
use salvo::csrf::*;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
// Handler for serving the home page with links to different CSRF protection methods
#[handler]
pub async fn home(res: &mut Response) {
let html = r#"
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Csrf CookieStore</title></head>
<body>
<h2>Csrf Exampe: CookieStore</h2>
<ul>
<li><a href="/bcrypt/">Bcrypt</a></li>
<li><a href="/hmac/">Hmac</a></li>
<li><a href="/aes_gcm/">Aes Gcm</a></li>
<li><a href="/ccp/">chacha20poly1305</a></li>
</ul>
</body>"#;
res.render(Text::Html(html));
}
// Handler for GET requests that displays a form with CSRF token
#[handler]
pub async fn get_page(depot: &mut Depot, res: &mut Response) {
let new_token = depot.csrf_token().unwrap_or_default();
res.render(Text::Html(get_page_html(new_token, "")));
}
// Handler for POST requests that processes form submission with CSRF validation
#[handler]
pub async fn post_page(req: &mut Request, depot: &mut Depot, res: &mut Response) {
// Define data structure for form submission
#[derive(Deserialize, Serialize, Debug)]
struct Data {
csrf_token: String,
message: String,
}
// Parse the submitted form data into the Data struct
let data = req.parse_form::<Data>().await.unwrap();
// Log the received form data for debugging
tracing::info!("posted data: {:?}", data);
// Generate a new CSRF token for the next request
let new_token = depot.csrf_token().unwrap_or_default();
// Generate HTML response with the new token and display the submitted data
let html = get_page_html(new_token, &format!("{data:#?}"));
// Send the HTML response back to the client
res.render(Text::Html(html));
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Configure CSRF token finder in form data
let form_finder = FormFinder::new("csrf_token");
// Initialize different CSRF protection methods
let bcrypt_csrf = bcrypt_cookie_csrf(form_finder.clone());
let hmac_csrf = hmac_cookie_csrf(*b"01234567012345670123456701234567", form_finder.clone());
let aes_gcm_cookie_csrf =
aes_gcm_cookie_csrf(*b"01234567012345670123456701234567", form_finder.clone());
let ccp_cookie_csrf =
ccp_cookie_csrf(*b"01234567012345670123456701234567", form_finder.clone());
// Configure router with different CSRF protection endpoints
let router = Router::new()
.get(home)
// Bcrypt-based CSRF protection
.push(
Router::with_hoop(bcrypt_csrf)
.path("bcrypt")
.get(get_page)
.post(post_page),
)
// HMAC-based CSRF protection
.push(
Router::with_hoop(hmac_csrf)
.path("hmac")
.get(get_page)
.post(post_page),
)
// AES-GCM-based CSRF protection
.push(
Router::with_hoop(aes_gcm_cookie_csrf)
.path("aes_gcm")
.get(get_page)
.post(post_page),
)
// ChaCha20Poly1305-based CSRF protection
.push(
Router::with_hoop(ccp_cookie_csrf)
.path("ccp")
.get(get_page)
.post(post_page),
);
// Start server on port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
// Helper function to generate HTML page with CSRF token and message
fn get_page_html(csrf_token: &str, msg: &str) -> String {
format!(
r#"
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Csrf Example</title></head>
<body>
<h2>Csrf Exampe: CookieStore</h2>
<ul>
<li><a href="/bcrypt/">Bcrypt</a></li>
<li><a href="/hmac/">Hmac</a></li>
<li><a href="/aes_gcm/">Aes Gcm</a></li>
<li><a href="/ccp/">chacha20poly1305</a></li>
</ul>
<form action="./" method="post">
<input type="hidden" name="csrf_token" value="{csrf_token}" />
<div>
<label>Message:<input type="text" name="message" /></label>
</div>
<button type="submit">Send</button>
</form>
<pre>{msg}</pre>
</body>
</html>
"#
)
}
---custom-filter---
---custom-filter/Cargo.mdx---
[package]
name = "example-custom-filter"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---custom-filter/src---
---custom-filter/src/main.mdx---
use salvo::prelude::*;
// Handler that returns a simple "Hello World" response
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Configure router with custom host filter
// only allow access from http://localhost:5800/, http://0.0.0.0:5800/ will get not found page.
let router = Router::new()
.filter_fn(|req, _| {
// Extract HOST header from request
let host = req.header::<String>("HOST").unwrap_or_default();
// Only allow requests from localhost:5800
host == "localhost:5800"
})
.get(hello);
// Start server on port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---body-channel---
---body-channel/Cargo.mdx---
[package]
name = "example-body-channel"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---body-channel/src---
---body-channel/src/main.mdx---
use salvo::prelude::*;
// Handler demonstrating streaming response using response channel
#[handler]
async fn hello(res: &mut Response) {
// Set response content type to plain text
res.add_header("content-type", "text/plain", true).unwrap();
// Create a channel for streaming response data
let mut tx = res.channel();
// Spawn async task to send data through the channel
tokio::spawn(async move {
tx.send_data("Hello world").await.unwrap();
});
}
#[tokio::main]
async fn main() {
// Initialize logging subsystem
tracing_subscriber::fmt().init();
// Bind server to port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
// Create router with single endpoint
let router = Router::new().get(hello);
// Start serving requests
Server::new(acceptor).serve(router).await;
}
---static-embed-files---
---static-embed-files/static---
---static-embed-files/static/test2.mdx---
copy2
---static-embed-files/static/test1.mdx---
copy1
---static-embed-files/static/index.mdx---
<html>
<body>
Index page
</body>
</html>
---static-embed-files/Cargo.mdx---
[package]
name = "example-static-embed-files"
version = "0.1.0"
edition = "2024"
[dependencies]
rust-embed = ">= 6, <= 9"
salvo = { version = "0.77.1", features = ["serve-static"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---static-embed-files/src---
---static-embed-files/src/main.mdx---
use rust_embed::RustEmbed;
use salvo::prelude::*;
use salvo::serve_static::static_embed;
#[derive(RustEmbed)]
#[folder = "static"]
struct Assets;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{*path}").get(static_embed::<Assets>().fallback("index.html"));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---hello---
---hello/Cargo.mdx---
[package]
name = "example-hello"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---hello/src---
---hello/src/main.mdx---
use salvo::prelude::*;
// Handler for English greeting
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
// Handler for Chinese greeting
#[handler]
async fn hello_zh() -> Result<&'static str, ()> {
Ok("你好,世界!")
}
#[tokio::main]
async fn main() {
// Initialize logging subsystem
tracing_subscriber::fmt().init();
// Bind server to port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
// Create router with two endpoints:
// - / (root path) returns English greeting
// - /你好 returns Chinese greeting
let router = Router::new()
.get(hello)
.push(Router::with_path("你好").get(hello_zh));
// Print router structure for debugging
println!("{:?}", router);
// Start serving requests
Server::new(acceptor).serve(router).await;
}
---otel-jaeger---
---otel-jaeger/Cargo.mdx---
[package]
name = "example-otel-jaeger"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "example-otel-client"
path = "src/client.rs"
[[bin]]
name = "example-otel-server1"
path = "src/server1.rs"
[[bin]]
name = "example-otel-server2"
path = "src/server2.rs"
[dependencies]
salvo = { version = "0.77.1", features = ["affix-state", "otel"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
opentelemetry = { version = "0.28", features = ["metrics"] }
reqwest = { version = "0.12" }
prometheus = "0.13"
opentelemetry-prometheus = "0.28"
opentelemetry-http = "0.28"
opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] }
opentelemetry-otlp = { workspace = true, features = ["http-proto", "tonic", "trace", "reqwest"] }
---otel-jaeger/src---
---otel-jaeger/src/server1.mdx---
use std::str::FromStr;
use std::sync::Arc;
use opentelemetry::trace::{FutureExt, SpanKind, TraceContextExt, Tracer as _, TracerProvider};
use opentelemetry::{KeyValue, global};
use opentelemetry_http::HeaderInjector;
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use opentelemetry_sdk::trace::{SdkTracerProvider, Tracer};
use reqwest::{Client, Method, Url};
use salvo::otel::{Metrics, Tracing};
use salvo::prelude::*;
mod exporter;
use exporter::Exporter;
fn init_tracer_provider() -> SdkTracerProvider {
global::set_text_map_propagator(TraceContextPropagator::new());
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.build()
.expect("failed to create exporter");
SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(Resource::builder().with_service_name("server1").build())
.build()
}
#[handler]
async fn index(req: &mut Request, depot: &mut Depot) -> String {
let tracer = depot.obtain::<Arc<Tracer>>().unwrap();
let span = tracer
.span_builder("request/server2")
.with_kind(SpanKind::Client)
.start(&**tracer);
let cx = opentelemetry::Context::current_with_span(span);
let client = Client::new();
let body = std::str::from_utf8(req.payload().await.unwrap()).unwrap();
let req = {
let mut req = reqwest::Request::new(
Method::GET,
Url::from_str("http://localhost:5801/api2").unwrap(),
);
global::get_text_map_propagator(|propagator| {
propagator.inject_context(&cx, &mut HeaderInjector(req.headers_mut()))
});
*req.body_mut() = Some(format!("{body} server1\n").into());
req
};
let fut = async move {
let cx = opentelemetry::Context::current();
let span = cx.span();
span.add_event("Send request to server2".to_string(), vec![]);
let resp = client.execute(req).await.unwrap();
span.add_event(
"Got response from server2!".to_string(),
vec![KeyValue::new("status", resp.status().to_string())],
);
resp
}
.with_context(cx);
fut.await.text().await.unwrap()
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let tracer = init_tracer_provider().tracer("app");
let router = Router::new()
.hoop(affix_state::inject(Arc::new(tracer.clone())))
.hoop(Metrics::new())
.hoop(Tracing::new(tracer))
.push(Router::with_path("api1").get(index))
.push(Router::with_path("metrics").get(Exporter::new()));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---otel-jaeger/src/server2.mdx---
use opentelemetry::global;
use opentelemetry::trace::TracerProvider;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::{Resource, propagation::TraceContextPropagator};
use salvo::otel::{Metrics, Tracing};
use salvo::prelude::*;
mod exporter;
use exporter::Exporter;
fn init_tracer_provider() -> SdkTracerProvider {
global::set_text_map_propagator(TraceContextPropagator::new());
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.build()
.expect("failed to create exporter");
SdkTracerProvider::builder()
.with_resource(Resource::builder().with_service_name("server2").build())
.with_batch_exporter(exporter)
.build()
}
#[handler]
async fn index(req: &mut Request) -> String {
format!(
"Body: {}",
std::str::from_utf8(req.payload().await.unwrap()).unwrap()
)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let tracer = init_tracer_provider().tracer("app");
let router = Router::new()
.hoop(Metrics::new())
.hoop(Tracing::new(tracer))
.push(Router::with_path("api2").get(index))
.push(Router::with_path("metrics").get(Exporter::new()));
let acceptor = TcpListener::new("0.0.0.0:5801").bind().await;
Server::new(acceptor).serve(router).await;
}
---otel-jaeger/src/client.mdx---
use std::error::Error;
use std::str::FromStr;
use opentelemetry::{
Context, KeyValue, global,
trace::{FutureExt, TraceContextExt, Tracer as _},
};
use opentelemetry_http::HeaderInjector;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use opentelemetry_sdk::trace::SdkTracerProvider;
use reqwest::{Client, Method, Url};
fn init_tracer_provider() -> SdkTracerProvider {
global::set_text_map_propagator(TraceContextPropagator::new());
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:14268/api/traces")
.build()
.expect("failed to create exporter");
SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.build()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
let provider = init_tracer_provider();
global::set_tracer_provider(provider.clone());
let client = Client::new();
let span = global::tracer("example-opentelemetry/client").start("request/server1");
let cx = Context::current_with_span(span);
let req = {
let mut req = reqwest::Request::new(
Method::GET,
Url::from_str("http://localhost:5800/api1").unwrap(),
);
global::get_text_map_propagator(|propagator| {
propagator.inject_context(&cx, &mut HeaderInjector(req.headers_mut()));
println!("{:?}", req.headers_mut());
});
*req.body_mut() = Some("client\n".into());
req
};
async move {
let cx = Context::current();
let span = cx.span();
span.add_event("Send request to server1".to_string(), vec![]);
let resp = client.execute(req).await.unwrap();
span.add_event(
"Got response from server1!".to_string(),
vec![KeyValue::new("status", resp.status().to_string())],
);
println!("{}", resp.text().await.unwrap());
}
.with_context(cx)
.await;
provider.shutdown()?;
Ok(())
}
---otel-jaeger/src/exporter.mdx---
use prometheus::{Encoder, Registry, TextEncoder};
use salvo::http::{Method, StatusCode, header};
use salvo::prelude::*;
pub struct Exporter {
registry: Registry,
}
#[handler]
impl Exporter {
pub fn new() -> Self {
let registry = Registry::new_custom(None, None).expect("create prometheus registry");
Self { registry }
}
fn handle(&self, req: &Request, res: &mut Response) {
if req.method() != Method::GET {
res.status_code(StatusCode::METHOD_NOT_ALLOWED);
return;
}
let encoder = TextEncoder::new();
let metric_families = self.registry.gather();
let mut body = Vec::new();
match encoder.encode(&metric_families, &mut body) {
Ok(()) => {
let _ =
res.add_header(header::CONTENT_TYPE, "text/javascript; charset=utf-8", true);
res.body(body);
}
Err(_) => {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
}
}
}
}
---routing-guid---
---routing-guid/Cargo.mdx---
[package]
name = "example-routing-guid"
version = "0.1.0"
edition = "2024"
[dependencies]
regex = "1"
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---routing-guid/src---
---routing-guid/src/main.mdx---
use regex::Regex;
use salvo::prelude::*;
use salvo::routing::PathFilter;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
// invalid guid: 123e4567-h89b-12d3-a456-9AC7CBDCEE52
// valid guid: 123e4567-e89b-12d3-a456-9AC7CBDCEE52
PathFilter::register_wisp_regex(
"guid",
Regex::new("[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}").unwrap(),
);
let router = Router::with_path("{id:guid}").get(index);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
#[handler]
async fn index(req: &mut Request, res: &mut Response) {
res.render(req.params().get::<str>("id").unwrap());
}
---static-dir-list---
---static-dir-list/static---
---static-dir-list/static/test---
---static-dir-list/static/test/test2.mdx---
copy2
---static-dir-list/static/test/test1.mdx---
copy1
---static-dir-list/static/test/dir1---
---static-dir-list/static/test/dir1/test3.mdx---
copy3
---static-dir-list/static/test/dir1/dir2---
---static-dir-list/static/test/dir1/dir2/test3.mdx---
dir2 test3
---static-dir-list/static/boy---
---static-dir-list/static/boy/.dot---
---static-dir-list/static/boy/.dot/dotfile.mdx---
Dot file
An elephant said to a mouse ,"no doubt that you are the smallest znd most useless thing that Ihave e ver seen ."
"Pless ,say it again .Let me take it down ."the mouse said ."I will tell a flea what I know.
---static-dir-list/static/boy/work_utf16_be.mdx---
����^�N��fb�N�N*��dibX�fb�N�^v��S�g:Q�y:��N�0�
S�g:���:� ��f/��^�N��T�[P~�N*�b[Pd��g,g,Q?_�^&N��b[�e�W(R�p�v�0�
N��f���:� ���Ab�O`N
f/��^�V0S�g:S͕��:� O`��N*fS_�bN
f/��^�V�?� �?�
N��f���:� O`��N
b���{Ibb�N*��^�v�~�O`wT�0�
��e�PN�\O[P��dibX��geN��N��fdFbKb�f�!~�g�dibX�f��YT������N-��O geN�S��:� O`N*T�by�!� ��geb��[Pd��!�
N��f�l��[�RbMv�S�g:���:� wR0l�_��!� R�N*bMf/��^�v��!� O`ZQ?N�w\1f/b��v�0�
---static-dir-list/static/boy/work_utf8_bom.mdx---
重庆交警把一个骑摩托车拦下,并让司机出示证件。
司机说: 都是重庆人,哥子给个面子撒,本本儿忘带了,我家斗在勒点的。
交警说: 莫豁我,你不是重庆嘞。司机反问: 你郎个晓得我不是重庆嘞? ?
交警说: 你莫不承认,等我拦个重庆的给你看哈。
这时候一小伙子骑摩托过来了,交警摆手拦车!结果摩托车飞奔而过,风中远远传来一句: 你个哈批! 过来抓老子撒!
交警转身对刚才的司机说: 看到没得! 勒个才是重庆的! 你娃儿一看就是成都的。
---static-dir-list/static/boy/love---
---static-dir-list/static/boy/love/avengers.mdx---
Let me take it down
An elephant said to a mouse ,"no doubt that you are the smallest znd most useless thing that Ihave e ver seen ."
"Pless ,say it again .Let me take it down ."the mouse said ."I will tell a flea what I know.
为我所用
一头大象对一只小老鼠说:“你无疑是我见过的最小、最没用的东西。”
“请再说一遍,让我把它记下来。”老鼠说。“我要讲给我认识的一只跳蚤听。
---static-dir-list/static/boy/love/game.mdx---
Mother: Freddie, why is your face so red?
Freddie: I was running up the street to stop a fight.
Mother: That's a very nice thing to do. Who was fighting?
Freddie: Me and Jackie Smith.
妈妈:弗雷迪,你的脸为什么那么红?
弗雷迪:我刚才在大街上跑,为的是阻止一次打架?
妈妈:你做的对,谁和谁在打架。
弗雷迪:我和杰克·史密斯。
---static-dir-list/static/boy/love/work.mdx---
Section 1.10.32 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"
---static-dir-list/static/boy/love/pc.mdx---
1914 translation by H. Rackham
"On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains."
---static-dir-list/static/boy/play.mdx---
<html>
<head>
<title>consectetur adipiscing elit</title></head>
<body>
<div id="lipsum">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla magna risus, dignissim eu egestas ut, tempor in nulla. Phasellus eleifend at nibh quis congue. Etiam vulputate tortor nec magna iaculis, non aliquet dui volutpat. Nulla eleifend, justo nec vestibulum porta, libero turpis egestas massa, et dictum quam lacus vitae quam. Vivamus accumsan auctor urna et posuere. Aliquam venenatis diam vel purus consectetur dignissim. In ultrices in arcu ac mattis. Aliquam vitae auctor eros. Duis vel magna at purus iaculis tristique. Maecenas eget arcu ex.
</p>
<p>
Morbi vel nunc vulputate, pulvinar odio eu, gravida lectus. Integer pharetra congue bibendum. Donec pharetra sem in leo commodo sagittis. Morbi nisi libero, tempus et metus at, dictum aliquam dui. Morbi bibendum orci nisi. Integer volutpat arcu ipsum, id placerat augue tincidunt non. Aenean vel nibh justo. Mauris convallis et enim ultrices feugiat. Praesent porta varius tellus sed suscipit. Mauris rutrum risus lorem, vitae maximus risus pharetra ut. Nunc enim augue, lacinia vel nulla vitae, convallis tincidunt ex. Fusce a quam nec est vulputate ultricies at sed massa. Integer in ex in dolor volutpat gravida eget quis lorem. Donec commodo in ligula nec malesuada. Aenean imperdiet justo a eros porta, nec mattis metus sagittis. Ut pharetra fringilla massa.
</p>
<p>
Sed viverra turpis ac lorem interdum pulvinar. Nulla lobortis facilisis luctus. In consequat lorem a velit dignissim, lacinia tristique metus finibus. Nunc varius vitae magna non varius. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sed placerat ex. In lacus mauris, aliquet id nulla eget, pellentesque ultrices felis. Aenean risus orci, sodales ac turpis ut, volutpat congue libero. Nullam facilisis diam ut nisl blandit, a posuere turpis varius. Phasellus consectetur pharetra ultricies. Aenean luctus ex justo, ac convallis sem eleifend eleifend. Nullam imperdiet velit eu lectus mollis sagittis.
</p>
<p>
Vestibulum vitae nisi ac arcu efficitur pulvinar. Quisque maximus ac dui vitae luctus. Sed eget consequat nisi. In pharetra lectus non nibh mollis auctor. Ut efficitur nisi id mi aliquam, at gravida augue hendrerit. Suspendisse sodales metus urna. Curabitur placerat quam dui, ut varius ipsum cursus eget. Curabitur sollicitudin fermentum orci at varius. Cras sed elementum justo.
</p>
<p>
Nunc nisi erat, dignissim sit amet ante cursus, pulvinar blandit est. Fusce ornare tortor id purus gravida, quis pharetra mi pellentesque. Suspendisse laoreet tincidunt lacus, non sollicitudin nibh ultrices sit amet. Fusce molestie leo arcu, a malesuada odio volutpat a. Fusce ut dui facilisis, molestie velit nec, eleifend orci. Donec tincidunt velit eget varius cursus. Suspendisse vitae ex tellus. Integer et erat eget ligula pretium tincidunt. Phasellus viverra odio quis faucibus hendrerit.
</p></div>
</body>
</html>
---static-dir-list/static/boy/work_utf8.mdx---
重庆交警把一个骑摩托车拦下,并让司机出示证件。
司机说: 都是重庆人,哥子给个面子撒,本本儿忘带了,我家斗在勒点的。
交警说: 莫豁我,你不是重庆嘞。司机反问: 你郎个晓得我不是重庆嘞? ?
交警说: 你莫不承认,等我拦个重庆的给你看哈。
这时候一小伙子骑摩托过来了,交警摆手拦车!结果摩托车飞奔而过,风中远远传来一句: 你个哈批! 过来抓老子撒!
交警转身对刚才的司机说: 看到没得! 勒个才是重庆的! 你娃儿一看就是成都的。
---static-dir-list/static/boy/work_utf16_le.mdx---
��͑�^�Nf��b�N*N��idXbf��bN�v^���S:g�Q:y���N0
��S:g�:� ���/f͑�^�N��TP[�~*Nb�P[�d�,g,g?Q�_&^�N�b�[�e(W�R�p�v0
��Nf��:� ���A�b�`O
N/f͑�^V0�S:g�S�:� �`Oΐ*NSf�_b
N/f͑�^V?� �?�
��Nf��:� �`O��
Nb���I{b�b*N͑�^�v�~`Ow�T0
�ُ�eP�N\OP[��idXbǏeg�N��Nf�FdKb�bf�!��~�gidXbf�ޘTY�Ǐ�Θ-N Oeg�N�S:� �`O*N�Tyb!� �Ǐeg�b�P[�d!�
��Nf�l����[RMb�v�S:g�:� �w0R�l�_!� ��R*NMb/f͑�^�v!� �`OZ?Q�Nw1\/fb���v0
�
---static-dir-list/static/boy/hate---
---static-dir-list/static/boy/hate/princess.mdx---
The mean man's party.
The notorious cheap skate finally decided to have a party. Explaining to a friend how to find his apartment, he said, "Come up to 5M and ring the doorbell with your elbow. When the door open, push with your foot."
"Why use my elbow and foot?"
"Well, gosh," was the reply, "You're not coming empty-hangded, are you?"
吝啬鬼请客
一个出了名的吝啬鬼终于决定要请一次客了。他在向一个朋友解释怎么找到他家时说:“你上到五楼,找中间那个门,然后用你的胳膊肘按门铃。门开了之后,再用你的脚把门推开。”
“为什么要用我的肘和脚呢?”
“你的双手得拿礼物啊。天哪,你总不会空着手来吧?”吝啬鬼回答。
---static-dir-list/static/boy/工作---
---static-dir-list/static/boy/工作/有 意思.mdx---
什么是996?
“996”工作制,指的是一种越来越流行的非官方工作制(早上 9 点 ~ 晚上 9 点,每周 6 天)。在一个实行“996”工作制的公司工作就意味着每周至少要工作 60 个小时。
我能做什么?
更新这个名单(注附带证据),来帮助每一位工作者。为不熟悉GitHub劳动者准备的提交渠道
把这个徽章添加到你的项目来支持 996.ICU
为你的项目添加反 996 许可证。
给社区与项目发展提出新的议案。
下午6点钟下班回家。
企业和群众可以登录中国政府网(www.gov.cn) 或下载国务院客户端,进入国务院“互联网+督查”专栏,也可以关注中国政府网微信公众号,进入国务院“互联网+督查”小程序提供线索、反映问题、提出建议。国务院办公厅将对收到的问题线索和意见建议进行汇总整理,督促有关地方、部门处理。对企业和群众反映强烈、带有普遍性的重要问题线索,国务院办公厅督查室将直接派员督查。经查证属实、较为典型的问题,将予以公开曝光、严肃处理。
互助式举报,通过群聊频道Slack 找到有相似举报需求的加班受害者,相互举报对方公司的违法事实,根据《劳动保障监察条例》第一章第九条 “任何组织或者个人对违反劳动保障法律、法规或者规章的行为,有权向劳动保障行政部门举报。” 通过电话以朋友的名义向当地劳动监察大队举报,以便更稳妥的保护当事人身份信息。详细内容请见互助式举报说明细项
---static-dir-list/static/boy/工作/index.mdx---
<html>
<head>
<title>consectetur adipiscing elit</title></head>
<body>
<div id="lipsum">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla magna risus, dignissim eu egestas ut, tempor in nulla. Phasellus eleifend at nibh quis congue. Etiam vulputate tortor nec magna iaculis, non aliquet dui volutpat. Nulla eleifend, justo nec vestibulum porta, libero turpis egestas massa, et dictum quam lacus vitae quam. Vivamus accumsan auctor urna et posuere. Aliquam venenatis diam vel purus consectetur dignissim. In ultrices in arcu ac mattis. Aliquam vitae auctor eros. Duis vel magna at purus iaculis tristique. Maecenas eget arcu ex.
</p>
<p>
Morbi vel nunc vulputate, pulvinar odio eu, gravida lectus. Integer pharetra congue bibendum. Donec pharetra sem in leo commodo sagittis. Morbi nisi libero, tempus et metus at, dictum aliquam dui. Morbi bibendum orci nisi. Integer volutpat arcu ipsum, id placerat augue tincidunt non. Aenean vel nibh justo. Mauris convallis et enim ultrices feugiat. Praesent porta varius tellus sed suscipit. Mauris rutrum risus lorem, vitae maximus risus pharetra ut. Nunc enim augue, lacinia vel nulla vitae, convallis tincidunt ex. Fusce a quam nec est vulputate ultricies at sed massa. Integer in ex in dolor volutpat gravida eget quis lorem. Donec commodo in ligula nec malesuada. Aenean imperdiet justo a eros porta, nec mattis metus sagittis. Ut pharetra fringilla massa.
</p>
<p>
Sed viverra turpis ac lorem interdum pulvinar. Nulla lobortis facilisis luctus. In consequat lorem a velit dignissim, lacinia tristique metus finibus. Nunc varius vitae magna non varius. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sed placerat ex. In lacus mauris, aliquet id nulla eget, pellentesque ultrices felis. Aenean risus orci, sodales ac turpis ut, volutpat congue libero. Nullam facilisis diam ut nisl blandit, a posuere turpis varius. Phasellus consectetur pharetra ultricies. Aenean luctus ex justo, ac convallis sem eleifend eleifend. Nullam imperdiet velit eu lectus mollis sagittis.
</p>
<p>
Vestibulum vitae nisi ac arcu efficitur pulvinar. Quisque maximus ac dui vitae luctus. Sed eget consequat nisi. In pharetra lectus non nibh mollis auctor. Ut efficitur nisi id mi aliquam, at gravida augue hendrerit. Suspendisse sodales metus urna. Curabitur placerat quam dui, ut varius ipsum cursus eget. Curabitur sollicitudin fermentum orci at varius. Cras sed elementum justo.
</p>
<p>
Nunc nisi erat, dignissim sit amet ante cursus, pulvinar blandit est. Fusce ornare tortor id purus gravida, quis pharetra mi pellentesque. Suspendisse laoreet tincidunt lacus, non sollicitudin nibh ultrices sit amet. Fusce molestie leo arcu, a malesuada odio volutpat a. Fusce ut dui facilisis, molestie velit nec, eleifend orci. Donec tincidunt velit eget varius cursus. Suspendisse vitae ex tellus. Integer et erat eget ligula pretium tincidunt. Phasellus viverra odio quis faucibus hendrerit.
</p></div>
</body>
</html>
---static-dir-list/static/boy/work_ansi.mdx---
Let me take it down
An elephant said to a mouse ,"no doubt that you are the smallest znd most useless thing that Ihave e ver seen ."
"Pless ,say it again .Let me take it down ."the mouse said ."I will tell a flea what I know.
---static-dir-list/static/girl---
---static-dir-list/static/girl/结婚---
---static-dir-list/static/girl/结婚/财富---
---static-dir-list/static/girl/结婚/财富/必须的.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/结婚/自由---
---static-dir-list/static/girl/结婚/自由/必须的.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/结婚/干嘛.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/结婚/孩子---
---static-dir-list/static/girl/结婚/孩子/不想.txt.txt.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/love---
---static-dir-list/static/girl/love/flowers---
---static-dir-list/static/girl/love/flowers/rose.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/love/flowers/lily.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/love/flowers/lotus.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/love/dog.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/love/skirt.mdx---
<html>
<body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
</body>
</html>
---static-dir-list/static/girl/love/eat.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/love/cat.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/sleep.mdx---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lectus neque, aliquet ut scelerisque in, blandit et dui. Fusce at elit a dolor cursus interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean euismod placerat eros, vitae rutrum nisi lacinia eu. In malesuada neque id sollicitudin malesuada. Cras quis lobortis risus, fringilla pretium leo. Maecenas semper mauris libero, in blandit libero consectetur ut. Fusce feugiat porttitor dolor. Aliquam commodo volutpat neque, facilisis congue ex blandit sit amet. Integer sodales erat non sem ullamcorper consectetur. Sed sodales lectus luctus purus pellentesque interdum. Vestibulum fermentum odio vitae massa tristique, et sodales ante ullamcorper. Duis ultrices imperdiet viverra.
Vivamus at neque orci. Sed gravida tortor vitae turpis tristique, at rutrum elit eleifend. Morbi quis eleifend nisl. Sed pretium eros quis viverra porttitor. Nulla consequat vitae nisl euismod iaculis. Vivamus tincidunt quam ac tellus accumsan consectetur. Curabitur aliquam sed urna quis bibendum. Curabitur congue, sem sit amet congue iaculis, tellus ex aliquam nisi, ut consequat arcu magna eget nibh. Pellentesque lobortis efficitur nunc, vel posuere ante accumsan a.
Nam mollis tellus erat, non sodales urna fermentum at. Aliquam in sagittis sapien. Suspendisse rhoncus vitae est et convallis. Mauris fermentum tincidunt nisi, quis dapibus velit viverra quis. Quisque tristique, elit cursus fringilla fermentum, metus eros sollicitudin lectus, a viverra nunc elit id orci. Fusce accumsan rhoncus malesuada. Donec in nisl eget quam commodo varius. Sed varius arcu eros, sit amet ultrices odio aliquet nec. Etiam dignissim eleifend nisl, ut pharetra enim interdum et. Maecenas pellentesque leo urna, eu ultricies nulla sollicitudin vel. Duis placerat purus neque, nec viverra dui congue non. Aenean faucibus in nisi at faucibus. Nullam ornare id ipsum eget ornare. Mauris rutrum ultricies massa, nec blandit nunc faucibus in. Maecenas sollicitudin malesuada nisl, vel lacinia nunc sodales cursus.
Cras et ligula ut nibh bibendum aliquet. Mauris efficitur, erat eget molestie fermentum, felis elit molestie tellus, vitae maximus eros diam non quam. Nam sagittis ullamcorper interdum. Nunc nisl lacus, elementum ac posuere eget, interdum nec neque. Suspendisse ac felis pretium, molestie odio vel, lobortis risus. Proin iaculis gravida nibh, id ultrices nulla mollis sit amet. Vestibulum suscipit dui eget felis pretium laoreet. Sed tristique enim ac risus eleifend dapibus. Sed scelerisque, tellus vitae dignissim malesuada, dui dolor interdum nisl, eget porttitor nisl lacus interdum velit. Nullam lobortis erat at est congue, a condimentum arcu finibus. In tempor massa a augue malesuada, nec semper augue posuere. Nulla ut maximus elit.
Phasellus eu elit neque. Etiam id molestie lectus, quis semper libero. Nulla ultricies purus nec pretium imperdiet. Donec vestibulum interdum sem, suscipit pharetra augue tincidunt nec. Sed ut semper libero, non placerat lectus. Pellentesque non feugiat nisi, eget lacinia nisi. Vivamus dapibus rutrum sollicitudin. Vivamus commodo ac sem nec accumsan. Etiam et tincidunt nulla. Pellentesque tempus odio eget ligula maximus, eget vulputate libero aliquet. Aenean laoreet dolor sit amet nibh egestas ullamcorper. Phasellus malesuada, ante quis suscipit tempor, libero lacus mattis diam, at venenatis libero eros non elit. Donec faucibus lac
---static-dir-list/static/girl/hate---
---static-dir-list/static/girl/hate/eunuch.mdx---
onsectetur nibh posuere tellus tempor, eget ornare lacus mollis. Sed vehicula libero purus, sed vestibulum leo consectetur vitae. Nullam eleifend sit amet quam eget elementum. Sed pulvinar, justo eu mollis convallis, urna erat sagittis tortor, sagittis cursus elit neque ac sapien. Nulla cursus semper pharetra. Curabitur tempus eleifend lorem, non eleifend nulla laoreet quis. Mauris porta sit amet augue a facilisis. Nulla luctus nibh a bibendum fringilla. Nam aliquam, quam sit amet tincidunt viverra, nisl quam pellentesque nisl, vitae iaculis tellus magna a tellus. Mauris vitae tellus nisi. Mauris accumsan mattis est, tempor venenatis ante tristique eget. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam a feugiat tortor.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula quam id purus scelerisque tincidunt. Vivamus vestibulum vel tortor ultrices tincidunt. Duis auctor laoreet felis vel luctus. Aenean hendrerit rutrum augue, ut pulvinar ipsum dignissim non. Morbi ultrices odio neque. Quisque vulputate viverra elit, sit amet consequat dolor interdum ac. Cras volutpat sapien nulla, pellentesque efficitur urna posuere ut. Etiam sollicitudin bibendum enim, in ornare arcu fermentum lobortis.
Sed sagittis mauris venenatis sem pretium, quis porta tortor pulvinar. Aenean nec justo sapien. Aenean ut risus at orci dictum finibus sit amet ut mauris. Donec mattis ipsum id lectus mollis aliquam. Nulla nunc mi, semper at felis vitae, aliquet elementum arcu. Aenean et vulputate tellus. Quisque ac tellus sit amet ipsum varius sodales ut sed elit. Nunc eget fringilla eros, et faucibus orci. Integer consectetur quam ac porttitor pretium. Nulla ac faucibus risus. Phasellus viverra nibh non bibendum aliquam. Nullam turpis libero, auctor et sem maximus, dictum tincidunt nisl. Vestibulum hendrerit efficitur felis ut bibendum. Sed justo diam, elementum et volutpat non, commodo a massa. Donec et molestie nunc. Mauris laoreet porta urna, ac interdum nisl auctor non.
Praesent ac dolor vitae nulla hendrerit commodo. Nullam felis lectus, pharetra quis tortor vehicula, scelerisque tincidunt nibh. Vestibulum fringilla pharetra tellus sit amet pharetra. Nam sed magna sem. Vestibulum sed sem orci. Vestibulum auctor ex in sem posuere ullamcorper. Maecenas purus tortor, maximus in laoreet eu, convallis at ligula. Aenean ac eros lacinia, sagittis purus et, gravida lectus.
Nulla sem ante, commodo sed risus pretium, gravida hendrerit augue. Sed nisl tellus, dictum id mauris vulputate, cursus convallis sem. Aliquam orci dui, porttitor non ante sit amet, mattis convallis dui. Nullam mattis fringilla tempus. Donec id pellentesque nibh. Duis neque nunc, fermentum non sapien non, consectetur convallis velit. Quisque at leo sit amet lacus luctus dignissim. Nam diam nulla, congue non sodales ac, finibus nec arcu. Aliquam in nisi bibendum, interdum dui a, ultricies eros.
---static-dir-list/Cargo.mdx---
[package]
name = "example-static-dir-list"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features=["serve-static"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---static-dir-list/src---
---static-dir-list/src/main.mdx---
use salvo::prelude::*;
use salvo::serve_static::StaticDir;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{*path}").get(
StaticDir::new([
"static-dir-list/static/boy",
"static-dir-list/static/girl",
"static/boy",
"static/girl",
])
.include_dot_files(false)
.defaults("index.html")
.auto_list(true),
);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---db-graphql---
---db-graphql/Cargo.mdx---
[package]
name = "example-db-graphql"
version = "0.1.0"
edition = "2024"
[dependencies]
parking_lot = "0.12"
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "^0.1"
tracing-subscriber = "^0.3"
juniper = "^0.15.10"
---db-graphql/src---
---db-graphql/src/main.mdx---
use salvo::prelude::*;
use juniper::http::GraphQLRequest;
use schema::create_schema;
use crate::schema::DatabaseContext;
pub mod mutation;
pub mod query;
pub mod schema;
#[tokio::main]
async fn main() {
// Create router with GraphQL endpoint
let router = Router::new().push(Router::with_path("graphql").post(graphql));
// Bind server to port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
// Start the server
Server::new(acceptor).serve(router).await;
}
// Handler for GraphQL requests
#[handler]
async fn graphql(req: &mut Request, res: &mut Response) {
// Create GraphQL schema
let schema = create_schema();
// Initialize database context
let context = DatabaseContext::new();
// Parse incoming GraphQL request
let data = req.parse_json::<GraphQLRequest>().await.unwrap();
// Execute GraphQL query
let response = data.execute(&schema, &context).await;
// Return JSON response
res.render(Json(response))
}
---db-graphql/src/mutation.mdx---
use juniper::{FieldResult, graphql_object};
use crate::schema::{DatabaseContext, User, UserInput};
// Root type for all GraphQL mutations
pub struct MutationRoot;
// Implement GraphQL mutation resolvers
#[graphql_object(context = DatabaseContext)]
impl MutationRoot {
// Mutation to create a new user
// Note: database needs RwLock for thread-safe writes
fn create_user(context: &DatabaseContext, user: UserInput) -> FieldResult<User> {
let mut write = context.0.write();
let user = User {
id: user.id,
name: user.name,
};
let user_to_return = user.clone();
write.insert(user);
Ok(user_to_return)
}
}
---db-graphql/src/query.mdx---
use juniper::{FieldError, FieldResult, graphql_object};
use crate::schema::{DatabaseContext, User};
// Root type for all GraphQL queries
pub struct QueryRoot;
// Implement GraphQL query resolvers
#[graphql_object(context = DatabaseContext)]
impl QueryRoot {
// Query to get all users from the database
fn get_all_users(context: &DatabaseContext) -> FieldResult<Vec<User>> {
let read = context.0.read();
let users = read.get_all_users();
let mut result = Vec::with_capacity(users.len());
result.reserve(users.len());
for user in users {
result.push(User {
id: user.id,
name: user.name.clone(),
})
}
Ok(result)
}
// Query to get a specific user by ID
fn get_user_by_id(context: &DatabaseContext, id: i32) -> FieldResult<User> {
let read = context.0.read();
let user = read.get_user_by_id(&id);
match user {
Some(user) => Ok(User {
id: user.id,
name: user.name.clone(),
}),
None => Err(FieldError::from("could not find the user")),
}
}
}
---db-graphql/src/schema.mdx---
use parking_lot::RwLock;
use std::collections::HashMap;
use std::default::Default;
use juniper::{EmptySubscription, GraphQLInputObject, GraphQLObject, RootNode};
use crate::{mutation::MutationRoot, query::QueryRoot};
// Define the GraphQL schema type with Query and Mutation roots
pub type Schema = RootNode<'static, QueryRoot, MutationRoot, EmptySubscription<DatabaseContext>>;
// Create and initialize the GraphQL schema
pub fn create_schema() -> Schema {
Schema::new(
QueryRoot {},
MutationRoot {},
EmptySubscription::<DatabaseContext>::default(),
)
}
// User model that can be returned in GraphQL responses
#[derive(GraphQLObject, Clone)]
pub struct User {
pub id: i32,
pub name: String,
}
// Input type for creating new users through GraphQL mutations
#[derive(GraphQLInputObject)]
pub struct UserInput {
pub id: i32,
pub name: String,
}
// In-memory database structure to store users
pub struct Database {
users: HashMap<i32, User>,
}
// Database context wrapper with thread-safe read/write access
pub struct DatabaseContext(pub RwLock<Database>);
// Default implementation for DatabaseContext
impl Default for DatabaseContext {
fn default() -> DatabaseContext {
DatabaseContext::new()
}
}
// DatabaseContext implementation with initialization
impl DatabaseContext {
pub fn new() -> Self {
DatabaseContext(RwLock::<Database>::new(Database::new()))
}
}
// Default implementation for Database
impl Default for Database {
fn default() -> Self {
Self::new()
}
}
// Database implementation with CRUD operations
impl Database {
// Initialize database with some sample users
pub fn new() -> Self {
let mut users = HashMap::new();
users.insert(
0,
User {
id: 0,
name: String::from("Alice"),
},
);
users.insert(
1,
User {
id: 1,
name: String::from("Bob"),
},
);
Database { users }
}
// Get all users from the database
pub fn get_all_users(&self) -> Vec<&User> {
Vec::from_iter(self.users.values())
}
// Get a specific user by ID
pub fn get_user_by_id(&self, id: &i32) -> Option<&User> {
self.users.get(id)
}
// Insert a new user into the database
pub fn insert(&mut self, user: User) {
self.users.insert(user.id, user);
}
}
---routing---
---routing/Cargo.mdx---
[package]
name = "example-routing"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---routing/src---
---routing/src/main.mdx---
use salvo::prelude::*;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let debug_mode = true;
let admin_mode = true;
let router = Router::new()
.get(index)
.push(
Router::with_path("users")
.hoop(auth)
.post(create_user)
.push(
Router::with_path("{id:num}")
.post(update_user)
.delete(delete_user),
),
)
.push(
Router::with_path("users")
.get(list_users)
.push(Router::with_path("{id:num}").get(show_user)),
)
.then(|router| {
if debug_mode {
router.push(Router::with_path("debug").get(debug))
} else {
router
}
})
.then(|router| {
if admin_mode {
router.push(Router::with_path("admin").get(admin))
} else {
router
}
});
println!("{router:#?}");
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
#[handler]
async fn admin(res: &mut Response) {
res.render(Text::Plain("Admin page"));
}
#[handler]
async fn debug(res: &mut Response) {
res.render(Text::Plain("Debug page"));
}
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Plain("Hello World!"));
}
#[handler]
async fn auth(res: &mut Response) {
res.render(Text::Plain("user has authed\n\n"));
}
#[handler]
async fn list_users(res: &mut Response) {
res.render(Text::Plain("list users"));
}
#[handler]
async fn show_user(res: &mut Response) {
res.render(Text::Plain("show user"));
}
#[handler]
async fn create_user(res: &mut Response) {
res.render(Text::Plain("user created"));
}
#[handler]
async fn update_user(res: &mut Response) {
res.render(Text::Plain("user updated"));
}
#[handler]
async fn delete_user(res: &mut Response) {
res.render(Text::Plain("user deleted"));
}
---oapi-upload-files---
---oapi-upload-files/Cargo.mdx---
[package]
name = "example-oapi-upload-files"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["oapi"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---oapi-upload-files/src---
---oapi-upload-files/src/main.mdx---
use std::fs::create_dir_all;
use std::path::Path;
use salvo::oapi::extract::*;
use salvo::prelude::*;
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
#[endpoint]
async fn upload(files: FormFiles, res: &mut Response) {
let mut msgs = Vec::with_capacity(files.len());
for file in files.into_inner() {
let dest = format!("temp/{}", file.name().unwrap_or("file"));
if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Plain(format!("file not found in request: {e}")));
} else {
msgs.push(dest);
}
}
res.render(Text::Plain(format!(
"Files uploaded:\n\n{}",
msgs.join("\n")
)));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
create_dir_all("temp").unwrap();
let router = Router::new().get(index).post(upload);
let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);
let router = router
.unshift(doc.into_router("/api-doc/openapi.json"))
.unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Upload files</title>
</head>
<body>
<h1>Upload files</h1>
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple/>
<input type="submit" value="upload" />
</form>
</body>
</html>
"#;
---todos---
---todos/Cargo.mdx---
[package]
name = "example-todos"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["size-limiter"] }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---todos/src---
---todos/src/main.mdx---
use std::sync::LazyLock;
use salvo::prelude::*;
use salvo::size_limiter;
use self::models::*;
static STORE: LazyLock<Db> = LazyLock::new(new_store);
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
start_server().await;
}
pub(crate) async fn start_server() {
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(route()).await;
}
#[handler]
fn index(res: &mut Response) {
res.render(Text::Html(
"<html>
<body>
<a href=\"/todos\">going to the todo page</a>
</body>
</html>",
));
}
fn route() -> Router {
Router::new()
.push(Router::new().get(index))
.push(Router::new().path("todos").push(todo_route()))
}
fn todo_route() -> Router {
Router::new()
.hoop(size_limiter::max_size(1024 * 16))
.get(list_todos)
.post(create_todo)
.push(
Router::with_path("{id}")
.put(update_todo)
.delete(delete_todo),
)
}
#[handler]
pub async fn list_todos(req: &mut Request, res: &mut Response) {
let opts = req.parse_body::<ListOptions>().await.unwrap_or_default();
let todos = STORE.lock().await;
let todos: Vec<Todo> = todos
.clone()
.into_iter()
.skip(opts.offset.unwrap_or(0))
.take(opts.limit.unwrap_or(usize::MAX))
.collect();
res.render(Json(todos));
}
#[handler]
pub async fn create_todo(req: &mut Request, res: &mut Response) {
let new_todo = req.parse_body::<Todo>().await.unwrap();
tracing::debug!(todo = ?new_todo, "create todo");
let mut vec = STORE.lock().await;
for todo in vec.iter() {
if todo.id == new_todo.id {
tracing::debug!(id = ?new_todo.id, "todo already exists");
res.status_code(StatusCode::BAD_REQUEST);
return;
}
}
vec.push(new_todo);
res.status_code(StatusCode::CREATED);
}
#[handler]
pub async fn update_todo(req: &mut Request, res: &mut Response) {
let id = req.param::<u64>("id").unwrap();
let updated_todo = req.parse_body::<Todo>().await.unwrap();
tracing::debug!(todo = ?updated_todo, id = ?id, "update todo");
let mut vec = STORE.lock().await;
for todo in vec.iter_mut() {
if todo.id == id {
*todo = updated_todo;
res.status_code(StatusCode::OK);
return;
}
}
tracing::debug!(?id, "todo is not found");
res.status_code(StatusCode::NOT_FOUND);
}
#[handler]
pub async fn delete_todo(req: &mut Request, res: &mut Response) {
let id = req.param::<u64>("id").unwrap();
tracing::debug!(?id, "delete todo");
let mut vec = STORE.lock().await;
let len = vec.len();
vec.retain(|todo| todo.id != id);
let deleted = vec.len() != len;
if deleted {
res.status_code(StatusCode::NO_CONTENT);
} else {
tracing::debug!(?id, "todo is not found");
res.status_code(StatusCode::NOT_FOUND);
}
}
mod models {
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
pub type Db = Mutex<Vec<Todo>>;
pub fn new_store() -> Db {
Mutex::new(Vec::new())
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Todo {
pub id: u64,
pub text: String,
pub completed: bool,
}
#[derive(Deserialize, Debug, Default)]
pub struct ListOptions {
pub offset: Option<usize>,
pub limit: Option<usize>,
}
}
#[cfg(test)]
mod tests {
use salvo::http::StatusCode;
use salvo::test::TestClient;
use super::models::Todo;
#[tokio::test]
async fn test_todo_create() {
tokio::task::spawn(async {
super::start_server().await;
});
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let res = TestClient::post("http://0.0.0.0:5800/todos")
.json(&test_todo())
.send(super::route())
.await;
assert_eq!(res.status_code.unwrap(), StatusCode::CREATED);
let res = TestClient::post("http://0.0.0.0:5800/todos")
.json(&test_todo())
.send(super::route())
.await;
assert_eq!(res.status_code.unwrap(), StatusCode::BAD_REQUEST);
}
fn test_todo() -> Todo {
Todo {
id: 1,
text: "test todo".into(),
completed: false,
}
}
}
---rate-limiter-static---
---rate-limiter-static/Cargo.mdx---
[package]
name = "example-rate-limiter-static"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["rate-limiter"]}
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---rate-limiter-static/src---
---rate-limiter-static/src/main.mdx---
use salvo::prelude::*;
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(1),
);
let router = Router::with_hoop(limiter).get(hello);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---remote-addr---
---remote-addr/Cargo.mdx---
[package]
name = "example-remote-addr"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---remote-addr/src---
---remote-addr/src/main.mdx---
use salvo::prelude::*;
#[handler]
async fn index(req: &mut Request, res: &mut Response) {
res.render(Text::Plain(format!(
"remote address: {:?}",
req.remote_addr()
)));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(index);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---jwt-auth---
---jwt-auth/Cargo.mdx---
[package]
name = "example-jwt-auth"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
jsonwebtoken = "9"
salvo = { version = "0.77.1", features = ["anyhow", "jwt-auth"] }
serde = "1"
time = "0.3"
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---jwt-auth/src---
---jwt-auth/src/main.mdx---
use jsonwebtoken::{self, EncodingKey};
use salvo::http::{Method, StatusError};
use salvo::jwt_auth::{ConstDecoder, QueryFinder};
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};
const SECRET_KEY: &str = "YOUR SECRET_KEY";
#[derive(Debug, Serialize, Deserialize)]
pub struct JwtClaims {
username: String,
exp: i64,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let auth_handler: JwtAuth<JwtClaims, _> =
JwtAuth::new(ConstDecoder::from_secret(SECRET_KEY.as_bytes()))
.finders(vec![
// Box::new(HeaderFinder::new()),
Box::new(QueryFinder::new("jwt_token")),
// Box::new(CookieFinder::new("jwt_token")),
])
.force_passed(true);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor)
.serve(Router::with_hoop(auth_handler).goal(index))
.await;
}
#[handler]
async fn index(req: &mut Request, depot: &mut Depot, res: &mut Response) -> anyhow::Result<()> {
if req.method() == Method::POST {
let (username, password) = (
req.form::<String>("username").await.unwrap_or_default(),
req.form::<String>("password").await.unwrap_or_default(),
);
if !validate(&username, &password) {
res.render(Text::Html(LOGIN_HTML));
return Ok(());
}
let exp = OffsetDateTime::now_utc() + Duration::days(14);
let claim = JwtClaims {
username,
exp: exp.unix_timestamp(),
};
let token = jsonwebtoken::encode(
&jsonwebtoken::Header::default(),
&claim,
&EncodingKey::from_secret(SECRET_KEY.as_bytes()),
)?;
res.render(Redirect::other(format!("/?jwt_token={token}")));
} else {
match depot.jwt_auth_state() {
JwtAuthState::Authorized => {
let data = depot.jwt_auth_data::<JwtClaims>().unwrap();
res.render(Text::Plain(format!(
"Hi {}, have logged in successfully!",
data.claims.username
)));
}
JwtAuthState::Unauthorized => {
res.render(Text::Html(LOGIN_HTML));
}
JwtAuthState::Forbidden => {
res.render(StatusError::forbidden());
}
}
}
Ok(())
}
fn validate(username: &str, password: &str) -> bool {
username == "root" && password == "pwd"
}
static LOGIN_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>JWT Auth Demo</title>
</head>
<body>
<h1>JWT Auth</h1>
<form action="/" method="post">
<label for="username"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" required>
<label for="password"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" required>
<button type="submit">Login</button>
</form>
</body>
</html>
"#;
---tls-native-tls---
---tls-native-tls/certs---
---tls-native-tls/Cargo.mdx---
[package]
name = "example-tls-native-tls"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["native-tls"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---tls-native-tls/src---
---tls-native-tls/src/main.mdx---
use salvo::conn::native_tls::NativeTlsConfig;
use salvo::prelude::*;
use tracing::Level;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello World"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_max_level(Level::DEBUG)
.init();
let router = Router::new().get(hello);
let config = NativeTlsConfig::new()
.pkcs12(include_bytes!("../certs/identity.p12").to_vec())
.password("mypass");
let acceptor = TcpListener::new("0.0.0.0:5800")
.native_tls(config)
.bind()
.await;
Server::new(acceptor).serve(router).await;
}
---Cargo.mdx---
[workspace]
members = ["*"]
exclude = ["todos-utoipa", "target", "temp"]
resolver = "2"
[workspace.package]
version = "0.1.0"
authors = ["Chrislearn Young <[email protected]>"]
edition = "2024"
publish = false
rust-version = "1.85"
description = """
Salvo is a powerful web framework that can make your work easier.
"""
homepage = "https://salvo.rs"
repository = "https://github.com/salvo-rs/salvo"
documentation = "https://docs.rs/salvo/"
readme = "./README.md"
keywords = ["http", "async", "web", "framework", "server"]
license = "MIT OR Apache-2.0"
categories = ["web-programming::http-server", "web-programming::websocket", "network-programming", "asynchronous"]
[workspace.dependencies]
anyhow = "1"
rbatis = "4.5"
rbdc = "4.5"
rbdc-mysql = "4.5"
rbs = "4.5"
async-std = "1.12"
async-trait = "0.1"
sea-orm = "1"
sea-orm-migration = "1"
eyre = "0.6"
tera = "1.3"
futures = "0.3"
opentelemetry = "0.29"
opentelemetry-appender-tracing = "0.29"
opentelemetry-http = "0.29"
opentelemetry-otlp = "0.29"
opentelemetry-prometheus = "0.29"
opentelemetry_sdk = "0.29"
opentelemetry-semantic-conventions = "0.28.0"
tracing-opentelemetry = "0.28.0"
tokio-stream = "0.1.14"
async-stream = "0.3.5"
futures-util = { version = "0.3", default-features = true }
jsonwebtoken = "9"
parking_lot = "0.12"
reqwest = "0.12"
salvo = { path = "../crates/salvo", default-features = true }
serde = "1"
serde_json = "1"
thiserror = "2"
tokio = "1"
tracing = "0.1"
tracing-appender = "0.2.3"
tracing-subscriber = "0.3"
tracing-test = "0.2.1"
url = "2"
chrono = "0.4"
sqlx = "0.8"
rust-embed = "8"
time = "0.3"
---force-https---
---force-https/certs---
---force-https/Cargo.mdx---
[package]
name = "example-force-https"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["rustls", "force-https"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---force-https/src---
---force-https/src/main.mdx---
use salvo::conn::rustls::{Keycert, RustlsConfig};
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let service = Service::new(router).hoop(ForceHttps::new().https_port(5443));
let config = RustlsConfig::new(
Keycert::new()
.cert(include_bytes!("../certs/cert.pem").as_ref())
.key(include_bytes!("../certs/key.pem").as_ref()),
);
let acceptor = TcpListener::new("0.0.0.0:5443")
.rustls(config)
.join(TcpListener::new("0.0.0.0:5800"))
.bind()
.await;
Server::new(acceptor).serve(service).await;
}
---jwt-clerk---
---jwt-clerk/app---
---jwt-clerk/app/index.mdx---
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>Clerk + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.jsx"></script>
</body>
</html>
---jwt-clerk/app/styles---
---jwt-clerk/app/components---
---jwt-clerk/app/static---
---jwt-clerk/app/static/robots.mdx---
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
---jwt-clerk/vite.config.mdx---
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
root: 'app',
publicDir: 'static',
server: {
port: 5801,
strictPort : true,
},
plugins: [react()],
});
---jwt-clerk/Cargo.mdx---
[package]
name = "example-jwt-clerk"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
dotenv = "0.15.0"
jsonwebtoken = "9"
salvo = { version = "0.77.1", features = ["anyhow", "jwt-auth", "proxy"] }
serde = "1"
time = "0.3"
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---jwt-clerk/src---
---jwt-clerk/src/main.mdx---
use salvo::http::StatusError;
use salvo::jwt_auth::{ConstDecoder, HeaderFinder};
use salvo::prelude::*;
use salvo::proxy::HyperClient;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct JwtClaims {
sid: String,
sub: String,
exp: i64,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let rsa_pem = include_bytes!("../jwt_key.pem").to_vec();
let decoder = ConstDecoder::from_rsa_pem(&rsa_pem).unwrap();
let auth_handler: JwtAuth<JwtClaims, ConstDecoder> = JwtAuth::new(decoder)
.finders(vec![Box::new(HeaderFinder::new())])
.force_passed(true);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
let router = Router::new()
.push(Router::with_hoop(auth_handler).path("welcome").get(welcome))
.push(Router::with_path("{**rest}").goal(Proxy::new(
vec!["http://localhost:5801"],
HyperClient::default(),
)));
Server::new(acceptor).serve(router).await;
}
#[handler]
async fn welcome(depot: &mut Depot) -> Result<String, StatusError> {
match depot.jwt_auth_state() {
JwtAuthState::Authorized => {
let data = depot.jwt_auth_data::<JwtClaims>().unwrap();
Ok(format!(
"Hi {}, have logged in successfully!",
data.claims.sub
))
}
JwtAuthState::Unauthorized => Err(StatusError::unauthorized()),
_ => Err(StatusError::forbidden()),
}
}
---websocket-chat---
---websocket-chat/Cargo.mdx---
[package]
name = "example-websocket-chat"
version = "0.1.0"
edition = "2024"
[dependencies]
futures-util = "0.3"
salvo = { version = "0.77.1", features = ["websocket"] }
tokio = { version = "1", features = ["macros"] }
tokio-stream = { version = "0.1", features = ["net"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---websocket-chat/src---
---websocket-chat/src/main.mdx---
// Copyright (c) 2018-2020 Sean McArthur
// Licensed under the MIT license http://opensource.org/licenses/MIT
//
// port from https://github.com/seanmonstar/warp/blob/master/examples/websocket_chat.rs
use std::collections::HashMap;
use std::sync::LazyLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use futures_util::{FutureExt, StreamExt};
use tokio::sync::{RwLock, mpsc};
use tokio_stream::wrappers::UnboundedReceiverStream;
use salvo::prelude::*;
use salvo::websocket::{Message, WebSocket, WebSocketUpgrade};
type Users = RwLock<HashMap<usize, mpsc::UnboundedSender<Result<Message, salvo::Error>>>>;
static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1);
static ONLINE_USERS: LazyLock<Users> = LazyLock::new(Users::default);
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new()
.goal(index)
.push(Router::with_path("chat").goal(user_connected));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
#[handler]
async fn user_connected(req: &mut Request, res: &mut Response) -> Result<(), StatusError> {
WebSocketUpgrade::new()
.upgrade(req, res, handle_socket)
.await
}
async fn handle_socket(ws: WebSocket) {
// Use a counter to assign a new unique ID for this user.
let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed);
tracing::info!("new chat user: {}", my_id);
// Split the socket into a sender and receive of messages.
let (user_ws_tx, mut user_ws_rx) = ws.split();
// Use an unbounded channel to handle buffering and flushing of messages
// to the websocket...
let (tx, rx) = mpsc::unbounded_channel();
let rx = UnboundedReceiverStream::new(rx);
let fut = rx.forward(user_ws_tx).map(|result| {
if let Err(e) = result {
tracing::error!(error = ?e, "websocket send error");
}
});
tokio::task::spawn(fut);
let fut = async move {
ONLINE_USERS.write().await.insert(my_id, tx);
while let Some(result) = user_ws_rx.next().await {
let msg = match result {
Ok(msg) => msg,
Err(e) => {
eprintln!("websocket error(uid={my_id}): {e}");
break;
}
};
user_message(my_id, msg).await;
}
user_disconnected(my_id).await;
};
tokio::task::spawn(fut);
}
async fn user_message(my_id: usize, msg: Message) {
let msg = if let Ok(s) = msg.as_str() {
s
} else {
return;
};
let new_msg = format!("<User#{my_id}>: {msg}");
// New message from this user, send it to everyone else (except same uid)...
for (&uid, tx) in ONLINE_USERS.read().await.iter() {
if my_id != uid {
if let Err(_disconnected) = tx.send(Ok(Message::text(new_msg.clone()))) {
// The tx is disconnected, our `user_disconnected` code
// should be happening in another task, nothing more to
// do here.
}
}
}
}
async fn user_disconnected(my_id: usize) {
eprintln!("good bye user: {my_id}");
// Stream closed up, so remove from the user list
ONLINE_USERS.write().await.remove(&my_id);
}
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>WS Chat</title>
</head>
<body>
<h1>WS Chat</h1>
<div id="chat">
<p><em>Connecting...</em></p>
</div>
<input type="text" id="text" />
<button type="button" id="submit">Submit</button>
<script>
const chat = document.getElementById('chat');
const msg = document.getElementById('msg');
const submit = document.getElementById('submit');
const ws = new WebSocket(`ws://${location.host}/chat`);
ws.onopen = function() {
chat.innerHTML = '<p><em>Connected!</em></p>';
};
ws.onmessage = function(msg) {
showMessage(msg.data);
};
ws.onclose = function() {
chat.getElementsByTagName('em')[0].innerText = 'Disconnected!';
};
submit.onclick = function() {
const msg = text.value;
ws.send(msg);
text.value = '';
showMessage('<You>: ' + msg);
};
function showMessage(data) {
const line = document.createElement('p');
line.innerText = data;
chat.appendChild(line);
}
</script>
</body>
</html>
"#;
---tls-native-tls-reload---
---tls-native-tls-reload/certs---
---tls-native-tls-reload/Cargo.mdx---
[package]
name = "example-tls-native-tls-reload"
version = "0.1.0"
edition = "2024"
[dependencies]
async-stream.workspace = true
salvo = { version = "0.77.1", features = ["native-tls"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---tls-native-tls-reload/src---
---tls-native-tls-reload/src/main.mdx---
use salvo::conn::native_tls::NativeTlsConfig;
use salvo::prelude::*;
use tokio::time::Duration;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello World"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let acceptor = TcpListener::new("0.0.0.0:5800")
.native_tls(async_stream::stream! {
loop {
yield load_config();
tokio::time::sleep(Duration::from_secs(60)).await;
}
})
.bind()
.await;
Server::new(acceptor).serve(router).await;
}
fn load_config() -> NativeTlsConfig {
NativeTlsConfig::new()
.pkcs12(include_bytes!("../certs/identity.p12").to_vec())
.password("mypass")
}
---flash-session-store---
---flash-session-store/Cargo.mdx---
[package]
name = "example-flash-session-store"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features=["flash", "session"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---flash-session-store/src---
---flash-session-store/src/main.mdx---
use std::fmt::Write;
use salvo::flash::{FlashDepotExt, SessionStore};
use salvo::prelude::*;
#[handler]
pub async fn set_flash(depot: &mut Depot, res: &mut Response) {
let flash = depot.outgoing_flash_mut();
flash.info("Hey there!").debug("How is it going?");
res.render(Redirect::other("/get"));
}
#[handler]
pub async fn get_flash(depot: &mut Depot, _res: &mut Response) -> String {
let mut body = String::new();
if let Some(flash) = depot.incoming_flash() {
for message in flash.iter() {
writeln!(body, "{} - {}", message.value, message.level).unwrap();
}
}
body
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let session_handler = salvo::session::SessionHandler::builder(
salvo::session::MemoryStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
let router = Router::new()
.hoop(session_handler)
.hoop(SessionStore::new().into_handler())
.push(Router::with_path("get").get(get_flash))
.push(Router::with_path("set").get(set_flash));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---graceful-shutdown---
---graceful-shutdown/Cargo.mdx---
[package]
name = "example-graceful-shutdown"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros", "signal"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---graceful-shutdown/src---
---graceful-shutdown/src/main.mdx---
use salvo::prelude::*;
use salvo::server::ServerHandle;
use tokio::signal;
#[tokio::main]
async fn main() {
// Bind server to port 5800
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
// Create server instance
let server = Server::new(acceptor);
// Get server handle for graceful shutdown
let handle = server.handle();
// Listen Shutdown Signal
tokio::spawn(listen_shutdown_signal(handle));
// Start serving requests (empty router in this example)
server.serve(Router::new()).await;
}
async fn listen_shutdown_signal(handle: ServerHandle) {
// Wait Shutdown Signal
let ctrl_c = async {
// Handle Ctrl+C signal
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
// Handle SIGTERM on Unix systems
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(windows)]
let terminate = async {
// Handle Ctrl+C on Windows (alternative implementation)
signal::windows::ctrl_c()
.expect("failed to install signal handler")
.recv()
.await;
};
// Wait for either signal to be received
tokio::select! {
_ = ctrl_c => println!("ctrl_c signal received"),
_ = terminate => println!("terminate signal received"),
};
// Graceful Shutdown Server
handle.stop_graceful(None);
}
---oapi-upload-file---
---oapi-upload-file/Cargo.mdx---
[package]
name = "example-oapi-upload-file"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["oapi"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---oapi-upload-file/src---
---oapi-upload-file/src/main.mdx---
use std::fs::create_dir_all;
use std::path::Path;
use salvo::oapi::extract::*;
use salvo::prelude::*;
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX_HTML));
}
#[endpoint]
async fn upload(file: FormFile, res: &mut Response) {
let dest = format!("temp/{}", file.name().unwrap_or("file"));
println!("{dest}");
let info = if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
format!("file not found in request: {e}")
} else {
format!("File uploaded to {dest}")
};
res.render(Text::Plain(info));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
create_dir_all("temp").unwrap();
let router = Router::new().get(index).post(upload);
let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);
let router = router
.unshift(doc.into_router("/api-doc/openapi.json"))
.unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Upload file</title>
</head>
<body>
<h1>Upload file</h1>
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
</body>
</html>
"#;
---use-depot---
---use-depot/Cargo.mdx---
[package]
name = "example-use-depot"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---use-depot/src---
---use-depot/src/main.mdx---
use salvo::prelude::*;
#[handler]
async fn set_user(depot: &mut Depot) {
depot.insert("user", "client");
}
#[handler]
async fn hello(depot: &mut Depot) -> String {
format!(
"Hello {}",
depot.get::<&str>("user").copied().unwrap_or_default()
)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().hoop(set_user).goal(hello);
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---unix-socket---
---unix-socket/Cargo.mdx---
[package]
name = "example-unix-socket"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["serve-static"] }
tokio = { version = "1", features = ["macros"] }
---unix-socket/src---
---unix-socket/src/main.mdx---
#[cfg(target_os = "linux")]
#[tokio::main]
async fn main() {
use salvo::prelude::*;
let router = Router::with_path("files/{*path}").get(StaticDir::new("./static"));
let acceptor = UnixListener::new("/tmp/salvo.sock").bind().await;
Server::new(acceptor).serve(router).await;
}
#[cfg(not(target_os = "linux"))]
#[tokio::main]
async fn main() {
println!("please run this example in linux");
}
---logging---
---logging/Cargo.mdx---
[package]
name = "example-logging"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["logging"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---logging/src---
---logging/src/main.mdx---
use salvo::logging::Logger;
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let service = Service::new(router).hoop(Logger::new());
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(service).await;
}
---logging-otlp---
---logging-otlp/Cargo.mdx---
[package]
name = "example-logging-otlp"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
salvo = { version = "0.77.1", features = ["logging"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-opentelemetry = { workspace = true }
opentelemetry = { version = "0.28" }
opentelemetry-appender-tracing = { workspace = true }
opentelemetry-otlp = { workspace = true, features = ["grpc-tonic"] }
opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] }
opentelemetry-semantic-conventions = { version = "0.28" }
tracing-appender = { workspace = true }
---logging-otlp/src---
---logging-otlp/src/main.mdx---
use anyhow::Result;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_otlp::{LogExporter, WithExportConfig};
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::logs::SdkLoggerProvider;
use salvo::logging::Logger;
use salvo::prelude::*;
use tracing::{instrument, level_filters::LevelFilter};
use tracing_subscriber::Layer;
use tracing_subscriber::fmt::{self, format::FmtSpan};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
#[instrument(fields(http.uri = req.uri().path(), http.method = req.method().as_str()))]
#[handler]
async fn hello(req: &mut Request) -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() -> Result<()> {
// console layer for tracing-subscriber
let console = fmt::Layer::new()
.with_span_events(FmtSpan::CLOSE)
.pretty()
.with_filter(LevelFilter::DEBUG);
// file appender layer for tracing-subscriber
let file_appender = tracing_appender::rolling::daily("./logs", "salvo.log");
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
let file = fmt::Layer::new()
.with_writer(non_blocking)
.pretty()
.with_filter(LevelFilter::INFO);
// opentelemetry tracing layer for tracing-subscriber
let provider = init_tracer_provider()?;
tracing_subscriber::registry()
.with(console)
.with(file)
.with(OpenTelemetryTracingBridge::new(&provider))
.init();
let router = Router::new().get(hello);
let service = Service::new(router).hoop(Logger::new());
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(service).await;
Ok(())
}
fn init_tracer_provider() -> anyhow::Result<SdkLoggerProvider> {
let exporter = LogExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:4317")
.build()?;
let provider = SdkLoggerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(
Resource::builder()
.with_service_name("salvo-tracing")
.build(),
)
.build();
Ok(provider)
}
---db-mysql-rbatis---
---db-mysql-rbatis/Cargo.mdx---
[package]
edition = "2024"
name = "example-db-mysql-rbatis"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std.workspace = true
rbatis.workspace = true
rbdc.workspace = true
rbdc-mysql.workspace = true
rbs.workspace = true
salvo = { version = "0.77.1" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
serde_json = "1"
---db-mysql-rbatis/src---
---db-mysql-rbatis/src/main.mdx---
#[macro_use]
extern crate rbatis;
extern crate rbdc;
use std::sync::LazyLock;
use rbatis::RBatis;
use rbdc_mysql::driver::MysqlDriver;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
// Global RBatis instance for database operations
pub static RB: LazyLock<RBatis> = LazyLock::new(RBatis::new);
// User model representing the database table structure
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct User {
pub id: i64,
pub username: String,
pub password: String,
}
// Implement select query for User model
// Generates SQL: SELECT * FROM user WHERE id = #{id} LIMIT 1
impl_select!(User{select_by_id(id:String) -> Option => "`where id = #{id} limit 1`"});
// Handler for retrieving a user by ID from the database
#[handler]
pub async fn get_user(req: &mut Request, res: &mut Response) {
// Extract user ID from query parameters
let uid = req.query::<i64>("uid").unwrap();
// Execute select query and get user data
let data = User::select_by_id(&*RB, uid.to_string()).await.unwrap();
println!("{data:?}");
// Return user data as JSON response
res.render(serde_json::to_string(&data).unwrap());
}
#[tokio::main]
async fn main() {
// Initialize logging system
tracing_subscriber::fmt().init();
// Configure MySQL connection
let mysql_uri = "mysql://root:123456@localhost/test";
// Initialize RBatis with MySQL driver and connection URI
RB.init(MysqlDriver {}, mysql_uri).unwrap();
// Configure router with user endpoint:
// - GET /users?uid={id} : Get user by ID
let router = Router::with_path("users").get(get_user);
// Start server on port 5800
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---extract-with-lifetimes---
---extract-with-lifetimes/Cargo.mdx---
[package]
name = "example-extract-with-lifetimes"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---extract-with-lifetimes/src---
---extract-with-lifetimes/src/main.mdx---
use salvo::macros::Extractible;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
#[handler]
async fn show(req: &mut Request, res: &mut Response) {
let content = format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>Parse data</title>
</head>
<body>
<h1>Hello, fill your profile</h1>
<form action="/{}?username=jobs" method="post">
<label>First Name:</label><input type="text" name="first_name" />
<label>Last Name:</label><input type="text" name="last_name" />
<legend>What is Your Favorite Pet?</legend>
<input type="checkbox" name="lovers" value="Cats">Cats<br>
<input type="checkbox" name="lovers" value="Dogs">Dogs<br>
<input type="checkbox" name="lovers" value="Birds">Birds<br>
<input type="submit" value="Submit" />
</form>
</body>
</html>
"#,
req.params().get("id").unwrap()
);
res.render(Text::Html(content));
}
#[handler]
async fn edit<'a>(bad_man: BadMan<'a>) -> String {
format!("{bad_man:#?}")
}
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(
default_source(from = "query"),
default_source(from = "param"),
default_source(from = "body")
)]
struct BadMan<'a> {
id: i64,
username: String,
first_name: &'a str,
last_name: String,
lovers: Vec<String>,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_path("{id}").get(show).post(edit);
println!("Example url: http://0.0.0.0:5800/95");
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---trailing-slash---
---trailing-slash/Cargo.mdx---
[package]
name = "example-trailing-slash"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["trailing-slash"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---trailing-slash/src---
---trailing-slash/src/main.mdx---
use salvo::prelude::*;
use salvo::trailing_slash::add_slash;
#[handler]
async fn hello() -> &'static str {
"Hello"
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::with_hoop(add_slash())
.push(Router::with_path("hello").get(hello))
.push(Router::with_path("hello.world").get(hello));
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
---request-id---
---request-id/Cargo.mdx---
[package]
name = "example-request-id"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["request-id"]}
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---request-id/src---
---request-id/src/main.mdx---
use salvo::prelude::*;
#[handler]
async fn hello(req: &mut Request) -> String {
format!("Request id: {:?}", req.header::<String>("x-request-id"))
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let acceptor = TcpListener::new("0.0.0.0:5800").bind().await;
let router = Router::new().hoop(RequestId::new()).get(hello);
Server::new(acceptor).serve(router).await;
}
---acme-tls-alpn01---
---acme-tls-alpn01/Cargo.mdx---
[package]
name = "example-acme-tls-alpn01"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.77.1", features = ["acme"] }
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
---acme-tls-alpn01/src---
---acme-tls-alpn01/src/main.mdx---
use salvo::prelude::*;
// This handler function responds with "Hello World" to any incoming request
#[handler]
async fn hello() -> &'static str {
"Hello World"
}
#[tokio::main]
async fn main() {
// Initialize the tracing subscriber for logging
tracing_subscriber::fmt().init();
// Create a new router and register the hello handler
let router = Router::new().get(hello);
// Set up a TCP listener on port 443 for HTTPS
let acceptor = TcpListener::new("0.0.0.0:443")
.acme() // Enable ACME for automatic SSL certificate management
// .cache_path("temp/letsencrypt") // Specify the path to store the certificate cache (uncomment if needed)
.add_domain("test.salvo.rs") // Replace this domain name with your own
.bind()
.await;
// Start the server with the configured acceptor and router
Server::new(acceptor).serve(router).await;
}