rust actix-web

简介

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(())
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值