use std::borrow::Cow;
use actix_utils::future::ready;
use crate::{
dev::{fn_service, AppService, HttpServiceFactory, ResourceDef, ServiceRequest},
http::{header::LOCATION, StatusCode},
HttpRequest, HttpResponse, Responder,
};
#[derive(Debug, Clone)]
pub struct Redirect {
from: Cow<'static, str>,
to: Cow<'static, str>,
status_code: StatusCode,
}
impl Redirect {
pub fn new(from: impl Into<Cow<'static, str>>, to: impl Into<Cow<'static, str>>) -> Self {
Self {
from: from.into(),
to: to.into(),
status_code: StatusCode::TEMPORARY_REDIRECT,
}
}
pub fn to(to: impl Into<Cow<'static, str>>) -> Self {
Self {
from: "/".into(),
to: to.into(),
status_code: StatusCode::TEMPORARY_REDIRECT,
}
}
pub fn permanent(self) -> Self {
self.using_status_code(StatusCode::PERMANENT_REDIRECT)
}
pub fn temporary(self) -> Self {
self.using_status_code(StatusCode::TEMPORARY_REDIRECT)
}
pub fn see_other(self) -> Self {
self.using_status_code(StatusCode::SEE_OTHER)
}
pub fn using_status_code(mut self, status: StatusCode) -> Self {
self.status_code = status;
self
}
}
impl HttpServiceFactory for Redirect {
fn register(self, config: &mut AppService) {
let redirect = self.clone();
let rdef = ResourceDef::new(self.from.into_owned());
let redirect_factory = fn_service(move |mut req: ServiceRequest| {
let res = redirect.clone().respond_to(req.parts_mut().0);
ready(Ok(req.into_response(res.map_into_boxed_body())))
});
config.register_service(rdef, None, redirect_factory, None)
}
}
impl Responder for Redirect {
type Body = ();
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
let mut res = HttpResponse::with_body(self.status_code, ());
if let Ok(hdr_val) = self.to.parse() {
res.headers_mut().insert(LOCATION, hdr_val);
} else {
log::error!(
"redirect target location can not be converted to header value: {:?}",
self.to
);
}
res
}
}
#[cfg(test)]
mod tests {
use crate::{dev::Service, http::StatusCode, test, App};
use super::*;
#[actix_rt::test]
async fn absolute_redirects() {
let redirector = Redirect::new("/one", "/two").permanent();
let svc = test::init_service(App::new().service(redirector)).await;
let req = test::TestRequest::default().uri("/one").to_request();
let res = svc.call(req).await.unwrap();
assert_eq!(res.status(), StatusCode::from_u16(308).unwrap());
let hdr = res.headers().get(&LOCATION).unwrap();
assert_eq!(hdr.to_str().unwrap(), "/two");
}
#[actix_rt::test]
async fn relative_redirects() {
let redirector = Redirect::new("/one", "two").permanent();
let svc = test::init_service(App::new().service(redirector)).await;
let req = test::TestRequest::default().uri("/one").to_request();
let res = svc.call(req).await.unwrap();
assert_eq!(res.status(), StatusCode::from_u16(308).unwrap());
let hdr = res.headers().get(&LOCATION).unwrap();
assert_eq!(hdr.to_str().unwrap(), "two");
}
#[actix_rt::test]
async fn temporary_redirects() {
let external_service = Redirect::new("/external", "https://duck.com");
let svc = test::init_service(App::new().service(external_service)).await;
let req = test::TestRequest::default().uri("/external").to_request();
let res = svc.call(req).await.unwrap();
assert_eq!(res.status(), StatusCode::from_u16(307).unwrap());
let hdr = res.headers().get(&LOCATION).unwrap();
assert_eq!(hdr.to_str().unwrap(), "https://duck.com");
}
#[actix_rt::test]
async fn as_responder() {
let responder = Redirect::to("https://duck.com");
let req = test::TestRequest::default().to_http_request();
let res = responder.respond_to(&req);
assert_eq!(res.status(), StatusCode::from_u16(307).unwrap());
let hdr = res.headers().get(&LOCATION).unwrap();
assert_eq!(hdr.to_str().unwrap(), "https://duck.com");
}
}