当前位置: 首页 > news >正文

【Actix Web】Rust Web开发实战:Actix Web框架全面指南

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Rust语言通关之路
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Rust Web开发
    • 一、Actix Web框架概述
      • 1.1 Actix Web的特点
      • 1.2 Actix Web与其他Rust框架比较
    • 二、环境准备与项目创建
      • 2.1 添加Actix Web依赖
    • 三、基本Web服务器
      • 3.1 最简单的HTTP服务器
    • 四、路由系统详解
      • 4.1 基本路由
      • 4.2 支持多种HTTP方法
      • 4.3 路径参数
      • 4.4 查询参数
      • 4.5 通配符和前缀
    • 五、请求体与响应处理
      • 5.1 处理JSON数据
      • 5.2 表单处理
        • 5.2.1普通处理表单提交
        • 5.2.2 Actix Web文件上传
        • 5.2.3 大文件上传内存问题
        • 5.2.4 文件上传超时
      • 5.3 大文件下载
    • 六、跨域静态资源
      • 🧠 1. 什么是 CORS?
      • 📦 2. 添加依赖
      • 🚀 3. 基本用法
      • ⚙️ 4. 自定义跨域策略(推荐)
      • 🧪 5. 常见配置参数详解
      • 🛡️ 6. 生产环境注意事项

Rust Web开发

一、Actix Web框架概述

Actix Web是Rust生态中最受欢迎的高性能Web框架之一,它构建在强大的Actix actor框架之上,提供了构建现代Web应用所需的完整工具集。
Actix Web以其卓越的性能、安全性和易用性在Rust社区中广受好评。

1.1 Actix Web的特点

高性能:Actix Web在TechEmpower基准测试中 consistently排名靠前
类型安全:充分利用Rust的类型系统在编译期捕获错误
异步支持:基于async/await语法构建,支持高并发
灵活的路由系统:直观的路由定义和强大的请求处理
中间件支持:可组合的中间件系统用于横切关注点
WebSocket支持:内置WebSocket支持用于实时应用

1.2 Actix Web与其他Rust框架比较

与其他Rust Web框架如Rocket、Warp等相比,Actix Web在性能与功能丰富性之间取得了良好的平衡。它比Rocket更灵活,比Warp更易上手,同时保持了极高的性能标准。

二、环境准备与项目创建

2.1 添加Actix Web依赖

在Cargo.toml中添加以下依赖:

[dependencies]
actix-rt = "2.10.0"  # Actix运行时
actix-web = "4.11.0"
serde = { version = "1.0.219", features = ["derive"] }  # 用于序列化

三、基本Web服务器

3.1 最简单的HTTP服务器

让我们从最基本的例子开始,创建一个返回"Hello World!"的服务器:

use actix_web::{ get, App, HttpResponse, HttpServer, Responder };//异步函数,上面加请求方法和路径
//方法返回值是Responder trait对象
#[get("/")]
async fn hello() -> impl Responder {//响应体内容HttpResponse::Ok().body("Hello World!")
}//main函数也要设置成异步
//actix_web::main宏会自动设置main函数为异步
#[actix_web::main]
async fn main() -> std::io::Result<()> {//通过HttpServer::new创建一个HTTP服务器,闭包返回一个App实例//App::new().service(hello)注册hello函数为服务//bind绑定地址和端口//run启动服务器//await等待服务器运行结束println!("Server running at http://127.0.0.1:8080");HttpServer::new(|| { App::new().service(hello) }).bind("127.0.0.1:8080")?.run().await
}

这段代码做了以下几件事:
定义了一个异步处理函数hello
使用#[get(“/”)]宏将其注册为根路径的GET方法处理器
创建了一个HttpServer实例
在本地8080端口启动服务器
在这里插入图片描述

浏览器访问
在这里插入图片描述

四、路由系统详解

Actix Web提供了强大而灵活的路由系统,支持各种HTTP方法和路径模式。

4.1 基本路由

除了使用属性宏,你还可以手动注册路由:
使用 .route():

use actix_web::{ web, App, HttpResponse, HttpServer }; //使用web来注册路由async fn manual_hello() -> HttpResponse {HttpResponse::Ok().body("Hey there!")
}#[actix_web::main]
async fn main() -> std::io::Result<()> {//使用.route("/hey", web::get().to(manual_hello))注册路由//web::get()表示GET请求//to(manual_hello)表示调用manual_hello函数HttpServer::new(|| { App::new().route("/hey", web::get().to(manual_hello)) }).bind("127.0.0.1:8080")?.run().await
}

访问
在这里插入图片描述

4.2 支持多种HTTP方法

Actix Web支持所有标准HTTP方法:

.route("/resource", web::get().to(get_handler))
.route("/resource", web::post().to(post_handler))
.route("/resource", web::put().to(put_handler))
.route("/resource", web::delete().to(delete_handler))

4.3 路径参数

方法一:使用元组提取多个参数
你可以从URL路径中提取参数:
在异步方法宏的路径里面设置路径参数
并通过path.into_inner()方法获取

use actix_web::{ get, App, HttpServer, Responder, web };//异步函数,上面加请求方法和路径
//方法返回值是Responder trait对象
//提取路径参数#[get("/users/{user_id}/{friend}")]
async fn user_info(path: web::Path<(u32, String)>) -> impl Responder {//获取路径参数let (user_id, friend) = path.into_inner();//也可以用format!宏返回字符串响应体,与HttpResponse::Ok().body()效果相同format!("User {} and friend {}!", user_id, friend)// HttpResponse::Ok().body(format!("User {} and friend {}!", user_id, friend))
}//main函数也要设置成异步
//actix_web::main宏会自动设置main函数为异步
#[actix_web::main]
async fn main() -> std::io::Result<()> {//通过HttpServer::new创建一个HTTP服务器,闭包返回一个App实例//App::new().service(hello)注册hello函数为服务//bind绑定地址和端口//run启动服务器//await等待服务器运行结束println!("Server running at http://127.0.0.1:8080");HttpServer::new(|| { App::new().service(user_info) }).bind("127.0.0.1:8080")?.run().await
}

在这里插入图片描述

方法二:使用结构体提取

use actix_web::{ get, App, HttpServer, Responder, web };//使用结构体来提取路径参数
#[derive(serde::Deserialize)]
struct UserInfo {user_id: u32,friend: String,
}#[get("/users/{user_id}/{friend}")]
async fn user_info_struct(path: web::Path<UserInfo>) -> impl Responder {//通过结构体来提取路径参数format!("User {} and friend {}!", path.user_id, path.friend)
}//main函数也要设置成异步
//actix_web::main宏会自动设置main函数为异步
#[actix_web::main]
async fn main() -> std::io::Result<()> {//通过HttpServer::new创建一个HTTP服务器,闭包返回一个App实例//App::new().service(hello)注册hello函数为服务//bind绑定地址和端口//run启动服务器//await等待服务器运行结束println!("Server running at http://127.0.0.1:8080");HttpServer::new(|| { App::new().service(user_info_struct) }).bind("127.0.0.1:8080")?.run().await
}

在这里插入图片描述

方法三:手动从 HttpRequest 中解析
使用.route获取路径参数

use actix_web::{ App, HttpServer, HttpResponse, HttpRequest, Responder, web };async fn greet(req: HttpRequest) -> impl Responder {//通过req.match_info().get("name")获取路径参数//get("name")中的name要与路径参数{name}中的name保持一致//unwrap_or("World")表示如果路径参数不存在,就使用默认值Worldlet name = req.match_info().get("name").unwrap_or("World");HttpResponse::Ok().body(format!("Hello, {}!", name))
}//使用.route获取路径参数
#[actix_web::main]
async fn main() -> std::io::Result<()> {//使用.route("/hey/{name}", web::get().to(greet))注册动态路径参数//web::get()表示GET请求//to(greet)表示调用greet函数HttpServer::new(|| { App::new().route("/hey/{name}", web::get().to(greet)) }).bind("127.0.0.1:8080")?.run().await
}

在这里插入图片描述

小结:
在这里插入图片描述

4.4 查询参数

在 Actix Web 中获取查询参数(Query Parameters)的方法非常直观,主要有两种方式:

✅ 方法一:使用 web::Query 自动反序列化
这是最常用和推荐的方式,借助 serde 自动将 URL 查询参数解析成结构体。

//查询参数获取
use actix_web::{ web, App, HttpResponse, HttpServer, Responder };
use serde::Deserialize;#[derive(Deserialize)]
struct Info {name: String,age: Option<u8>, // 可以是可选的
}async fn query_handler(query: web::Query<Info>) -> impl Responder {//这里采用引用的方式,避免数据的移动。因为String类型没有实现Copy traitlet name = &query.name;//age是可选的,所以需要unwrap_or来处理,如果没有传递age参数,则默认为0let age = query.age.unwrap_or(0);HttpResponse::Ok().body(format!("Name: {}, Age: {}", name, age))
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| { App::new().route("/search", web::get().to(query_handler)) }).bind(("127.0.0.1", 8080))?.run().await
}

在这里插入图片描述

如果查询参数没有age,就取默认值
在这里插入图片描述

通过宏配置路由也行

//查询参数获取
use actix_web::{ web, App, HttpResponse, HttpServer, Responder, get };
use serde::Deserialize;#[derive(Deserialize)]
struct Info {name: String,age: Option<u8>, // 可以是可选的
}#[get("/search")]
async fn query_handler(query: web::Query<Info>) -> impl Responder {//这里采用引用的方式,避免数据的移动。因为String类型没有实现Copy traitlet name = &query.name;//age是可选的,所以需要unwrap_or来处理,如果没有传递age参数,则默认为0let age = query.age.unwrap_or(0);HttpResponse::Ok().body(format!("Name: {}, Age: {}", name, age))
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| { App::new().service(query_handler) }).bind(("127.0.0.1", 8080))?.run().await
}

✅ 方法二:手动从 HttpRequest 获取字符串形式
这种方式适用于只想取部分参数、不确定字段名、或不使用结构体的情况。

//查询参数获取
use actix_web::{ App, HttpResponse, HttpServer, Responder, get, HttpRequest };//手动获取查询参数
#[get("/search")]
async fn query_manual(req: HttpRequest) -> impl Responder {//通过HttpRequest.query_string()获取查询字符串let query = req.query_string(); // 返回原始字符串,例如 "name=tom&age=20"HttpResponse::Ok().body(format!("Query string: {}", query))
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| { App::new().service(query_manual) }).bind(("127.0.0.1", 8080))?.run().await
}

在这里插入图片描述

你也可以手动解析成键值对:
通过serde_urlencoded解析

依赖:

[dependencies]
actix-rt = "2.10.0"
actix-web = "4.11.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_urlencoded = "0.7.1"
//查询参数获取,并解析
use actix_web::{ App, HttpResponse, HttpServer, Responder, get, HttpRequest };
use std::collections::HashMap;
#[get("/search")]
async fn query_map(req: HttpRequest) -> impl Responder {//通过serde_urlencoded::from_str()将查询字符串转换为HashMaplet params: HashMap<String, String> = serde_urlencoded::from_str(req.query_string()).unwrap_or_default();//从HashMap中获取参数值,如果参数不存在,则使用默认值let name = params.get("name").cloned().unwrap_or("Unknown".into());let age = params.get("age").cloned().unwrap_or("0".into());HttpResponse::Ok().body(format!("Name: {}, Age: {}", name, age))
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| { App::new().service(query_map) }).bind(("127.0.0.1", 8080))?.run().await
}

在这里插入图片描述

🔁 小结对比
在这里插入图片描述

4.5 通配符和前缀

Actix Web支持复杂的路径模式:

// 匹配所有以"/path/"开头的URL
.route("/path/{tail:.*}", web::get().to(handler))// 匹配数字ID
.route("/user/{id:[0-9]+}", web::get().to(handler))
use actix_web::{ App, HttpServer, HttpResponse, HttpRequest, Responder, get };//匹配以hey开头的任意路径
#[get("/hey/{name:.*}")]
async fn greet(req: HttpRequest) -> impl Responder {//通过req.match_info().get("name")获取路径参数//unwrap_or("World")表示如果路径参数不存在,就使用默认值Worldlet name = req.match_info().get("name").unwrap_or("World");HttpResponse::Ok().body(format!("Hello, {}!", name))
}//使用.route获取路径参数
#[actix_web::main]
async fn main() -> std::io::Result<()> {//使用.route("/hey/{name}", web::get().to(greet))注册动态路径参数//web::get()表示GET请求//to(greet)表示调用greet函数HttpServer::new(|| { App::new().service(greet) }).bind("127.0.0.1:8080")?.run().await
}

在这里插入图片描述

五、请求体与响应处理

5.1 处理JSON数据

Actix Web内置了对JSON的支持:

//处理json请求
use actix_web::{ web, App, HttpServer, Responder, get, post };
use serde::{ Deserialize, Serialize };#[derive(Serialize, Deserialize, Debug)]
struct MyObj {name: String,number: i32,
}// get请求返回JSON响应
#[get("/get-json")]
async fn get_json() -> web::Json<MyObj> {println!("get_json");//通过web::Json()将MyObj转换为JSON响应web::Json(MyObj {name: "Test".to_string(),number: 42,})
}// post请求接收JSON请求体
#[post("/receive-json")]
async fn receive_json(obj: web::Json<MyObj>) -> impl Responder {println!("Received: {:?}", obj);format!("Received {} with number {}", obj.name, obj.number)
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| { App::new().service(get_json).service(receive_json) }).bind(("127.0.0.1", 8080))?.run().await
}

get请求
在这里插入图片描述

post请求
在这里插入图片描述

5.2 表单处理

5.2.1普通处理表单提交

💡 注意事项
表单请求的 Content-Type 必须为 application/x-www-form-urlencoded。
不支持 multipart/form-data(文件上传)时需使用 actix-multipart,那是另一类处理。

//处理表单请求
use actix_web::{ web, App, HttpServer, Responder, post };
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct FormData {username: String,password: String,
}#[post("/login")]
async fn login(form: web::Form<FormData>) -> impl Responder {println!("Received: {:?}", form);format!("Welcome {}, your password is {}!", form.username, form.password)
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| { App::new().service(login) }).bind(("127.0.0.1", 8080))?.run().await
}

在这里插入图片描述

5.2.2 Actix Web文件上传

📦 Actix Web 中的文件上传(multipart/form-data)
✅ 使用 actix-multipart 处理 multipart/form-data

表单中如果使用 <input type="file">,表单必须设置 enctype=“multipart/form-data”,对应 HTTP 请求的 Content-Type 是 multipart/form-data,这时 Actix Web 需要借助:
actix-multipart:解析 multipart 表单。
futures-util:异步流支持。
tokio::fs:异步保存文件。

actix_files::Files
可以开启静态文件服务

基本配置选项

Files::new("/static", "./static").index_file("index.html")       // 默认索引文件.prefer_utf8(true)             // 优先使用UTF-8编码.use_last_modified(true)       // 启用Last-Modified头.use_etag(true)                // 启用ETag缓存.disable_content_disposition() // 禁用自动添加Content-Disposition头

Cargo.toml依赖:

[dependencies]
actix-files = "0.6.6"
actix-multipart = "0.7.2"
actix-rt = "2.10.0"
actix-web = "4.11.0"
futures-util = "0.3.31"
sanitize-filename = "0.6.0"
serde = { version = "1.0.219", features = ["derive"] }
tokio = { version = "1.45.1", features = ["full"] }

完整代码

//文件上传
use actix_multipart::Multipart;
use actix_web::{ App, HttpResponse, HttpServer, Result, web };
use futures_util::StreamExt;
use sanitize_filename::sanitize;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;  // 大文件异步写入文件
use actix_files::Files;
use serde::Serialize;//创建个结构体来保存用户名和文件路径
#[derive(Serialize, Debug)]
struct UploadResponse {username: String,uploaded_files: Vec<String>,
}async fn upload(mut payload: Multipart) -> Result<HttpResponse> {// 保存用户名和文件路径let mut username = String::new();//保存文件路径let mut uploaded_files = Vec::new();// 遍历表单所有字段while let Some(field) = payload.next().await {// 获取字段let mut field = field?;// 获取表单字段名if let Some(content_disposition) = field.content_disposition() {if let Some(name) = content_disposition.get_name() {// 根据字段名处理不同的字段,这里的字段就是前端上传文件时,form-data里面的字段match name {// 处理用户名字段"username" => {while let Some(chunk) = field.next().await {let data = chunk?;username.push_str(&String::from_utf8_lossy(&data));println!("Received username: {}", username);}}// 处理文件字段"avatar" => {if let Some(filename) = content_disposition.get_filename() {// 保存文件//sanitize 将用户提供的原始文件名转换成安全、合法的文件名。它的核心作用是防止文件名注入攻击和确保文件系统兼容性。let filename = sanitize(filename);let filepath = format!("./uploads/{}", filename);// 创建文件let mut f = File::create(&filepath).await?;// 读取文件数据并写入到文件中while let Some(chunk) = field.next().await {let data = chunk?;f.write_all(&data).await?;}uploaded_files.push(filepath);// 打印保存的文件路径//每次保存一个文件后,都会把文件路径 filepath 添加到这个向量的末尾://使用 last() 可以确保你打印的是刚刚保存的那个文件的路径,而不是之前保存的文件路径。println!("Received file: {}", uploaded_files.last().unwrap());}}// 处理未知字段_ => {username.push_str(&format!("Unknown field: {}\n", name));uploaded_files = vec!["Unknown field".to_string()];}}}}}// 返回json响应Ok(HttpResponse::Ok().json(UploadResponse { username, uploaded_files }))
}#[actix_web::main]
async fn main() -> std::io::Result<()> {// 创建uploads目录,类似mkdir -p。乐意递归创建目录std::fs::create_dir_all("./uploads")?;HttpServer::new(|| {App::new().route("/upload", web::post().to(upload)).service(Files::new("/login", "./public").index_file("upload.html")) // 访问根/路径,展示html页面.service(Files::new("/uploads", "./uploads").show_files_listing()) // 静态文件服务,可以访问/uploads目录下的文件}).bind(("127.0.0.1", 8080))?.run().await
}

可以上传单文件,也可以上传多文件
在这里插入图片描述

开启静态服务注意事项
在这里插入图片描述

5.2.3 大文件上传内存问题

问题:上传大文件时内存占用过高。
解决方案:
使用流式处理,不要一次性加载整个文件到内存
设置合理的缓冲区大小
使用tokio::fs::File进行异步文件IO

use tokio::io::AsyncWriteExt;while let Some(chunk) = field.next().await {let data = chunk?;file.write_all(&data).await?;
}
5.2.4 文件上传超时

问题:慢速连接导致上传超时。

解决方案:
设置合理的超时时间
提供进度反馈

use actix_web::web::PayloadConfig;// 设置30分钟超时
App::new().app_data(PayloadConfig::new(10_000_000) // 10MB.timeout(std::time::Duration::from_secs(1800)))

5.3 大文件下载

在 Actix Web 中实现 大文件下载(如视频、安装包等),你需要使用 分块流式传输(chunked streaming) 的方式发送文件,以避免一次性将整个文件加载进内存。
🧠 1)目标
✅ 支持 大文件下载(>1GB 也没问题)
✅ 使用低内存占用方式读取(流式读取)
✅ 支持设置响应头(如 Content-Disposition)触发浏览器下载

📦 2)添加依赖

[dependencies]
actix-web = "4.11.0"
tokio = { version = "1.45.1", features = ["full"] }
tokio-util = "0.7.15"

🚀 3. 完整代码:流式下载大文件

use actix_web::{ web, HttpResponse, Result, get, App, HttpServer };
use tokio::fs::File;
use tokio_util::io::ReaderStream;
use actix_web::http::{ header, StatusCode };#[get("/download/{filename}")]
async fn download_file(path: web::Path<String>) -> Result<HttpResponse> {let filename = path.into_inner();let file_path = format!("./downloads/{}", filename);// 尝试打开文件let file = match File::open(&file_path).await {Ok(f) => f,Err(_) => {return Ok(HttpResponse::NotFound().body("File not found"));}};// 创建 tokio 异步流let stream = ReaderStream::new(file);// 设置响应头let content_disposition = format!("attachment; filename=\"{}\"", filename);Ok(HttpResponse::build(StatusCode::OK).append_header((header::CONTENT_TYPE, "application/octet-stream")).append_header((header::CONTENT_DISPOSITION, content_disposition)).streaming(stream))
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| { App::new().service(download_file) }).bind(("127.0.0.1", 8080))?.run().await
}

🌐 4. 浏览器访问下载

在这里插入图片描述

六、跨域静态资源

在使用 Actix Web 构建前后端分离的 Web 应用时,经常会遇到 跨域请求(CORS) 的问题。为了解决这个问题,Actix 提供了专用中间件:actix-cors。

🧠 1. 什么是 CORS?

CORS(Cross-Origin Resource Sharing) 是一种机制,用于允许浏览器从一个域名的网页请求另一个域名的资源。

例如:
前端运行在 http://localhost:3000
后端(Actix Web)运行在 http://127.0.0.1:8080
默认情况下,浏览器不允许跨域请求,除非服务器明确设置允许。

📦 2. 添加依赖

在你的 Cargo.toml 中添加:

[dependencies]
actix-cors = "0.7.1"
actix-web = "4.11.0"

🚀 3. 基本用法

✅ 最简单的 CORS 配置(开发用)

use actix_cors::Cors;
use actix_web::{App, HttpServer, web, HttpResponse};#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| {App::new().wrap(Cors::permissive() // 允许所有跨域请求(不推荐用于生产)).route("/api/data", web::get().to(|| async {HttpResponse::Ok().body("hello from actix")}))}).bind(("127.0.0.1", 8080))?.run().await
}

⚙️ 4. 自定义跨域策略(推荐)

use actix_cors::Cors;
use actix_web::{http, App, HttpServer, HttpResponse, web};#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| {let cors = Cors::default().allowed_origin("http://localhost:3000") // 指定允许的前端地址.allowed_methods(vec!["GET", "POST", "PUT", "DELETE"]) // 允许的方法.allowed_headers(vec![http::header::CONTENT_TYPE, http::header::AUTHORIZATION]).allow_credentials(true) // 允许携带 cookie.max_age(3600); // 预检请求缓存时间(秒)App::new().wrap(cors).route("/api/hello", web::get().to(|| async {HttpResponse::Ok().body("Hello from CORS!")}))}).bind(("127.0.0.1", 8080))?.run().await
}#[actix_web::main]
async fn main() -> std::io::Result<()> {// 创建uploads目录,类似mkdir -p。乐意递归创建目录std::fs::create_dir_all("./uploads")?;HttpServer::new(|| {App::new()//设置跨域.wrap(Cors::default().allow_any_origin().allow_any_method().allow_any_header()) //允许所有源,所有方法,所有请求头.route("/upload", web::post().to(upload)).service(Files::new("/login", "./public").index_file("upload.html")) // 访问根/路径,展示html页面.service(Files::new("/uploads", "./uploads").show_files_listing()) // 静态文件服务,可以访问/uploads目录下的文件}).bind(("172.20.10.188", 8080))?.run().await
}

🧪 5. 常见配置参数详解

在这里插入图片描述

🛡️ 6. 生产环境注意事项

绝对不要使用 .permissive() 或 .allowed_origin(“*”) 配置在生产环境。
对 allowed_origin 进行精确控制或白名单判断。
CORS 错误一般在浏览器控制台会有提示,不会在 Rust 后端看到错误日志。

http://www.lryc.cn/news/575329.html

相关文章:

  • 从零到一训练一个 0.6B 的 MoE 大语言模型
  • 百面Bert
  • 《网络攻防技术》《数据分析与挖掘》《网络体系结构与安全防护》这三个研究领域就业如何?
  • ASP.NET Core Web API 实现 JWT 身份验证
  • list类的详细讲解
  • 基于 Python 的批量文件重命名软件设计与实现
  • 二叉树理论基础
  • 【偏微分方程】基本概念
  • 逆向入门(8)汇编篇-rol指令的学习
  • 【kubernetes】--Service
  • 深入理解提示词工程:原理、分类与实战应用
  • 基于 opencv+yolov8+easyocr的车牌追踪识别
  • linux-修改文件命令(补充)
  • Windows 安装 Redis8.0.2
  • 多传感器标定简介
  • day042-负载均衡与web集群搭建
  • python3虚拟机线程切换过程
  • 定位坐标系深度研究报告
  • LangGraph--基础学习(Human-in-the-loop 人工参与深入学习2)
  • 达梦数据库安装
  • 深入理解Redis
  • 【深度学习新浪潮】什么是上下文工程?
  • Introduction to Software Engineering(TE)
  • Linux 怎么恢复sshd.service
  • 【C++】std::function是什么
  • 【网络实验】-配置用户登录
  • 《高等数学》(同济大学·第7版)第九章 多元函数微分法及其应用第一节多元函数的基本概念
  • ARM内核之CMSIS
  • 《从0到1:C/C++音视频开发自学完全指南》
  • 超级好用的小软件:geek,卸载软件,2m大小