赞
踩
使用rust编写一个web服务不如使用java提供的spring boot一样简单,需要手工去添加依赖,目前rust web生态已趋近成熟,可以尝试进行web开发。
本次开发的服务使用的依赖有
做一个简单的Web应用,有以下几个步骤
我们一步一步来,首先我们先创建一个应用
cargo new todolist
然后,我们添加依赖,这里我们使用cargo add 添加
cargo add axum serde tokio tower tower-http tracing tracing-subscriber bb8 bb8-postgres clap --features serde/derive,tokio/rt-multi-thread,tower-http/fs,tower-http/trace,clap/derive
这样的话,就不用添加版本了。
这里我们建一个简单的数据库
create database todolist;
create table todo (
id serial primary key,
description varchar(512) not null,
completed bool not null
);
然后我们正式进入我们的代码部分:
定义postgresql连接,这里我使用了clip库,从命令行传入数据连接参数
// 定义传入参数模型 #[derive(Parser, Debug)] #[command(version, about, long_about=None)] struct Args { #[arg(short='H', long)] host: String, #[arg(short, long)] user: String, #[arg(short, long)] password: String, #[arg(short, long)] dbname: String, } // 主体部分,建立postgreSQL的数据库连接 let args = Args::parse(); let connection_str = format!("host={} user={} password={} dbname={}", args.host,args.user,args.password,args.dbname); let manager = PostgresConnectionManager::new_from_stringlike(connection_str, NoTls).unwrap(); let pool = Pool::builder().build(manager).await.unwrap();
这里,我使用了axum中的AppState来管理全局所要使用的变量,在axum中使用Router::new()
提供的with_state
,值得注意的是,这里的struct必须实现Clone trait。
#[derive(Clone)]
struct MyAppState {
dbpool: Pool<PostgresConnectionManager<NoTls>>,
}
接下来,我们定义初始化日志模块
tracing_subscriber::fmt::init();
一行代码就能搞定。
然后我们定义几个model,注意这里面实现的trait,serde提供的Serialize
,Deserialize
,还有Debug
, Clone
。
#[derive(Debug, Serialize, Deserialize, Clone)] struct Todo { id: i32, description: String, completed: bool, } #[derive(Debug, Serialize, Deserialize, Clone)] struct CreateTodo { description: String, } #[derive(Debug, Serialize, Deserialize, Clone)] struct UpdateTodo { id: i32, description: Option<String>, completed: Option<bool> } #[derive(Debug, Serialize, Deserialize, Clone)] struct ResultWrapper<T> { code: u32, message: String, data: Option<T>, }
接下来我们定义handler,分别是获取todo数据列表,新建数据列表和删除数据列表
这里要求返回的结果必须实现IntoResponse,否则无法在axum的Route中注册,可以使用axum提供的Json Struct包括数据和结果,这样就能将数据正常转换为Respone。
State则在axum中进行注册,可以直接在参数列表中传入,这里bb8提供的Pool,不用考虑所有权,不使用clone,直接进行get使用。
返回的结果为一个tuple,第一元素为状态码,第二个为参数。
async fn todo_list(State(app_state): State<MyAppState>) -> impl IntoResponse { match app_state.dbpool.get().await { Ok(db) => match db.query("SELECT * FROM todo", &[]).await { Ok(rows) => { let data: Vec<Todo> = rows.into_iter().map(|i| { Todo { id: i.get(0), description: i.get(1), completed: i.get(2) } }).collect(); (StatusCode::OK, Json(ResultWrapper{code: 0, message: "ok".to_string(), data: Some(data)})) }, Err(e) => { (StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper{code: 500, message: e.to_string(), data: None})) } }, Err(e) => { (StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper{code: 500, message: e.to_string(), data: None})) } } } async fn todo_delete(State(pool): State<MyAppState>, Json(id): Json<i32>) -> impl IntoResponse { match pool.dbpool.get().await { Ok(db) => match db.execute("DELETE FROM todo WHERE id = $1", &[&id]).await { Ok(r) => { tracing::info!("todo list id {} had been deleted", id); ( StatusCode::OK, Json(ResultWrapper { code: 0, message: "ok".to_string(), data: Some(r), }), ) } Err(e) => ( StatusCode::BAD_REQUEST, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), }, Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), } } async fn todo_create( State(pool): State<MyAppState>, Json(input): Json<CreateTodo>, ) -> impl IntoResponse { match pool.dbpool.get().await { Ok(db) => { match db .query( "INSERT INTO todo (description, completed) VALUES ($1, FALSE) RETURNING id", &[&input.description], ) .await { Ok(rows) => { if let Some(row) = rows.get(0) { let id: i32 = row.get(0); ( StatusCode::OK, Json(ResultWrapper { code: 0, message: "ok".to_string(), data: Some(id), }), ) } else { ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 400, message: "no return data".to_string(), data: None, }), ) } } Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), } } Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), } }
我们看一下,axum的主体部分,即路由注册和端口注册启动这个环节
这里面有:
let app = Router::new()
.route("/", post(todo_create))
.route("/", delete(todo_delete))
.route("/", get(todo_list))
.with_state(my_state)
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind("127.0.0.1:8889")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
这样,整体一个简单的todolist webserver就已完成,这里面还有一个update部分没有编写,不过仿照上面的handler也可以编写出来。
下面是整体代码
use axum::{ extract::State, http::StatusCode, response::IntoResponse, routing::{delete, get, post}, Json, Router, }; use bb8::Pool; use bb8_postgres::{tokio_postgres::NoTls, PostgresConnectionManager}; use clap::Parser; use serde::{Deserialize, Serialize}; use tower_http::trace::TraceLayer; #[derive(Parser, Debug)] #[command(version, about, long_about=None)] struct Args { #[arg(short = 'H', long)] host: String, #[arg(short, long)] user: String, #[arg(short, long)] password: String, #[arg(short, long)] dbname: String, } #[derive(Clone)] struct MyAppState { dbpool: Pool<PostgresConnectionManager<NoTls>>, } #[tokio::main] async fn main() { let args = Args::parse(); let connection_str = format!( "host={} user={} password={} dbname={}", args.host, args.user, args.password, args.dbname ); let manager = PostgresConnectionManager::new_from_stringlike(connection_str, NoTls).unwrap(); let pool = Pool::builder().build(manager).await.unwrap(); let my_state = MyAppState { dbpool: pool }; tracing_subscriber::fmt::init(); let app = Router::new() .route("/", post(todo_create)) .route("/", delete(todo_delete)) .route("/", get(todo_list)) .with_state(my_state) .layer(TraceLayer::new_for_http()); let listener = tokio::net::TcpListener::bind("127.0.0.1:8889") .await .unwrap(); axum::serve(listener, app).await.unwrap(); } #[derive(Debug, Serialize, Deserialize, Clone)] struct Todo { id: i32, description: String, completed: bool, } #[derive(Debug, Serialize, Deserialize, Clone)] struct CreateTodo { description: String, } #[derive(Debug, Serialize, Deserialize, Clone)] struct UpdateTodo { id: i32, description: Option<String>, completed: Option<bool>, } #[derive(Debug, Serialize, Deserialize, Clone)] struct ResultWrapper<T> { code: u32, message: String, data: Option<T>, } async fn todo_list(State(app_state): State<MyAppState>) -> impl IntoResponse { match app_state.dbpool.get().await { Ok(db) => match db.query("SELECT * FROM todo", &[]).await { Ok(rows) => { let data: Vec<Todo> = rows .into_iter() .map(|i| Todo { id: i.get(0), description: i.get(1), completed: i.get(2), }) .collect(); ( StatusCode::OK, Json(ResultWrapper { code: 0, message: "ok".to_string(), data: Some(data), }), ) } Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), }, Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), } } async fn todo_delete(State(pool): State<MyAppState>, Json(id): Json<i32>) -> impl IntoResponse { match pool.dbpool.get().await { Ok(db) => match db.execute("DELETE FROM todo WHERE id = $1", &[&id]).await { Ok(r) => { tracing::info!("todo list id {} had been deleted", id); ( StatusCode::OK, Json(ResultWrapper { code: 0, message: "ok".to_string(), data: Some(r), }), ) } Err(e) => ( StatusCode::BAD_REQUEST, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), }, Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), } } async fn todo_create( State(pool): State<MyAppState>, Json(input): Json<CreateTodo>, ) -> impl IntoResponse { match pool.dbpool.get().await { Ok(db) => { match db .query( "INSERT INTO todo (description, completed) VALUES ($1, FALSE) RETURNING id", &[&input.description], ) .await { Ok(rows) => { if let Some(row) = rows.get(0) { let id: i32 = row.get(0); ( StatusCode::OK, Json(ResultWrapper { code: 0, message: "ok".to_string(), data: Some(id), }), ) } else { ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 400, message: "no return data".to_string(), data: None, }), ) } } Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), } } Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ResultWrapper { code: 500, message: e.to_string(), data: None, }), ), } }
依赖的版本为
[dependencies]
axum = "0.7.5"
bb8 = "0.8.5"
bb8-postgres = "0.8.1"
clap = { version = "4.5.13", features = ["derive"] }
serde = { version = "1.0.204", features = ["derive"] }
tokio = { version = "1.39.2", features = ["rt-multi-thread"] }
tower = "0.4.13"
tower-http = { version = "0.5.2", features = ["fs", "trace"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。