Merge branch 'master' into feat/redirect

This commit is contained in:
Rob Ede 2022-11-25 21:04:42 +00:00
commit db97179c22
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
6 changed files with 167 additions and 29 deletions

View File

@ -6,8 +6,10 @@
- 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 rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961]
- Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265]
[#1961]: https://github.com/actix/actix-web/pull/1961
[#2265]: https://github.com/actix/actix-web/pull/2265
[#2631]: https://github.com/actix/actix-web/pull/2631
[#2784]: https://github.com/actix/actix-web/pull/2784
[#2867]: https://github.com/actix/actix-web/pull/2867

View File

@ -0,0 +1,99 @@
use super::{Guard, GuardContext};
use crate::http::header::Accept;
/// A guard that verifies that an `Accept` header is present and it contains a compatible MIME type.
///
/// An exception is that matching `*/*` must be explicitly enabled because most browsers send this
/// as part of their `Accept` header for almost every request.
///
/// # Examples
/// ```
/// use actix_web::{guard::Acceptable, web, HttpResponse};
///
/// web::resource("/images")
/// .guard(Acceptable::new(mime::IMAGE_STAR))
/// .default_service(web::to(|| async {
/// HttpResponse::Ok().body("only called when images responses are acceptable")
/// }));
/// ```
#[derive(Debug, Clone)]
pub struct Acceptable {
mime: mime::Mime,
/// Wether to match `*/*` mime type.
///
/// Defaults to false because it's not very useful otherwise.
match_star_star: bool,
}
impl Acceptable {
/// Constructs new `Acceptable` guard with the given `mime` type/pattern.
pub fn new(mime: mime::Mime) -> Self {
Self {
mime,
match_star_star: false,
}
}
/// Allows `*/*` in the `Accept` header to pass the guard check.
pub fn match_star_star(mut self) -> Self {
self.match_star_star = true;
self
}
}
impl Guard for Acceptable {
fn check(&self, ctx: &GuardContext<'_>) -> bool {
let accept = match ctx.header::<Accept>() {
Some(hdr) => hdr,
None => return false,
};
let target_type = self.mime.type_();
let target_subtype = self.mime.subtype();
for mime in accept.0.into_iter().map(|q| q.item) {
return match (mime.type_(), mime.subtype()) {
(typ, subtype) if typ == target_type && subtype == target_subtype => true,
(typ, mime::STAR) if typ == target_type => true,
(mime::STAR, mime::STAR) if self.match_star_star => true,
_ => continue,
};
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{http::header, test::TestRequest};
#[test]
fn test_acceptable() {
let req = TestRequest::default().to_srv_request();
assert!(!Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx()));
let req = TestRequest::default()
.insert_header((header::ACCEPT, "application/json"))
.to_srv_request();
assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx()));
let req = TestRequest::default()
.insert_header((header::ACCEPT, "text/html, application/json"))
.to_srv_request();
assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx()));
}
#[test]
fn test_acceptable_star() {
let req = TestRequest::default()
.insert_header((header::ACCEPT, "text/html, */*;q=0.8"))
.to_srv_request();
assert!(Acceptable::new(mime::APPLICATION_JSON)
.match_star_star()
.check(&req.guard_ctx()));
}
}

View File

@ -56,6 +56,9 @@ use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead
use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _};
mod acceptable;
pub use self::acceptable::Acceptable;
/// Provides access to request parts that are useful during routing.
#[derive(Debug)]
pub struct GuardContext<'a> {

View File

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

View File

@ -3,7 +3,6 @@
use std::borrow::Cow;
use actix_utils::future::ready;
use log::debug;
use crate::{
dev::{fn_service, AppService, HttpServiceFactory, ResourceDef, ServiceRequest},
@ -13,25 +12,35 @@ use crate::{
/// 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.
/// article][mdn-redirects] on why 307 is preferred over 302.
///
/// # Examples
/// As service:
/// ```
/// use actix_web::{web, App};
///
/// App::new()
/// // redirect "/duck" to DuckDuckGo
/// .service(web::Redirect::new("/duck", "https://duckduckgo.com/"))
/// .service(web::redirect("/duck", "https://duck.com"))
/// .service(
/// // redirect "/api/old" to "/api/new" using `web::redirect` helper
/// // redirect "/api/old" to "/api/new"
/// web::scope("/api").service(web::redirect("/old", "/new"))
/// );
/// ```
///
/// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections
/// As responder:
/// ```
/// use actix_web::web::Redirect;
///
/// async fn handler() -> impl Responder {
/// // sends a permanent (308) redirect to duck.com
/// Redirect::to("https://duck.com").permanent()
/// }
/// # actix_web::web::to(handler);
/// ```
///
/// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#temporary_redirections
#[derive(Debug, Clone)]
pub struct Redirect {
from: Cow<'static, str>,
@ -40,23 +49,26 @@ pub struct Redirect {
}
impl Redirect {
/// Create a new `Redirect` service, first providing the path that should be redirected.
/// Construct a new `Redirect` service that matches a path.
///
/// The default "to" location is the root path (`/`). It is expected that you should call either
/// [`to`](Redirect::to) or [`to`](Redirect::to) afterwards.
/// This service will match exact paths equal to `from` within the current scope. I.e., when
/// registered on the root `App`, it will match exact, whole paths. But when registered on a
/// `Scope`, it will match paths under that scope, ignoring the defined scope prefix, just like
/// a normal `Resource` or `Route`.
///
/// Note this function has no effect when used as a responder.
/// The `to` argument can be path or URL; whatever is provided shall be used verbatim when
/// setting the redirect location. This means that relative paths can be used to navigate
/// relatively to matched paths.
///
/// 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.
/// Prefer [`Redirect::to()`](Self::to) when using `Redirect` as a responder since `from` has
/// no meaning in that context.
///
/// # Examples
/// ```
/// # use actix_web::web::Redirect;
/// // redirects "/oh/hi/mark" to "/oh/bye/mark"
/// Redirect::new("/oh/hi/mark", "../../bye/mark");
/// # use actix_web::{web::Redirect, App};
/// App::new()
/// // redirects "/oh/hi/mark" to "/oh/bye/johnny"
/// .service(Redirect::new("/oh/hi/mark", "../../bye/johnny"));
/// ```
pub fn new(from: impl Into<Cow<'static, str>>, to: impl Into<Cow<'static, str>>) -> Self {
Self {
@ -66,9 +78,20 @@ impl Redirect {
}
}
/// Shortcut for creating a redirect to use as a `Responder`.
/// Construct a new `Redirect` to use as a responder.
///
/// Only receives a `to` argument since responders do not need to do route matching.
/// Only receives the `to` argument since responders do not need to do route matching.
///
/// # Examples
/// ```
/// use actix_web::web::Redirect;
///
/// async fn admin_page() -> impl Responder {
/// // sends a temporary 307 redirect to the login path
/// Redirect::to("/login")
/// }
/// # actix_web::web::to(handler);
/// ```
pub fn to(to: impl Into<Cow<'static, str>>) -> Self {
Self {
from: "/".into(),
@ -79,7 +102,7 @@ impl Redirect {
/// Use the "308 Permanent Redirect" status when responding.
///
/// See [this MDN article](mdn-redirects) on why 308 is preferred over 301.
/// 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 {
@ -88,13 +111,20 @@ impl Redirect {
/// Use the "307 Temporary Redirect" status when responding.
///
/// See [this MDN article](mdn-redirects) on why 307 is preferred over 302.
/// 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)
}
/// Use the "303 See Other" status when responding.
///
/// This status code is semantically correct as the response to a successful login, for example.
pub fn see_other(self) -> Self {
self.using_status_code(StatusCode::SEE_OTHER)
}
/// 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`
@ -102,8 +132,7 @@ impl Redirect {
/// 301 and 302 codes, respectively.
///
/// ```
/// # use actix_web::http::StatusCode;
/// # use actix_web::web::Redirect;
/// # use actix_web::{http::StatusCode, web::Redirect};
/// // redirects would use "301 Moved Permanently" status code
/// Redirect::new("/old", "/new")
/// .using_status_code(StatusCode::MOVED_PERMANENTLY);
@ -140,7 +169,7 @@ impl Responder for Redirect {
if let Ok(hdr_val) = self.to.parse() {
res.headers_mut().insert(LOCATION, hdr_val);
} else {
debug!(
log::error!(
"redirect target location can not be converted to header value: {:?}",
self.to
);

View File

@ -11,8 +11,10 @@
//! - [`Bytes`]: Raw payload
//!
//! # Responders
//! - [`Json`]: JSON request payload
//! - [`Bytes`]: Raw request payload
//! - [`Json`]: JSON response
//! - [`Form`]: URL-encoded response
//! - [`Bytes`]: Raw bytes response
//! - [`Redirect`](Redirect::to): Convenient redirect responses
use std::{borrow::Cow, future::Future};
@ -46,6 +48,7 @@ pub use crate::types::*;
/// For instance, to route `GET`-requests on any route matching `/users/{userid}/{friend}` and store
/// `userid` and `friend` in the exposed `Path` object:
///
/// # Examples
/// ```
/// use actix_web::{web, App, HttpResponse};
///
@ -75,6 +78,7 @@ pub fn resource<T: IntoPatterns>(path: T) -> Resource {
/// - `/{project_id}/path2`
/// - `/{project_id}/path3`
///
/// # Examples
/// ```
/// use actix_web::{web, App, HttpResponse};
///
@ -188,11 +192,13 @@ pub fn service<T: IntoPatterns>(path: T) -> WebService {
///
/// See [`Redirect`] docs for usage details.
///
/// # Examples
/// ```
/// use actix_web::{web, App};
///
/// let app = App::new()
/// .service(web::redirect("/one", "/two"));
/// // the client will resolve this redirect to /api/to-path
/// .service(web::redirect("/api/from-path", "to-path"));
/// ```
pub fn redirect(
from: impl Into<Cow<'static, str>>,