Rust Axum实现Controller合并路由遇404,疑路由实例变更问题
我用Rust和Axum开发简单REST API,尝试将路由、HTTP请求方法与处理器整合,模拟其他语言框架的Controller功能。启动服务器后访问/projects(端口9090)时收到404错误,而非预期的项目列表或空列表,怀疑问题出在路由实例的变更方式上。
核心代码
Controller定义及初始化代码
use crate::state::AppState; use axum::response::IntoResponse; use axum::Router; use serde::{Deserialize, Serialize}; mod create; mod delete; mod find_one; mod health_check; mod list; mod update; const HEALTH_CHECK_PATH: &str = "/health_check"; const PROJECTS_PATH: &str = "/projects"; const PROJECTS_NAME_PATH: &str = "/projects/:name"; #[derive(Clone, Debug)] pub struct Controller { pub(crate) router: Router<AppState>, } impl Controller { pub fn new() -> Controller { let controller = Controller { router: Router::new(), }; controller .create() .delete() .find_one() .health_check() .list() .update(); controller } }
list()方法代码
use crate::state::AppState; use axum::extract::{Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::routing::get; use axum::Json; use axum_macros::debug_handler; use serde::Deserialize; use crate::controller::{Controller, PROJECTS_PATH}; impl Controller { pub fn list(&self) -> &Controller { // #[derive(Debug, Deserialize, Default)] // pub struct Pagination { // pub offset: Option<usize>, // pub limit: Option<usize>, // } #[debug_handler] pub async fn handler( //pagination: Option<Query<Pagination>>, State(state): State<AppState>, ) -> impl IntoResponse { //let Query(_pagination) = pagination.unwrap_or_default(); match state.query_port.read().unwrap().list() { Ok(res) => (StatusCode::OK, Json(res)).into_response(), Err(err) => { (StatusCode::INTERNAL_SERVER_ERROR, Json(err.to_string())).into_response() } } } let _ = self.router.clone().route(PROJECTS_PATH, get(handler)); self } }
问题根源
初始化时未修改原Controller的Router:
Controller::new()中创建初始controller后,链式调用的.create()、.list()等方法返回的是原实例的引用,但这些方法并未修改原实例的router属性,最后返回的还是初始的空Router。路由修改仅作用于克隆实例:在
list()方法中,self.router.clone().route(...)只是对Router的副本添加路由,原Controller的router完全没有被更新,相当于白执行了添加路由的操作。
修复方案
步骤1:修改Controller方法为可变引用,更新内部Router
将所有Controller的方法(如list、create等)改为接收&mut self,并将self.router替换为添加路由后的新Router实例(Axum的Router是不可变的,每次调用.route()都会生成新实例):
以list()方法为例:
impl Controller { pub fn list(&mut self) -> &mut Self { // 保留handler代码不变 #[debug_handler] pub async fn handler( State(state): State<AppState>, ) -> impl IntoResponse { match state.query_port.read().unwrap().list() { Ok(res) => (StatusCode::OK, Json(res)).into_response(), Err(err) => { (StatusCode::INTERNAL_SERVER_ERROR, Json(err.to_string())).into_response() } } } // 直接更新self.router为添加路由后的新实例 self.router = self.router.route(PROJECTS_PATH, get(handler)); self } }
步骤2:修改Controller::new()使用可变实例
初始化时使用可变的Controller,确保链式调用的方法能修改内部的Router:
impl Controller { pub fn new() -> Self { let mut controller = Controller { router: Router::new(), }; // 链式调用可变方法,修改controller的router controller .create() .delete() .find_one() .health_check() .list() .update(); controller } }
步骤3:统一其他方法的修改逻辑
对create、delete、find_one等其他方法执行同样的修改:将方法签名改为pub fn xxx(&mut self) -> &mut Self,并在方法内用self.router = self.router.route(...)更新路由。
这样修改后,Controller的router会正确包含所有注册的路由,访问/projects时就能正确触发对应的处理器,不会再返回404。
内容的提问来源于stack exchange,提问作者A Bit of Help

