Compare commits

...

1 Commits

Author SHA1 Message Date
weisd
646070ae7a 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
2025-07-11 07:38:42 +08:00
3 changed files with 80 additions and 0 deletions

View File

@@ -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);

View File

@@ -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<S> Layer<S> for RedirectLayer {
type Service = RedirectService<S>;
fn layer(&self, inner: S) -> Self::Service {
RedirectService { inner }
}
}
/// Service implementation for redirect functionality
#[derive(Clone)]
pub struct RedirectService<S> {
inner: S,
}
impl<S, RestBody, GrpcBody> Service<HttpRequest<Incoming>> for RedirectService<S>
where
S: Service<HttpRequest<Incoming>, Response = Response<HybridBody<RestBody, GrpcBody>>> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + Send + 'static,
RestBody: Default + Send + 'static,
GrpcBody: Send + 'static,
{
type Response = Response<HybridBody<RestBody, GrpcBody>>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, req: HttpRequest<Incoming>) -> 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) })
}
}

View File

@@ -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;