简介
Actix Web 是一款基于 Rust 语言开发的高性能 Web 框架。
它通过异步编程模型、强大的请求路由、中间件支持,为开发者提供了丰富的工具和选项,
是构建可伸缩、高并发的 Web 应用程序的理想选择。
由于 actix-web 较早发布,所以在众多rust web库中 是目前拥有最好生态环境和社区的框架!
高性能、可扩展,它采用了基于actor模型的异步编程模型,利用 Rust 的 Futures 和 async/await 语法来实现高效的非阻塞I/O操作。
在性能方面表现非常出色,适用于高并发、低延迟的web场景。
上手案例
- Cargo.toml
[dependencies]
actix-web = "4.4.0"
- main.rs
use actix_web::{App, get, HttpResponse, HttpServer, Responder, route, web};
// 通过宏标注指出请求路径和方法
#[get("/t1")]
async fn get_request() -> impl Responder {
HttpResponse::Ok().body("ok")
}
// 把请求体提取成String
async fn post_request(body: String) -> impl Responder {
println!("{}", body);
HttpResponse::Ok().body("ok")
}
/// 测试链接:
/// post@/t1
/// get@/t1
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let addr = "127.0.0.1";
let port = 8080;
let app = HttpServer::new(|| {
App::new()
// 在这里传入定义的服务
.service(get_request)
// 这里注意到,route接收三个参数:路径,请求方法和handler
.route("/t1", web::post().to(post_request))
})
.bind((addr, port))?;
println!(" \n\n 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈 ==> 服务启动,监听地址为: {}:{} <==🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈 \n\n", addr, port);
app.run().await
}
路由
route,service,resource,scope,handler
- handler:route内部处理业务的部分,一般handler和route是一个意思
- route:表示一个包含拦截器(guard)的handler
- resource:route集合,一般用来组织不同请求方法的同一URL路径下的不同route
- scope:resource集合,也可以是route几个,用来组织多个拥有相同URL前缀的resource/route
- service:表示一个完整的服务
use actix_web::{get, post, route, web, App, HttpResponse, HttpServer, Responder};
async fn get1() -> impl Responder {
HttpResponse::Ok().body("get1")
}
async fn post1() -> impl Responder {
HttpResponse::Ok().body("post1")
}
async fn get2() -> impl Responder {
HttpResponse::Ok().body("get2")
}
async fn post2() -> impl Responder {
HttpResponse::Ok().body("post2")
}
#[get("/ok")]
async fn ok() -> impl Responder {
HttpResponse::Ok()
}
/// 测试连接:
/// get@/t1/a
/// post@/t1/a
/// get@/t1/b
/// post@/t1/b
/// get@/ok
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
// 定义一个服务
.service(
web::scope("/t1") // 定义一个scope
.service(
web::resource("/a") // 定义一个resource,resource可能包含多个route
.route(web::get().to(get1)) // GET ==> t1/a
.route(web::post().to(post1)), // POST ==> t1/a
)
.service(
web::resource("/b")
.route(web::get().to(get2))
.route(web::post().to(post2)),
),
)
// actix-web的请求都是在service里处理的,所以你可以直接处理,当然需要通过宏包装请求路径和方法
.service(ok)
})
.bind("127.0.0.1:8888")?
.run()
.await
}
路由守卫: guard
类似一种拦截器,它定义了请求需要满足的前置条件,只有满足时,和它绑定的route才会被调用。
use actix_web::{get, post, route, web, App, HttpResponse, HttpServer, Responder, guard};
async fn get1() -> impl Responder {
HttpResponse::Ok().body("get1")
}
async fn post1() -> impl Responder {
HttpResponse::Ok().body("post1")
}
async fn get2() -> impl Responder {
HttpResponse::Ok().body("get2")
}
async fn post2() -> impl Responder {
HttpResponse::Ok().body("post2")
}
#[get("/ok")]
async fn ok() -> impl Responder {
HttpResponse::Ok()
}
/// 测试连接:
/// get@/t1/a
/// post@/t1/a
/// get@/t1/b
/// post@/t1/b
/// get@/ok
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
// 定义一个服务
.service(
web::scope("/t1") // 定义一个scope
// 要求 该 socop 下的所有请求 的 请求头必 须包含如下, 否则报404错误,任何数据不返回
.guard(guard::Header("Token", "code"))
.service(
web::resource("/a") // 定义一个resource,resource可能包含多个route
.route(web::get().to(get1)) // GET ==> t1/a
.route(web::post().to(post1)), // POST ==> t1/a
)
.service(
web::resource("/b")
.route(web::get().to(get2))
.route(web::post().to(post2)),
),
)
// actix-web的请求都是在service里处理的,所以你可以直接处理,当然需要通过宏包装请求路径和方法
.service(ok)
})
.bind("127.0.0.1:8888")?
.run()
.await
}
请求报文
请求参数
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
// GET 访问路径: /t1/11/wtt
#[get("/t1/{id}/{name}")]
async fn get1(path: web::Path<(i32, String)>) -> impl Responder {
// println!("{:?}", path.into_inner());
let wtt = path.into_inner();
println!("第一个参数 {}", wtt.0); // 11
println!("第二个参数 {}", wtt.1); // wtt
HttpResponse::Ok().body("使用的是 web::Path")
}
#[derive(Debug, Deserialize)]
struct User {
id: i32,
name: String,
}
// GET 访问路径: /t2/11/wtt
#[get("/t2/{id}/{name}")]
async fn get2(user: web::Path<User>) -> impl Responder {
let wtt = user.into_inner();
println!("第一个属性 {}", wtt.id); // 11
println!("第二个属性 {}", wtt.name); // wtt
HttpResponse::Ok().body("使用的是 web::Path")
}
// GET 访问路径: /t3?id=11&name=wtt
#[get("/t3")]
async fn get3(user: web::Query<User>) -> impl Responder {
let wtt = user.into_inner();
println!("第一个属性 {}", wtt.id); // 11
println!("第二个属性 {}", wtt.name); // wtt
HttpResponse::Ok().body("使用的是 web::Query")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.service(get1)
.service(get2)
.service(get3)
})
.bind("127.0.0.1:8888")?
.run()
.await
}
请求体
- json
use actix_web::{get,post, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: i32,
name: String,
}
// 访问路径: /t2 Content-Type:application/json {"id": 11,"name":"wtt"}
#[post("/t2")]
async fn get2(user: web::Json<User>) -> impl Responder {
let wtt = user.into_inner();
println!("第一个属性 {}", wtt.id); // 11
println!("第二个属性 {}", wtt.name); // wtt
HttpResponse::Ok().body("使用的是 web::Json")
}
- form
use actix_web::{get,post, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: i32,
name: String,
}
// 访问路径: /t3 Content-Type:application/x-www-form-urlencoded "id": 11 "name":"wtt"
#[post("/t3")]
async fn get2(user: web::Form<User>) -> impl Responder {
let wtt = user.into_inner();
println!("第一个属性 {}", wtt.id); // 11
println!("第二个属性 {}", wtt.name); // wtt
HttpResponse::Ok().body("使用的是 web::Form")
}
- file upload
use actix_multipart::Multipart;
use actix_web::{
post,
web::{self, Buf},
App, Error, HttpServer,
};
use futures::StreamExt;
use std::fs::File;
use std::io::Write;
#[post("/t2")]
async fn multipart_upload(mut parts: Multipart) -> Result<String, Error> {
let max_limit = 2*1024*1024; // 上传文件大小限制为 2M
let mut i = 1;
while let Some(field_old) = parts.next().await {
println!("获取第{i}个文件========================");
i+=1;
// 文件句柄
let mut field = field_old?;
let name_key = field.name().to_string();
println!("键值对 名称{}", name_key);
let mut bytes = web::BytesMut::new();
// 读取 文件 字节内容
while let Some(chunk) = field.next().await {
let chunk = chunk?;
bytes.extend_from_slice(chunk.chunk());
}
let content_bytes_len = bytes.len();
println!("文件内容长度{}",content_bytes_len);
if content_bytes_len > max_limit {
println!("上传文件过大");
}
// println!("文件内容: {}",String::from_utf8_lossy(bytes.as_ref()));
let name_file_option = field.content_disposition().get_filename();
let mut name_file: &str = "default_name.txt";
if let Some(f) = name_file_option {
name_file = f;
}
// 通过 BytesMut 生成 本地文件
let mut file = File::create(name_file)?;
file.write_all(&bytes)?;
}
Ok("ok".to_string())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || App::new().service(multipart_upload))
.bind("127.0.0.1:8888")?
.run()
.await
}
请求头
use actix_web::{post, web, App, HttpResponse, HttpRequest,HttpServer, Responder};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: i32,
name: String,
}
// 访问路径: /t3 Content-Type:application/x-www-form-urlencoded "id": 11 "name":"wtt"
#[post("/t3")]
async fn get2(user: web::Form<User>,req: HttpRequest) -> impl Responder {
// 获取请求头信息
let headers = req.headers();
// 遍历请求头信息并打印
for (key, value) in headers.iter() {
println!("{}: {}", key, value.to_str().unwrap());
}
// 获取 某一个 信息
let user_agent = headers.get("user-agent");
if let Some(val) = user_agent {
println!("获取到 user_agent 是 {:?}", val) // 获取到 user_agent 是 "PostmanRuntime/7.26.8"
} else {
println!("未获取到 user_agent 从请求头中")
};
let wtt = user.into_inner();
println!("第一个属性 {}", wtt.id); // 11
println!("第二个属性 {}", wtt.name); // wtt
HttpResponse::Ok().body("使用的是 web::Form")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || App::new().service(get2))
.bind("127.0.0.1:8888")?
.run()
.await
}
错误处理
- errorwtt.rs
自定义 错误
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};
use std::fmt;
use std::fmt::Formatter;
#[derive(Debug)]
pub enum AppErrorType {
RedisError,
ActError,
NotFoundError,
PgError,
SerdeJsonError,
JwtError,
}
// / Struct ///
#[derive(Debug)]
pub struct AppError {
pub message: Option<String>,
pub code: i8,
pub error_type: AppErrorType,
}
// // Impl /
impl AppError {
pub fn new(msg: Option<String>, code: i8, error_type: AppErrorType) -> Box<dyn std::error::Error> {
let e = AppError{
message: msg,
code,
error_type,
};
Box::new(e)
}
fn message(&self) -> String {
match &*self {
//结构体解构
AppError {
message: Some(message),
error_type: _,
code: _,
} =>{
// format!("code:{}_type:{:?}_msg:{}", self.code, self.error_type, message) //返回给 前端的 数据
format!("{}", message) //返回给 前端的 数据
},
AppError {
message: None,
error_type: _,
code: _,
} => format!(
"*Error>>{:?} => The requested item was not found",
self.error_type
),
}
}
}
// // Impl For Trait //
impl fmt::Display for AppError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{:?}", self)
}
}
impl ResponseError for AppError {
fn status_code(&self) -> StatusCode {
match self.error_type {
AppErrorType::NotFoundError => StatusCode::NOT_FOUND, //404
AppErrorType::RedisError => StatusCode::BAD_REQUEST, //400
AppErrorType::PgError => StatusCode::INTERNAL_SERVER_ERROR, //500
AppErrorType::SerdeJsonError => StatusCode::FORBIDDEN, //403
AppErrorType::JwtError => StatusCode::NOT_EXTENDED, //510
AppErrorType::ActError => StatusCode::BAD_GATEWAY, //502
}
}
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code()).body(self.message())
}
}
// 实现 std::error::Error trait
impl std::error::Error for AppError {}
// 统一 相应数据 类型
pub type HttpRes = actix_web::Result<HttpResponse, AppError>;
- main.rs
使用自定义错误
use actix_web::{get, web, App, HttpResponse, HttpServer};
use serde::Deserialize;
use serde::Serialize;
mod errorwtt;
#[derive(Debug, Deserialize, Serialize)]
struct User {
id: i32,
name: String,
}
#[get("/t3")]
async fn get3(user: web::Query<User>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
if user.id == 0 {
let e = errorwtt::AppError::new(
Some(String::from("uid不可为0")),
16,
errorwtt::AppErrorType::ActError,
);
return Err(e);
} else {
let option_pool = mysql_init().await;
let pool: MySqlPool;
if let Some(conn) = option_pool {
pool = conn;
} else {
panic!("数据库连接失败");
}
let mut conncet = pool.acquire().await?;
let data = sqlx::query_as::<_, Slides>("select * from slides where id = ?").bind(1)
// .fetch_all(&mut conncet) // 获取多条数据, 找不到数据不报错
.fetch_one(&mut conncet) // 获取一条数据,找不到数据 报错
.await?;
// println!("{:?}", data);
return Ok(HttpResponse::Ok().json(data));
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || App::new().service(get3))
.bind("127.0.0.1:8888")?
.run()
.await
}
响应报文
中间件
中间件一般用于:
- 请求前置处理
- 响应后置处理
- App状态更新
- 日志,Redis等其他扩展服务
两种方式
方式一: wrap_fn()
通过传入闭包的方式完成我们中间件的逻辑。
耗时中间件
- Cargo.toml
[dependencies]
actix-web = "4.4.0"
tokio = { version = "1", features = ["full"] }
# 提供对异步编程的支持和工具
futures-util = "0.3"
- main.rs
use actix_web::{dev::Service as _, web, App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.wrap_fn(|req, srv| {
println!("222222");
let start_time = std::time::Instant::now();
println!("1、开始计时");
let path = req.path().to_owned();
// 通过调用 srv.call(req),将请求传递给实际的 服务处理程序 进行处理。
let fut = srv.call(req);
async move {
// 执行时间 在 路由函数 执行之后
println!("333333");
let res = fut.await;
let elapsed_time = start_time.elapsed();
println!("2、计时结束");
println!("请求路径 {} 共计用时 {:?}", path, elapsed_time);
res
}
})
.route(
"/aaa",
web::get().to(|| async {
// 暂停1秒
std::thread::sleep(std::time::Duration::from_secs(1));
println!("--------------");
"see you"
}),
)
})
.bind("127.0.0.1:8888")?
.run()
.await
}
方式二: wrap()
一般中间件处理的逻辑可能很多,使用 wrap_fn的方式 会显得很冗余。
可以通过方法warp传入一个实现了 Service
trait and Transform
trait 的struct,
这样就会调用我们实现好的方法。
耗时中间件
- Cargo.toml
[dependencies]
actix-web = "4.4.0"
tokio = { version = "1", features = ["full"] }
# 提供对异步编程的支持和工具
futures-util = "0.3"
- wtt.rs (中间件模块文件)
// 中间件 => 打印接口耗时
use std::{future::{ready, Ready}, time::Instant};
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;
// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct Timed;
// Middleware factory is `Transform` trait
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for Timed
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = TimedMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(TimedMiddleware { service }))
}
}
pub struct TimedMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for TimedMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let start_time = Instant::now();
let path = req.path().to_owned();
let method = req.method().to_string();
let remote_addr = req.connection_info().peer_addr().unwrap_or("unknown").to_string();
let version = format!("{:?}", req.version()); // 使用 format! 宏转换版本号为字符串
let headers = req.headers().clone();
println!("{}", "1. 开始计时");
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
let elapsed = start_time.elapsed();
let status = res.status();
let content_length = res
.headers()
.get(actix_web::http::header::CONTENT_LENGTH)
.and_then(|v| v.to_str().ok())
.unwrap_or("-");
let user_agent = headers
.get(actix_web::http::header::USER_AGENT)
.and_then(|v| v.to_str().ok())
.unwrap_or("-");
println!("{}", "2. 计时结束");
println!("{} {} {} {} {} {} {} 花费时间 [{:.6}] ms",
remote_addr,
method,
path,
version,
status.as_u16(),
content_length,
user_agent,
elapsed.as_millis());
Ok(res)
})
}
}
- main.rs
use actix_web::{web, App, HttpServer};
mod wtt;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.wrap(wtt::Timed)
.route(
"/aaa",
web::get().to(|| async {
// 暂停1秒
std::thread::sleep(std::time::Duration::from_secs(1));
println!("--------------");
"see you"
}),
)
})
.bind("127.0.0.1:8888")?
.run()
.await
}
鉴权中间件
- Cargo.toml
[dependencies]
actix-web = "4.4.0"
actix-rt = "2.6.0"
serde = { version = "1.0.190", features = ["derive"] }
serde_json = "1.0.108"
serde_yaml = "0.9.27"
futures="0.3.29"
sqlx = { version = "0.6", features = ["mysql", "runtime-tokio-rustls"] }
tokio = { version = "1", features = ["full"] }
# 提供对异步编程的支持和工具
futures-util = "0.3"
- wtt.rs (中间件文件)
use std::future::{ready, Ready};
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
error,Error,
};
use futures_util::future::LocalBoxFuture;
// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct Auth;
// Middleware factory is `Transform` trait
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for Auth
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = AuthMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(AuthMiddleware { service }))
}
}
pub struct AuthMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
// 进行鉴权操作,判断是否有权限
if has_permission(&req) {
// 有权限,继续执行后续中间件
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
Ok(res)
})
} else {
// 没有权限,立即返回响应
Box::pin(async move {
// 鉴权失败,返回未授权的响应,停止后续中间件的调用
Err(error::ErrorUnauthorized("禁止访问"))
})
}
}
}
fn has_permission(req: &ServiceRequest) -> bool {
// 实现你的鉴权逻辑,根据需求判断是否有权限
// 返回 true 表示有权限,返回 false 表示没有权限
let token = req.headers().get("token").unwrap();
// println!("{:?}", value);
token == "wtt" // token 等于 wtt 则 有权限访问api
}
- main.rs
use actix_web::{web, App, HttpServer};
mod wtt;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.wrap(wtt::Auth) / 使用 鉴权中间件
.route(
"/aaa", // get ==》 /aaa
web::get().to(|| async {
// 暂停2秒
std::thread::sleep(std::time::Duration::from_secs(2));
println!("--------------");
"8888888888"
}),
)
})
.bind("127.0.0.1:8888")?
.run()
.await
}
执行顺序: 洋葱模型
- main.rs
use actix_web::{dev::Service as _, web, App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
// 第一个 中间件
.wrap_fn(|req, srv| {
println!("222222");
// 通过调用 srv.call(req),将请求传递给实际的 服务处理程序 进行处理。
let fut = srv.call(req);
async move {
// 执行时间 在 路由函数 执行之后
let res = fut.await;
println!("444444");
res
}
})
// 第二个 中间件
.wrap_fn(|req, srv| {
println!("111111");
// 通过调用 srv.call(req),将请求传递给实际的 服务处理程序 进行处理。
let fut = srv.call(req);
async move {
// 执行时间 在 路由函数 执行之后
let res = fut.await;
println!("555555");
res
}
})
.route(
"/aaa",
web::get().to(|| async {
// 暂停1秒
std::thread::sleep(std::time::Duration::from_secs(1));
println!("333333");
"see you"
}),
)
})
.bind("127.0.0.1:8888")?
.run()
.await
}
输出顺序为: 111111、222222、333333、444444、555555
日志系统
- Cargo.toml
[dependencies]
log = "0.4.0"
env_logger = "0.9.0" ## 引入env_logger库
chrono = "*"
log4rs = "1.0"
- main.rs
use log::{error, warn, info};
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Config, Root};
use log4rs::encode::pattern::PatternEncoder;
fn main() {
// 创建 FileAppender
let logfile = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d(%Y/%m/%d %H:%M:%S)} - {l} - {m}\n")))
.build("log/output.log")
.unwrap();
// 创建 Config
let config = Config::builder()
.appender(Appender::builder().build("logfile", Box::new(logfile)))
.build(Root::builder().appender("logfile").build(log::LevelFilter::Info))
.unwrap();
// 使用配置初始化日志系统
log4rs::init_config(config).unwrap();
// 记录日志
error!("This is an error message.");
warn!("This is a warning message.");
info!("This is an info message.");
}
输出:
2023/11/16 09:31:25 - ERROR - This is an error message.
2023/11/16 09:31:25 - WARN - This is a warning message.
2023/11/16 09:31:25 - INFO - This is an info message.
Mysql数据库
SQLx
是一个Rust语言的异步SQL执行库,它支持多种数据库,包括MySQL、PostgreSQL、SQLite等。
以MySQL数据库为例,介绍SQLx在Rust语言中的基础用法和进阶用法。
连接
- Cargo.toml
[dependencies]
# sqlx = { version = "0.7.2", features = [
# "runtime-tokio-native-tls",
# "mysql",
# "chrono",
# "json",
# "macros"
# ] }
sqlx = { version = "0.6", features = ["mysql", "runtime-tokio-rustls"] }
tokio = { version="1", features=["full"]}
serde = { version = "1.0.126", features = ["derive"] }
serde_json = "1.0.64"
- main.rs
use serde::{Deserialize, Serialize};
use sqlx::mysql::{MySqlPool, MySqlPoolOptions};
async fn mysql_init() -> Option<MySqlPool> {
let url = "mysql://root:123456@172.19.24.12:3306/test";
let pool = MySqlPoolOptions::new().connect(&url).await;
match pool {
Ok(conn) => {
println!("Mysql数据池 连接成功");
Some(conn)
}
Err(err) => {
println!("Mysql数据池 连接异常: {:?}", err);
None
}
}
}
#[derive(Debug, sqlx::FromRow, Deserialize, Serialize)]
struct Slides {
id: u64,
img: String,
sorts: u8,
links: String,
create_at: i64,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let option_pool = mysql_init().await;
let pool: MySqlPool;
if let Some(conn) = option_pool {
pool = conn;
} else {
panic!("数据库连接失败");
}
let mut conncet = pool.acquire().await?;
let data = sqlx::query_as::<_, Slides>("select * from slides")
.fetch_all(&mut conncet) // 获取多条数据, 找不到数据不报错
// .fetch_one(&pool) // 获取一条数据,找不到数据 报错
.await?;
println!("{:?}", data);
let json_str = serde_json::to_string(&data)?;
println!("json 字符串 => {:?}", json_str);
Ok(())
}
查
- query_as()
使用query_as()方法执行SQL查询语句时,可以自动将返回结果转换为指定类型的结构体。
use serde::{Deserialize, Serialize};
use sqlx::mysql::{MySqlPool, MySqlPoolOptions};
async fn mysql_init() -> Option<MySqlPool> {
let url = "mysql://root:123456@172.19.24.12:3306/test";
let pool = MySqlPoolOptions::new().connect(&url).await;
match pool {
Ok(conn) => {
println!("Mysql数据池 连接成功");
Some(conn)
}
Err(err) => {
println!("Mysql数据池 连接异常: {:?}", err);
None
}
}
}
#[derive(Debug, sqlx::FromRow, Deserialize, Serialize)]
struct Slides {
id: u64,
img: String,
sorts: u8,
links: String,
create_at: i64,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let option_pool = mysql_init().await;
let pool: MySqlPool;
if let Some(conn) = option_pool {
pool = conn;
} else {
panic!("数据库连接失败");
}
let mut conncet = pool.acquire().await?;
let data = sqlx::query_as::<_, Slides>("select * from slides where id=? and name=?").bind(1).bind("tom")
.fetch_all(&mut conncet) // 获取多条数据, 找不到数据不报错
// .fetch_one(&pool) // 获取一条数据,找不到数据 报错
.await?;
println!("{:?}", data);
let json_str = serde_json::to_string(&data)?;
println!("json 字符串 => {:?}", json_str);
Ok(())
}
增
- execute()
使用execute()方法插入数据时,需要手动指定插入的数据
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let option_pool = mysql_init().await;
let pool: MySqlPool;
if let Some(conn) = option_pool {
pool = conn;
} else {
panic!("数据库连接失败");
}
let mut conncet = pool.acquire().await?;
let name = "tom";
let result = sqlx::query("INSERT INTO slides (img) VALUES (?)")
.bind(name)
.execute(&mut conncet)
.await?;
println!("{:?}", result); // MySqlQueryResult { rows_affected: 1, last_insert_id: 7 }
Ok(())
}
删
- execute()
使用execute()方法删除数据时,需要手动指定删除的条件。
let mut conncet = pool.acquire().await?;
let result = sqlx::query("DELETE FROM slides WHERE id > ?")
.bind(10)
.execute(&mut conncet)
.await?;
println!("{:?}", result); // MySqlQueryResult { rows_affected: 3, last_insert_id: 0 }
改
- execute()
使用execute()方法更新数据时,需要手动指定更新的条件和更新的数据。
let mut conncet = pool.acquire().await?;
let name = "tom666";
let id = 10;
let result = sqlx::query("UPDATE slides SET img = ? WHERE id = ?")
.bind(name)
.bind(id)
.execute(&mut conncet)
.await?;
println!("{:?}", result); // MySqlQueryResult { rows_affected: 1, last_insert_id: 0 }
事务
使用SQLx执行事务时,可以使用begin()
方法开始事务,使用commit()
方法提交事务,使用rollback()
方法回滚事务。
use serde::{Deserialize, Serialize};
use sqlx::mysql::{MySqlPool, MySqlPoolOptions};
async fn mysql_init() -> Option<MySqlPool> {
let url = "mysql://root:123456@172.19.24.12:3306/test";
/*
使用PoolOptions::new()方法创建连接池,
使用acquire()方法获取连接
*/
let pool = MySqlPoolOptions::new().connect(&url).await;
match pool {
Ok(conn) => {
println!("Mysql数据池 连接成功");
Some(conn)
}
Err(err) => {
println!("Mysql数据池 连接异常: {:?}", err);
None
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let option_pool = mysql_init().await;
let pool: MySqlPool;
if let Some(conn) = option_pool {
pool = conn;
} else {
panic!("数据库连接失败");
}
// let mut wtt = pool.acquire().await?;
let mut tx = pool.begin().await?;
let result = sqlx::query("INSERT INTO slides (img) VALUES (?)")
.bind("user.name")
.execute(&mut tx)
.await?;
println!("{:?}", result);
if result.rows_affected() == 0 {
tx.rollback().await?;
} else {
tx.commit().await?;
}
Ok(())
}
Redis缓存
Redis-rs 是 Rust 的高级 Redis 库。它通过一个非常灵活但低级的 API 提供了对所有 Redis 功能的方便访问。
它使用可定制的类型转换特性,以便任何操作都可以返回您所期望的类型的结果。这是一个非常愉快的开发体验。
连接Redis、键值对
- Cargo.toml
[dependencies]
redis = "0.23.3"
# redis = "0.22"
- main.rs
use redis::{Client, Commands};
fn main() {
let passwd = "ruiwanxiao6908";
let host = "172.19.24.12";
let port = "6666";
let index = "4";
let open_params = format!("redis://:{passwd}@{host}:{port}/{index}");
let client = Client::open(open_params).unwrap();
let mut conn = client.get_connection().unwrap();
println!("成功连接到 redis");
// 设置key值
let _: () = conn.set("key", "value").unwrap();
// 获取key值
let value: String = conn.get("key").unwrap();
println!("Value: {}", value);
}
Hash
Hash是Redis中一种特殊的数据结构,可以将多个键值对存储到一个键中。在Redis中,Hash通常用于存储对象,比如用户信息、商品信息等。
use redis::{Client, Commands, RedisResult};
fn main() {
let passwd = "ruiwanxiao6908";
let host = "172.19.24.12";
let port = "6666";
let index = "0";
let open_params = format!("redis://:{passwd}@{host}:{port}/{index}");
let client = Client::open(open_params).unwrap();
let mut conn = client.get_connection().unwrap();
println!("成功连接到 redis");
// 设置、增加 Hash值
let _: () = conn.hset("user:123", "name", "Alice").unwrap();
let _: () = conn.hset("user:123", "age", 20).unwrap();
// 获取Hash值
let name: RedisResult<String> = conn.hget("user:123", "name");
let age: RedisResult<i32> = conn.hget("user:123", "age");
println!("Name: {:?}", name);
println!("Age: {:?}", age);
// 删除 单个 属性
let _:() = conn.hdel("user:123", "name").unwrap();
}
List
List是一种可以按下标顺序访问的数据结构,可以在一端添加元素,在另一端删除元素,非常适合用于消息队列等场景。
use redis::{Client, Commands};
use std::num::NonZeroUsize;
fn main() {
let passwd = "ruiwanxiao6908";
let host = "172.19.24.12";
let port = "6666";
let index = "0";
let open_params = format!("redis://:{passwd}@{host}:{port}/{index}");
let client = Client::open(open_params).unwrap();
let mut conn = client.get_connection().unwrap();
println!("成功连接到 redis");
// 设置List值
let _: () = conn.rpush("queue", "A").unwrap();
let _: () = conn.rpush("queue", "B").unwrap();
let _: () = conn.rpush("queue", "C").unwrap();
// 获取List值
let item1: Option<String> = conn.lpop("queue", NonZeroUsize::new(0)).unwrap(); // 删除 第1个进来的
println!("Item1: {:?}", item1);
let item2: Option<String> = conn.lpop("queue", NonZeroUsize::new(0)).unwrap(); // 删除 第2个进来的
println!("Item1: {:?}", item2);
}
设置 过期时间
use redis::{Client, Commands};
fn main() {
let client = Client::open("redis://127.0.0.1/").unwrap();
let conn = client.get_connection().unwrap();
let key = "cache_key";
let value = "cache_value";
let expire_sec = 60;
let _: () = conn.set_ex(key, expire_sec, value).unwrap();
}
数据请求(reqwest:西部请求)
rust 众多的 HTTP 客户端 中,有 低级的 和 高级的。为了使用方便简单 这里采用 高级的 reqwest。
-
hyper
hyper 是 Rust 中的低级 HTTP 库,它已投入生产并完全用 Rust 编写。此外,它是唯一提到生产准备的库。
hyper 性能好,偏底层,而且面向 async 设计,应用广泛,已成为 Rust 网络程序生态的重要基石之一。
知名的 HTTP client reqwest, HTTP server warp 和 axum, Rust 的 gRPC 实现 tonic 等,都使用了 hyper。
我们不一定会直接使用 hyper,但了解 hyper 对于我们了解 Rust 的网络程序生态,学习设计良好的网络程序,都有好处。 -
reqwest
这里选择使用 更高级的 HTTP 库 reqwest,该库建立在 hyper 之上,因此它提供了许多与 hyper 相同的优势,并且具有更方便的启动语法。
=默认=情况下,reqwest 包含一个能够发出异步请求
的客户端。也可以将其配置为使用“阻塞”客户端,即 同步请求
的客户端。
安装
在项目的 Cargo.toml 中添加依赖:
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = reqwest::get("https://httpbin.org/ip")
.await?
.json::<HashMap<String, String>>()
.await?;
println!("{:#?}", resp); // 返回当前请求浏览器的ip地址,也就是你当前的ip地址。
Ok(())
}
同步请求
GET
Cargo.toml
[dependencies]
// 这里的 features 中 要引入 blocking, 否则 下面代码中的 use reqwest::blocking::Client; 无法引入
reqwest = { version = "0.11", features = ["blocking", "json"] }
tokio = { version = "1", features = ["full"] }
main.rs
use reqwest::blocking::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get("https://httpbin.org/ip").send()?;
println!("{}", response.text()?);
Ok(())
}
使用blocking模块中的Client创建了一个HTTP客户端,然后使用get方法发送了一个GET请求,请求了httpbin.org的/get接口,并使用text方法获取响应内容。
- 文件下载
use reqwest::blocking::Client;
use std::fs::File;
use std::io::copy;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let mut response = client.get("https://localhost/image/png").send()?;
let mut file = File::create("image.png")?;
copy(&mut response, &mut file)?;
Ok(())
}
POST
- 字符串
use reqwest::blocking::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.post("https://localhost/post")
.body("hello world")
.send()?;
println!("{}", response.text()?);
Ok(())
}
- JSON
[dependencies]
reqwest = { version = "0.11", features = ["json", "blocking"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0.190", features = ["derive"] }
serde_json = "1.0.108"
use reqwest::blocking::Client;
use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let data = json!({
"name": "wtt",
"age": 20
});
let response = client.post("https://httpbin.org/post")
.json(&data)
.send()?;
println!("{}", response.text()?);
Ok(())
}
- form data
use reqwest::blocking::Client;
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let mut data = HashMap::new();
data.insert("name", "wtt");
data.insert("age", "20");
let response = client.post("https://httpbin.org/post")
.form(&data)
.send()?;
println!("{}", response.text()?);
Ok(())
}
请求头
use reqwest::blocking::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get("https://httpbin.org/get")
.header("wtt", "whero")
.send()?;
println!("{}", response.text()?);
Ok(())
}
异步请求
GET
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get("https://httpbin.org/get").send().await?;
println!("{}", response.text().await?);
Ok(())
}
这个例子中,使用async/await语法,在异步上下文中发送了一个GET请求。
POST
use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let data = json!({
"name": "wtt",
"age": 20
});
let response = client.post("https://httpbin.org/post").json(&data).send().await?;
println!("{}", response.text().await?);
Ok(())
}
进阶用法
- 自定义SSL证书
use reqwest::blocking::Client;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.add_root_certificate(reqwest::Certificate::from_pem(
&std::fs::read(Path::new("cert.pem"))?,
))
.build()?;
let response = client.get("https://localhost/get").send()?;
println!("{}", response.text()?);
Ok(())
}
使用add_root_certificate方法设置了一个自定义的SSL证书,发送了一个GET请求。
- 连接池
使用pool_idle_timeout方法设置了连接池的空闲超时时间,发送了一个GET请求。
use reqwest::blocking::Client;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.pool_idle_timeout(Duration::from_secs(30))
.build()?;
let response = client.get("https://localhost/get").send()?;
println!("{}", response.text()?);
Ok(())
}
在高并发场景下,使用连接池可以提高HTTP客户端的性能和稳定性。可以使用builder方法设置连接池的大小和空闲超时时间。
use reqwest::blocking::Client;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.pool_idle_timeout(Duration::from_secs(30))
.pool_max_idle_per_host(10)
.build()?;
let response = client.get("https://localhost/get").send()?;
println!("{}", response.text()?);
Ok(())
}
- 自定义重试策略
use reqwest::blocking::Client;
use reqwest::Url;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.retry(|attempt| {
let url = Url::parse("https://localhost/get").unwrap();
if attempt > 3 {
return None;
}
Some(Duration::from_secs(attempt * 2) + url.host_str().unwrap().len() as u64)
})
.build()?;
let response = client.get("https://localhost/get").send()?;
println!("{}", response.text()?);
Ok(())
}
使用 retry 方法设置了一个自定义的重试策略,发送了一个GET请求。
- 错误处理
在发送HTTP请求时,可能会出现各种错误,如网络错误,服务器错误等。可以使用Result类型来处理错误,或者使用?运算符简化代码。
use reqwest::blocking::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get("https://localhost/get").send()?;
if response.status().is_success() {
println!("{}", response.text()?);
} else {
println!("Error: {}", response.status());
}
Ok(())
}
- 使用代理
在访问某些网站时,可能需要使用代理服务器。可以使用builder方法设置代理服务器的地址和端口。
use reqwest::blocking::Client;
use reqwest::Proxy;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.proxy(Proxy::http("http://localhost:8080")?)
.build()?;
let response = client.get("https://localhost/get").send()?;
println!("{}", response.text()?);
Ok(())
}