From 646070ae7ace982acf00e80d8673a05750cecf1b Mon Sep 17 00:00:00 2001 From: weisd Date: Fri, 11 Jul 2025 07:38:42 +0800 Subject: [PATCH] Feat/browser redirect layer (#167) * feat: add browser redirect layer to route GET requests to console * refactor: move RedirectLayer to separate layer.rs file * feat: restrict redirect layer to only handle root path and index.html * feat: restrict redirect layer to only handle root path /rustfs and index.html --- rustfs/src/server/http.rs | 2 + rustfs/src/server/layer.rs | 77 ++++++++++++++++++++++++++++++++++++++ rustfs/src/server/mod.rs | 1 + 3 files changed, 80 insertions(+) create mode 100644 rustfs/src/server/layer.rs diff --git a/rustfs/src/server/http.rs b/rustfs/src/server/http.rs index a85be294d..7ffb2c478 100644 --- a/rustfs/src/server/http.rs +++ b/rustfs/src/server/http.rs @@ -4,6 +4,7 @@ use crate::auth::IAMAuth; use crate::admin; use crate::config; use crate::server::hybrid::hybrid; +use crate::server::layer::RedirectLayer; use crate::server::{ServiceState, ServiceStateManager}; use crate::storage; use bytes::Bytes; @@ -346,6 +347,7 @@ fn process_connection( }), ) .layer(CorsLayer::permissive()) + .layer(RedirectLayer) .service(service); let hybrid_service = TowerToHyperService::new(hybrid_service); diff --git a/rustfs/src/server/layer.rs b/rustfs/src/server/layer.rs new file mode 100644 index 000000000..d00af7172 --- /dev/null +++ b/rustfs/src/server/layer.rs @@ -0,0 +1,77 @@ +use crate::server::hybrid::HybridBody; +use http::{Request as HttpRequest, Response, StatusCode}; +use hyper::body::Incoming; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tower::{Layer, Service}; +use tracing::debug; + +/// Redirect layer that redirects browser requests to the console +#[derive(Clone)] +pub struct RedirectLayer; + +impl Layer for RedirectLayer { + type Service = RedirectService; + + fn layer(&self, inner: S) -> Self::Service { + RedirectService { inner } + } +} + +/// Service implementation for redirect functionality +#[derive(Clone)] +pub struct RedirectService { + inner: S, +} + +impl Service> for RedirectService +where + S: Service, Response = Response>> + Clone + Send + 'static, + S::Future: Send + 'static, + S::Error: Into> + Send + 'static, + RestBody: Default + Send + 'static, + GrpcBody: Send + 'static, +{ + type Response = Response>; + type Error = Box; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, req: HttpRequest) -> Self::Future { + // Check if this is a GET request without Authorization header and User-Agent contains Mozilla + // and the path is either "/" or "/index.html" + let path = req.uri().path().trim_end_matches('/'); + let should_redirect = req.method() == http::Method::GET + && !req.headers().contains_key(http::header::AUTHORIZATION) + && req + .headers() + .get(http::header::USER_AGENT) + .and_then(|v| v.to_str().ok()) + .map(|ua| ua.contains("Mozilla")) + .unwrap_or(false) + && (path.is_empty() || path == "/rustfs" || path == "/index.html"); + + if should_redirect { + debug!("Redirecting browser request from {} to console", path); + + // Create redirect response + let redirect_response = Response::builder() + .status(StatusCode::FOUND) + .header(http::header::LOCATION, "/rustfs/console/") + .body(HybridBody::Rest { + rest_body: RestBody::default(), + }) + .expect("failed to build redirect response"); + + return Box::pin(async move { Ok(redirect_response) }); + } + + // Otherwise, forward to the next service + let mut inner = self.inner.clone(); + Box::pin(async move { inner.call(req).await.map_err(Into::into) }) + } +} diff --git a/rustfs/src/server/mod.rs b/rustfs/src/server/mod.rs index 7663755a2..5efd86170 100644 --- a/rustfs/src/server/mod.rs +++ b/rustfs/src/server/mod.rs @@ -14,6 +14,7 @@ mod http; mod hybrid; +mod layer; mod service_state; pub(crate) use http::start_http_server; pub(crate) use service_state::SHUTDOWN_TIMEOUT;