Cross-Origin Control
CORS (Cross-Origin Resource Sharing) is a mechanism that allows browsers to make requests to cross-origin servers, thereby overcoming the restrictions imposed by the browser's same-origin policy.
What is the Same-Origin Policy?
The same-origin policy is a security feature of browsers that restricts how documents or scripts loaded from one origin can interact with resources from another origin. "Same origin" refers to having the same protocol, domain, and port number.
Why is CORS Needed?
CORS support is required when a frontend application needs to access APIs from a different origin. For example:
- The frontend application is deployed at
https://frontend.com
- The API service is deployed at
https://api.backend.com
Without CORS, the browser will prevent the frontend application from accessing the API service.
How CORS Works
CORS implements cross-origin access control through a series of HTTP headers:
- Simple Requests: Sent directly, with the server controlling whether access is allowed via response headers.
- Preflight Requests: The browser first sends an OPTIONS request to ask the server if cross-origin access is allowed, and only sends the actual request after receiving permission.
Since the browser sends preflight requests with Method::OPTIONS, it is necessary to handle such requests. The CORS middleware must be added to the Service.
Using CORS in Salvo
Salvo provides built-in CORS middleware that can be easily configured and used. Here is an example code snippet:
Warning
Note: The .hoop(cors) middleware is applied to the Service, not the Router. This ensures automatic handling of OPTIONS preflight requests.
let cors = Cors::new()
.allow_origin(["http://127.0.0.1:8698", "http://localhost:8698"])
.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);
Example Code
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:8698
// - Allow specific HTTP methods
// - Allow authorization header
let cors = Cors::new()
.allow_origin(["http://127.0.0.1:8698", "http://localhost:8698"])
.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 8698
let acceptor = TcpListener::new("0.0.0.0:8698").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>
"#;
[package]
name = "example-cors"
version.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true
[dependencies]
salvo = { workspace = true, features = ["cors"] }
tokio = { workspace = true, features = ["macros"] }
tracing.workspace = true
tracing-subscriber.workspace = true
Main Configuration Options
The CORS middleware provides various configuration options:
- Allowed Origins: Controls which domains can access resources.
- Allowed Methods: Specifies allowed HTTP methods (GET, POST, etc.).
- Allowed Headers: Specifies allowed request headers.
- Exposed Headers: Specifies which response headers can be accessed by the client.
- Allow Credentials: Determines whether requests can include credentials such as cookies.
- Preflight Request Cache Time: The cache duration for preflight request results.
By configuring CORS appropriately, you can ensure both security and the ability to meet cross-origin access requirements.