llm-code-example

---concurrency-limiter---

---concurrency-limiter/Cargo.mdx---

concurrency-limiter/Cargo.toml
[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---

concurrency-limiter/src/main.rs
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---

csrf-session-store/Cargo.toml
[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---

csrf-session-store/src/main.rs
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---

webtransport/static/client.html
<!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---

webtransport/Cargo.toml
[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---

webtransport/src/main.rs
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---

catch-error/Cargo.toml
[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---

catch-error/src/main.rs
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---

join-listeners/Cargo.toml
[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---

join-listeners/src/main.rs
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---

oapi-todos/Cargo.toml
[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---

oapi-todos/src/main.rs
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---

db-mongodb/Cargo.toml
[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---

db-mongodb/src/main.rs
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---

websocket/Cargo.toml
[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---

websocket/src/main.rs
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---

sse/Cargo.toml
[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---

sse/src/main.rs
// 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---

oapi-hello/Cargo.toml
[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---

oapi-hello/src/main.rs
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---

static-embed-file/static/test2.txt
copy2

---static-embed-file/static/test1.mdx---

static-embed-file/static/test1.txt
copy1

---static-embed-file/Cargo.mdx---

static-embed-file/Cargo.toml
[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---

static-embed-file/src/main.rs
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---

tls-rustls-reload/Cargo.toml
[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---

tls-rustls-reload/src/main.rs
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---

craft/Cargo.toml
[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---

craft/src/main.rs
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---

affix-state/Cargo.toml
[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---

affix-state/src/main.rs
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---

size-limiter/Cargo.toml
[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---

size-limiter/src/main.rs
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---

tls-openssl/Cargo.toml
[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---

tls-openssl/src/main.rs
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---

webtransport-acme-http01/static/client.html
<!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---

webtransport-acme-http01/Cargo.toml
[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---

webtransport-acme-http01/src/main.rs
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---

acme-http01-quinn/Cargo.toml
[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---

acme-http01-quinn/src/main.rs
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---

caching-headers/Cargo.toml
[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---

caching-headers/src/main.rs
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---

basic-auth/Cargo.toml
[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---

basic-auth/src/main.rs
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---

rate-limiter-dynamic/Cargo.toml
[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---

rate-limiter-dynamic/src/main.rs
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---

oapi-generics/Cargo.toml
[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---

oapi-generics/src/main.rs
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---

upload-files/Cargo.toml
[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---

upload-files/src/main.rs
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---

todos-utoipa/Cargo.toml
[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---

todos-utoipa/src/main.rs
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---

catch-panic/Cargo.toml
[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---

catch-panic/src/main.rs
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---

proxy-react-app/react-app/public/index.html
<!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---

proxy-react-app/react-app/public/robots.txt
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

---proxy-react-app/react-app/src---

---proxy-react-app/react-app/src/App.test.mdx---

proxy-react-app/react-app/src/App.test.js
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---

proxy-react-app/react-app/src/setupTests.js
// 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---

proxy-react-app/react-app/src/index.js
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---

proxy-react-app/react-app/src/App.js
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---

proxy-react-app/react-app/src/reportWebVitals.js
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---

proxy-react-app/Cargo.toml
[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---

proxy-react-app/src/main.rs
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---

proxy-websocket/Cargo.toml
[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---

proxy-websocket/src/main.rs
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---

timeout/Cargo.toml
[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---

timeout/src/main.rs
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---

tls-openssl-reload/Cargo.toml
[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---

tls-openssl-reload/src/main.rs
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---

proxy-simple/Cargo.toml
[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---

proxy-simple/src/main.rs
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---

middleware-add-header/Cargo.toml
[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---

middleware-add-header/src/main.rs
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---

custom-error-page/Cargo.toml
[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---

custom-error-page/src/main.rs
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---

hello-h3/Cargo.toml
[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---

hello-h3/src/main.rs
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---

with-sentry/Cargo.toml
[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---

with-sentry/src/main.rs
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---

flash-cookie-store/Cargo.toml
[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---

flash-cookie-store/src/main.rs
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---

template-askama/templates/hello.html
Hello, {{ name }}!

---template-askama/Cargo.mdx---

template-askama/Cargo.toml
[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---

template-askama/src/main.rs
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---

extract-data/Cargo.toml
[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---

extract-data/src/main.rs
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---

with-listenfd/Cargo.toml
[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---

with-listenfd/src/main.rs
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---

multi-servers/Cargo.toml
[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---

multi-servers/src/main.rs
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---

fuse-attack/Cargo.toml
[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---

fuse-attack/src/main.rs
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---

session-login/Cargo.toml
[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---

session-login/src/main.rs
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---

acme-http01/Cargo.toml
[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---

acme-http01/src/main.rs
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---

sse-chat/Cargo.toml
[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---

sse-chat/src/main.rs
// 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---

db-postgres-sqlx/Cargo.toml
[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---

db-postgres-sqlx/src/main.rs
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---

db-sea-orm/Cargo.toml
[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---

db-sea-orm/src/entity/post.rs
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---

db-sea-orm/src/entity/mod.rs
pub mod post;

---db-sea-orm/src/main.mdx---

db-sea-orm/src/main.rs
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---

db-sea-orm/src/migration/m20220120_000001_create_post_table.rs
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---

db-sea-orm/src/migration/main.rs
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---

db-sea-orm/src/migration/mod.rs
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---

with-tower/Cargo.toml
[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---

with-tower/src/main.rs
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---

upload-file/Cargo.toml
[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---

upload-file/src/main.rs
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---

compression/static/ws_chat.txt
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---

compression/static/todos.txt
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---

compression/static/sse_chat.txt
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---

compression/Cargo.toml
[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---

compression/src/main.rs
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---

cors/Cargo.toml
[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---

cors/src/main.rs
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---

redirect/Cargo.toml
[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---

redirect/src/main.rs
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;
}