fix(upload): make ConnectInfo optional to prevent 500 in release builds
Some checks failed
CI / check (push) Failing after 26m31s
CI / build (push) Has been skipped

upload_image declared ConnectInfo<SocketAddr> as a required Axum extractor,
but dioxus::server::serve() owns the listener and cannot call
into_make_service_with_connect_info::<SocketAddr>(), so the request extension
is absent on manually-merged routes. The extractor failed and Axum returned
500. dev (dx serve) passed by luck; release builds failed consistently.

Match serve_image's graceful-degradation pattern: take
Option<Extension<ConnectInfo<SocketAddr>>>, fall back to the 'unknown'
rate-limit bucket when missing. Production should deploy behind a reverse
proxy with TRUSTED_PROXY_COUNT so rate limiting keys on the real client IP.

Also correct the misleading comment in main.rs: axum 0.8 does have
ConnectInfoLayer/into_make_service_with_connect_info; the real blocker is
that Dioxus owns the listener.
This commit is contained in:
xfy 2026-06-18 17:07:07 +08:00
parent 20e352bf85
commit 67212a52b3
2 changed files with 15 additions and 7 deletions

View File

@ -7,7 +7,7 @@
#[cfg(feature = "server")]
use axum::{
extract::{ConnectInfo, Multipart},
extract::{ConnectInfo, Extension, Multipart},
http::{HeaderMap, StatusCode},
response::Json,
};
@ -70,13 +70,19 @@ fn validate_raw_image(data: &[u8], mime_type: &str) -> bool {
///
/// 流程:限流 → 解析 session → 校验 admin → 读取 multipart → 校验类型/大小 →
/// 转码(如适用)→ 按日期落盘 → 返回相对 URL。
///
/// `ConnectInfo` 以可选扩展注入:`dioxus::server::serve()` 接管了 listener
/// 无法调用 `into_make_service_with_connect_info::<SocketAddr>()`,所以这里
/// 与 `serve_image` 保持一致的优雅降级——扩展缺失时退回 `"unknown"` 限流桶。
/// 生产环境应在反向代理后部署并配置 `TRUSTED_PROXY_COUNT`,让限流拿到真实 IP。
pub async fn upload_image(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
connect_info: Option<Extension<ConnectInfo<SocketAddr>>>,
headers: HeaderMap,
mut multipart: Multipart,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
// 0. Rate limit check
let ip = crate::api::rate_limit::get_client_ip_with_peer(&headers, Some(addr));
let peer = connect_info.map(|Extension(ConnectInfo(addr))| addr);
let ip = crate::api::rate_limit::get_client_ip_with_peer(&headers, peer);
if let Err(msg) = crate::api::rate_limit::check_upload_limit(&ip) {
return Err((
StatusCode::TOO_MANY_REQUESTS,

View File

@ -326,10 +326,12 @@ fn main() {
));
// 静态资源路由:图片文件服务。
// 注意axum 0.8 没有 ConnectInfoLayer且 dioxus::server::serve 不会把
// ConnectInfo 扩展传播到手动 merge 的路由,所以 serve_image 使用
// Option<Extension<ConnectInfo<SocketAddr>>> 优雅降级。生产环境应在反向代理后
// 部署并配置 TRUSTED_PROXY_COUNT使限流能拿到真实客户端 IP。
// 注意:`dioxus::server::serve()` 接管了 listener 与 `into_make_service`
// 调用,没有机会换成 `into_make_service_with_connect_info::<SocketAddr>()`
// 所以手动 merge 进来的路由(含 static_routes拿不到 `ConnectInfo` 扩展。
// serve_image / upload_image 因此都用 `Option<Extension<ConnectInfo<SocketAddr>>>`
// 优雅降级。生产环境应在反向代理后部署并配置 TRUSTED_PROXY_COUNT
// 使限流能拿到真实客户端 IP。
let static_routes = axum::Router::new()
.route(
"/uploads/{*path}",