add redirect service

This commit is contained in:
Rob Ede 2022-11-25 18:28:52 +00:00
parent d97bd7ec17
commit aa3aa1c93b
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
5 changed files with 233 additions and 2 deletions

View File

@ -10,6 +10,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
with: { components: rustfmt }
- run: cargo fmt --all -- --check - run: cargo fmt --all -- --check
clippy: clippy:
@ -21,7 +22,6 @@ jobs:
with: { components: clippy } with: { components: clippy }
- name: Generate Cargo.lock - name: Generate Cargo.lock
uses: actions-rs/cargo@v1
run: cargo generate-lockfile run: cargo generate-lockfile
- name: Cache Dependencies - name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0 uses: Swatinem/rust-cache@v1.2.0

View File

@ -5,7 +5,9 @@
- Add `ContentDisposition::attachment` constructor. [#2867] - Add `ContentDisposition::attachment` constructor. [#2867]
- Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784] - Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784]
- Add `Logger::custom_response_replace()`. [#2631] - Add `Logger::custom_response_replace()`. [#2631]
- Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961]
[#1961]: https://github.com/actix/actix-web/pull/1961
[#2631]: https://github.com/actix/actix-web/pull/2631 [#2631]: https://github.com/actix/actix-web/pull/2631
[#2784]: https://github.com/actix/actix-web/pull/2784 [#2784]: https://github.com/actix/actix-web/pull/2784
[#2867]: https://github.com/actix/actix-web/pull/2867 [#2867]: https://github.com/actix/actix-web/pull/2867

View File

@ -86,6 +86,7 @@ mod helpers;
pub mod http; pub mod http;
mod info; mod info;
pub mod middleware; pub mod middleware;
mod redirect;
mod request; mod request;
mod request_data; mod request_data;
mod resource; mod resource;
@ -106,6 +107,7 @@ pub use crate::error::Result;
pub use crate::error::{Error, ResponseError}; pub use crate::error::{Error, ResponseError};
pub use crate::extract::FromRequest; pub use crate::extract::FromRequest;
pub use crate::handler::Handler; pub use crate::handler::Handler;
pub use crate::redirect::Redirect;
pub use crate::request::HttpRequest; pub use crate::request::HttpRequest;
pub use crate::resource::Resource; pub use crate::resource::Resource;
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder}; pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};

209
actix-web/src/redirect.rs Normal file
View File

@ -0,0 +1,209 @@
//! See [`Redirect`] for service/responder documentation.
use std::borrow::Cow;
use actix_utils::future::ready;
use log::debug;
use crate::{
dev::{fn_service, AppService, HttpServiceFactory, ResourceDef, ServiceRequest},
http::{header::LOCATION, StatusCode},
HttpRequest, HttpResponse, Responder,
};
/// An HTTP service for redirecting one path to another path or URL.
///
/// Redirects are either [relative](Redirect::to) or [absolute](Redirect::to).
///
/// By default, the "307 Temporary Redirect" status is used when responding. See [this MDN
/// article](mdn-redirects) on why 307 is preferred over 302.
///
/// # Examples
/// ```
/// use actix_web::{web, App};
///
/// App::new()
/// // redirect "/duck" to DuckDuckGo
/// .service(web::Redirect::new("/duck", "https://duckduckgo.com/"))
/// .service(
/// // redirect "/api/old" to "/api/new" using `web::redirect` helper
/// web::scope("/api").service(web::redirect("/old", "/new"))
/// );
/// ```
///
/// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections
#[derive(Debug, Clone)]
pub struct Redirect {
from: Cow<'static, str>,
to: Cow<'static, str>,
status_code: StatusCode,
}
impl Redirect {
/// Create a new `Redirect` service, first providing the path that should be redirected.
///
/// The default "to" location is the root path (`/`). It is expected that you should call either
/// [`to`](Redirect::to) or [`to`](Redirect::to) afterwards.
///
/// Note this function has no effect when used as a responder.
///
/// Redirect to an address or path.
///
/// Whatever argument is provided shall be used as-is when setting the redirect location.
/// You can also use relative paths to navigate relative to the matched path.
///
/// # Examples
/// ```
/// # use actix_web::web::Redirect;
/// // redirects "/oh/hi/mark" to "/oh/bye/mark"
/// Redirect::new("/oh/hi/mark", "../../bye/mark");
/// ```
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,
}
}
/// Shortcut for creating a redirect to use as a `Responder`.
///
/// Only receives a `to` argument since responders do not need to do route matching.
pub fn to(to: impl Into<Cow<'static, str>>) -> Self {
Self {
from: "/".into(),
to: to.into(),
status_code: StatusCode::TEMPORARY_REDIRECT,
}
}
/// Use the "308 Permanent Redirect" status when responding.
///
/// See [this MDN article](mdn-redirects) on why 308 is preferred over 301.
///
/// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections
pub fn permanent(self) -> Self {
self.using_status_code(StatusCode::PERMANENT_REDIRECT)
}
/// Use the "307 Temporary Redirect" status when responding.
///
/// See [this MDN article](mdn-redirects) on why 307 is preferred over 302.
///
/// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#temporary_redirections
pub fn temporary(self) -> Self {
self.using_status_code(StatusCode::TEMPORARY_REDIRECT)
}
/// Allows the use of custom status codes for less common redirect types.
///
/// In most cases, the default status ("308 Permanent Redirect") or using the `temporary`
/// method, which uses the "307 Temporary Redirect" status have more consistent behavior than
/// 301 and 302 codes, respectively.
///
/// ```
/// # use actix_web::http::StatusCode;
/// # use actix_web::web::Redirect;
/// // redirects would use "301 Moved Permanently" status code
/// Redirect::new("/old", "/new")
/// .using_status_code(StatusCode::MOVED_PERMANENTLY);
///
/// // redirects would use "302 Found" status code
/// Redirect::new("/old", "/new")
/// .using_status_code(StatusCode::FOUND);
/// ```
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 {
debug!(
"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");
}
}

View File

@ -14,7 +14,7 @@
//! - [`Json`]: JSON request payload //! - [`Json`]: JSON request payload
//! - [`Bytes`]: Raw request payload //! - [`Bytes`]: Raw request payload
use std::future::Future; use std::{borrow::Cow, future::Future};
use actix_router::IntoPatterns; use actix_router::IntoPatterns;
pub use bytes::{Buf, BufMut, Bytes, BytesMut}; pub use bytes::{Buf, BufMut, Bytes, BytesMut};
@ -26,6 +26,7 @@ use crate::{
pub use crate::config::ServiceConfig; pub use crate::config::ServiceConfig;
pub use crate::data::Data; pub use crate::data::Data;
pub use crate::redirect::Redirect;
pub use crate::request_data::ReqData; pub use crate::request_data::ReqData;
pub use crate::types::*; pub use crate::types::*;
@ -183,6 +184,23 @@ pub fn service<T: IntoPatterns>(path: T) -> WebService {
WebService::new(path) WebService::new(path)
} }
/// Create a relative or absolute redirect.
///
/// See [`Redirect`] docs for usage details.
///
/// ```
/// use actix_web::{web, App};
///
/// let app = App::new()
/// .service(web::redirect("/one", "/two"));
/// ```
pub fn redirect(
from: impl Into<Cow<'static, str>>,
to: impl Into<Cow<'static, str>>,
) -> Redirect {
Redirect::new(from, to)
}
/// Executes blocking function on a thread pool, returns future that resolves to result of the /// Executes blocking function on a thread pool, returns future that resolves to result of the
/// function execution. /// function execution.
pub fn block<F, R>(f: F) -> impl Future<Output = Result<R, BlockingError>> pub fn block<F, R>(f: F) -> impl Future<Output = Result<R, BlockingError>>