1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 07:53:00 +01:00

added extractor configuration system

This commit is contained in:
Nikolay Kim 2019-03-03 00:57:48 -08:00
parent 08fcb6891e
commit 6df85e32df
9 changed files with 210 additions and 50 deletions

View File

@ -23,7 +23,7 @@ use actix_http::http::StatusCode;
use actix_http::{HttpMessage, Response};
use actix_router::PathDeserializer;
use crate::handler::FromRequest;
use crate::handler::{ConfigStorage, ExtractorConfig, FromRequest};
use crate::request::HttpRequest;
use crate::responder::Responder;
use crate::service::ServiceFromRequest;
@ -133,6 +133,7 @@ where
{
type Error = Error;
type Future = FutureResult<Self, Error>;
type Config = ();
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
@ -219,6 +220,7 @@ where
{
type Error = Error;
type Future = FutureResult<Self, Error>;
type Config = ();
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
@ -299,16 +301,18 @@ where
{
type Error = Error;
type Future = Box<Future<Item = Self, Error = Error>>;
type Config = FormConfig;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
let cfg = FormConfig::default();
let req2 = req.clone();
let cfg = req.load_config::<FormConfig>();
let limit = cfg.limit;
let err = Rc::clone(&cfg.ehandler);
Box::new(
UrlEncoded::new(req)
.limit(cfg.limit)
.limit(limit)
.map_err(move |e| (*err)(e, &req2))
.map(Form),
)
@ -356,6 +360,7 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
/// );
/// }
/// ```
#[derive(Clone)]
pub struct FormConfig {
limit: usize,
ehandler: Rc<Fn(UrlencodedError, &HttpRequest) -> Error>,
@ -363,13 +368,13 @@ pub struct FormConfig {
impl FormConfig {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
pub fn error_handler<F>(mut self, f: F) -> Self
where
F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static,
{
@ -378,6 +383,8 @@ impl FormConfig {
}
}
impl ExtractorConfig for FormConfig {}
impl Default for FormConfig {
fn default() -> Self {
FormConfig {
@ -509,16 +516,18 @@ where
{
type Error = Error;
type Future = Box<Future<Item = Self, Error = Error>>;
type Config = JsonConfig;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
let cfg = JsonConfig::default();
let req2 = req.clone();
let cfg = req.load_config::<JsonConfig>();
let limit = cfg.limit;
let err = Rc::clone(&cfg.ehandler);
Box::new(
JsonBody::new(req)
.limit(cfg.limit)
.limit(limit)
.map_err(move |e| (*err)(e, &req2))
.map(Json),
)
@ -555,6 +564,7 @@ where
/// });
/// }
/// ```
#[derive(Clone)]
pub struct JsonConfig {
limit: usize,
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest) -> Error>,
@ -562,13 +572,13 @@ pub struct JsonConfig {
impl JsonConfig {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
pub fn error_handler<F>(mut self, f: F) -> Self
where
F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static,
{
@ -577,6 +587,8 @@ impl JsonConfig {
}
}
impl ExtractorConfig for JsonConfig {}
impl Default for JsonConfig {
fn default() -> Self {
JsonConfig {
@ -617,16 +629,18 @@ where
type Error = Error;
type Future =
Either<Box<Future<Item = Bytes, Error = Error>>, FutureResult<Bytes, Error>>;
type Config = PayloadConfig;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
let cfg = PayloadConfig::default();
let cfg = req.load_config::<PayloadConfig>();
if let Err(e) = cfg.check_mimetype(req) {
return Either::B(err(e));
}
Either::A(Box::new(MessageBody::new(req).limit(cfg.limit).from_err()))
let limit = cfg.limit;
Either::A(Box::new(MessageBody::new(req).limit(limit).from_err()))
}
}
@ -664,10 +678,11 @@ where
type Error = Error;
type Future =
Either<Box<Future<Item = String, Error = Error>>, FutureResult<String, Error>>;
type Config = PayloadConfig;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
let cfg = PayloadConfig::default();
let cfg = req.load_config::<PayloadConfig>();
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
@ -679,10 +694,11 @@ where
Ok(enc) => enc,
Err(e) => return Either::B(err(e.into())),
};
let limit = cfg.limit;
Either::A(Box::new(
MessageBody::new(req)
.limit(cfg.limit)
.limit(limit)
.from_err()
.and_then(move |body| {
let enc: *const Encoding = encoding as *const Encoding;
@ -753,6 +769,7 @@ where
{
type Error = Error;
type Future = Box<Future<Item = Option<T>, Error = Error>>;
type Config = T::Config;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
@ -816,6 +833,7 @@ where
{
type Error = Error;
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
type Config = T::Config;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
@ -827,21 +845,27 @@ where
}
/// Payload configuration for request's payload.
#[derive(Clone)]
pub struct PayloadConfig {
limit: usize,
mimetype: Option<Mime>,
}
impl PayloadConfig {
/// Create `PayloadConfig` instance and set max size of payload.
pub fn new(limit: usize) -> Self {
Self::default().limit(limit)
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Set required mime-type of the request. By default mime type is not
/// enforced.
pub fn mimetype(&mut self, mt: Mime) -> &mut Self {
pub fn mimetype(mut self, mt: Mime) -> Self {
self.mimetype = Some(mt);
self
}
@ -867,6 +891,8 @@ impl PayloadConfig {
}
}
impl ExtractorConfig for PayloadConfig {}
impl Default for PayloadConfig {
fn default() -> Self {
PayloadConfig {
@ -876,6 +902,16 @@ impl Default for PayloadConfig {
}
}
macro_rules! tuple_config ({ $($T:ident),+} => {
impl<$($T,)+> ExtractorConfig for ($($T,)+)
where $($T: ExtractorConfig + Clone,)+
{
fn store_default(ext: &mut ConfigStorage) {
$($T::store_default(ext);)+
}
}
});
macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
/// FromRequest implementation for tuple
@ -883,6 +919,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
{
type Error = Error;
type Future = $fut_type<P, $($T),+>;
type Config = ($($T::Config,)+);
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
$fut_type {
@ -932,6 +969,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
impl<P> FromRequest<P> for () {
type Error = Error;
type Future = FutureResult<(), Error>;
type Config = ();
fn from_request(_req: &mut ServiceFromRequest<P>) -> Self::Future {
ok(())
@ -942,6 +980,17 @@ impl<P> FromRequest<P> for () {
mod m {
use super::*;
tuple_config!(A);
tuple_config!(A, B);
tuple_config!(A, B, C);
tuple_config!(A, B, C, D);
tuple_config!(A, B, C, D, E);
tuple_config!(A, B, C, D, E, F);
tuple_config!(A, B, C, D, E, F, G);
tuple_config!(A, B, C, D, E, F, G, H);
tuple_config!(A, B, C, D, E, F, G, H, I);
tuple_config!(A, B, C, D, E, F, G, H, I, J);
tuple_from_req!(TupleFromRequest1, (0, A));
tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));

View File

@ -1,6 +1,8 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::{Error, Response};
use actix_http::{Error, Extensions, Response};
use actix_service::{NewService, Service, Void};
use futures::future::{ok, FutureResult};
use futures::{try_ready, Async, Future, IntoFuture, Poll};
@ -19,10 +21,41 @@ pub trait FromRequest<P>: Sized {
/// Future that resolves to a Self
type Future: Future<Item = Self, Error = Self::Error>;
/// Configuration for the extractor
type Config: ExtractorConfig;
/// Convert request to a Self
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future;
}
/// Storage for extractor configs
#[derive(Default)]
pub struct ConfigStorage {
pub(crate) storage: Option<Rc<Extensions>>,
}
impl ConfigStorage {
pub fn store<C: ExtractorConfig>(&mut self, config: C) {
if self.storage.is_none() {
self.storage = Some(Rc::new(Extensions::new()));
}
if let Some(ref mut ext) = self.storage {
Rc::get_mut(ext).unwrap().insert(config);
}
}
}
pub trait ExtractorConfig: Default + Clone + 'static {
/// Set default configuration to config storage
fn store_default(ext: &mut ConfigStorage) {
ext.store(Self::default())
}
}
impl ExtractorConfig for () {
fn store_default(_: &mut ConfigStorage) {}
}
/// Handler converter factory
pub trait Factory<T, R>: Clone
where
@ -288,19 +321,17 @@ where
/// Extract arguments from request
pub struct Extract<P, T: FromRequest<P>> {
config: Rc<RefCell<Option<Rc<Extensions>>>>,
_t: PhantomData<(P, T)>,
}
impl<P, T: FromRequest<P>> Extract<P, T> {
pub fn new() -> Self {
Extract { _t: PhantomData }
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>) -> Self {
Extract {
config,
_t: PhantomData,
}
}
impl<P, T: FromRequest<P>> Default for Extract<P, T> {
fn default() -> Self {
Self::new()
}
}
impl<P, T: FromRequest<P>> NewService for Extract<P, T> {
@ -312,11 +343,15 @@ impl<P, T: FromRequest<P>> NewService for Extract<P, T> {
type Future = FutureResult<Self::Service, ()>;
fn new_service(&self, _: &()) -> Self::Future {
ok(ExtractService { _t: PhantomData })
ok(ExtractService {
_t: PhantomData,
config: self.config.borrow().clone(),
})
}
}
pub struct ExtractService<P, T: FromRequest<P>> {
config: Option<Rc<Extensions>>,
_t: PhantomData<(P, T)>,
}
@ -331,7 +366,7 @@ impl<P, T: FromRequest<P>> Service for ExtractService<P, T> {
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let mut req = req.into();
let mut req = ServiceFromRequest::new(req, self.config.clone());
ExtractResponse {
fut: T::from_request(&mut req),
req: Some(req),
@ -365,7 +400,6 @@ impl<P, T: FromRequest<P>> Future for ExtractResponse<P, T> {
macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
impl<Func, $($T,)+ Res> Factory<($($T,)+), Res> for Func
where Func: Fn($($T,)+) -> Res + Clone + 'static,
//$($T,)+
Res: Responder + 'static,
{
fn call(&self, param: ($($T,)+)) -> Res {

View File

@ -1,7 +1,7 @@
#![allow(clippy::type_complexity)]
mod app;
mod extractor;
pub mod extractor;
pub mod handler;
// mod info;
pub mod blocking;
@ -20,7 +20,7 @@ pub use actix_http::Response as HttpResponse;
pub use actix_http::{http, Error, HttpMessage, ResponseError};
pub use crate::app::App;
pub use crate::extractor::{Form, Json, Path, Query};
pub use crate::extractor::{Form, Json, Path, PayloadConfig, Query};
pub use crate::handler::FromRequest;
pub use crate::request::HttpRequest;
pub use crate::resource::Resource;

View File

@ -143,6 +143,7 @@ impl HttpMessage for HttpRequest {
impl<P> FromRequest<P> for HttpRequest {
type Error = Error;
type Future = FutureResult<Self, Error>;
type Config = ();
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {

View File

@ -75,7 +75,7 @@ where
/// }
/// ```
pub fn route(mut self, route: Route<P>) -> Self {
self.routes.push(route);
self.routes.push(route.finish());
self
}

View File

@ -1,11 +1,15 @@
use std::cell::RefCell;
use std::rc::Rc;
use actix_http::{http::Method, Error, Response};
use actix_http::{http::Method, Error, Extensions, Response};
use actix_service::{NewService, Service};
use futures::{Async, Future, IntoFuture, Poll};
use crate::filter::{self, Filter};
use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, FromRequest, Handle};
use crate::handler::{
AsyncFactory, AsyncHandle, ConfigStorage, Extract, ExtractorConfig, Factory,
FromRequest, Handle,
};
use crate::responder::Responder;
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
use crate::HttpResponse;
@ -37,33 +41,50 @@ type BoxedRouteNewService<Req, Res> = Box<
pub struct Route<P> {
service: BoxedRouteNewService<ServiceRequest<P>, ServiceResponse>,
filters: Rc<Vec<Box<Filter>>>,
config: ConfigStorage,
config_ref: Rc<RefCell<Option<Rc<Extensions>>>>,
}
impl<P: 'static> Route<P> {
/// Create new route which matches any request.
pub fn new() -> Route<P> {
let config_ref = Rc::new(RefCell::new(None));
Route {
service: Box::new(RouteNewService::new(Extract::new().and_then(
service: Box::new(RouteNewService::new(
Extract::new(config_ref.clone()).and_then(
Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()),
))),
),
)),
filters: Rc::new(Vec::new()),
config: ConfigStorage::default(),
config_ref,
}
}
/// Create new `GET` route.
pub fn get() -> Route<P> {
Route::new().method(Method::GET)
}
/// Create new `POST` route.
pub fn post() -> Route<P> {
Route::new().method(Method::POST)
}
/// Create new `PUT` route.
pub fn put() -> Route<P> {
Route::new().method(Method::PUT)
}
/// Create new `DELETE` route.
pub fn delete() -> Route<P> {
Route::new().method(Method::DELETE)
}
pub(crate) fn finish(self) -> Self {
*self.config_ref.borrow_mut() = self.config.storage.clone();
self
}
}
impl<P> NewService for Route<P> {
@ -260,8 +281,10 @@ impl<P: 'static> Route<P> {
T: FromRequest<P> + 'static,
R: Responder + 'static,
{
T::Config::store_default(&mut self.config);
self.service = Box::new(RouteNewService::new(
Extract::new().and_then(Handle::new(handler).map_err(|_| panic!())),
Extract::new(self.config_ref.clone())
.and_then(Handle::new(handler).map_err(|_| panic!())),
));
self
}
@ -305,10 +328,39 @@ impl<P: 'static> Route<P> {
R::Error: Into<Error>,
{
self.service = Box::new(RouteNewService::new(
Extract::new().and_then(AsyncHandle::new(handler).map_err(|_| panic!())),
Extract::new(self.config_ref.clone())
.and_then(AsyncHandle::new(handler).map_err(|_| panic!())),
));
self
}
/// This method allows to add extractor configuration
/// for specific route.
///
/// ```rust
/// use actix_web::{web, extractor, App};
///
/// /// extract text data from request
/// fn index(body: String) -> String {
/// format!("Body {}!", body)
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.route(
/// web::get()
/// // limit size of the payload
/// .config(extractor::PayloadConfig::new(4096))
/// // register handler
/// .to(index)
/// )
/// });
/// }
/// ```
pub fn config<C: ExtractorConfig>(mut self, config: C) -> Self {
self.config.store(config);
self
}
}
// pub struct RouteServiceBuilder<P, T, U1, U2> {

View File

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::cell::{Ref, RefMut};
use std::rc::Rc;
@ -167,9 +168,18 @@ impl<P> std::ops::DerefMut for ServiceRequest<P> {
pub struct ServiceFromRequest<P> {
req: HttpRequest,
payload: Payload<P>,
config: Option<Rc<Extensions>>,
}
impl<P> ServiceFromRequest<P> {
pub(crate) fn new(req: ServiceRequest<P>, config: Option<Rc<Extensions>>) -> Self {
Self {
req: req.req,
payload: req.payload,
config,
}
}
#[inline]
pub fn into_request(self) -> HttpRequest {
self.req
@ -180,6 +190,16 @@ impl<P> ServiceFromRequest<P> {
pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse {
ServiceResponse::new(self.req, err.into().into())
}
/// Load extractor configuration
pub fn load_config<T: Clone + Default + 'static>(&self) -> Cow<T> {
if let Some(ref ext) = self.config {
if let Some(cfg) = ext.get::<T>() {
return Cow::Borrowed(cfg);
}
}
Cow::Owned(T::default())
}
}
impl<P> std::ops::Deref for ServiceFromRequest<P> {
@ -204,15 +224,6 @@ impl<P> HttpMessage for ServiceFromRequest<P> {
}
}
impl<P> From<ServiceRequest<P>> for ServiceFromRequest<P> {
fn from(req: ServiceRequest<P>) -> Self {
Self {
req: req.req,
payload: req.payload,
}
}
}
pub struct ServiceResponse<B = Body> {
request: HttpRequest,
response: Response<B>,

View File

@ -48,6 +48,7 @@ impl<S> Clone for State<S> {
impl<S: 'static, P> FromRequest<P> for State<S> {
type Error = Error;
type Future = FutureResult<Self, Error>;
type Config = ();
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {

View File

@ -9,7 +9,7 @@ use actix_router::{Path, Url};
use bytes::Bytes;
use crate::request::HttpRequest;
use crate::service::ServiceRequest;
use crate::service::{ServiceFromRequest, ServiceRequest};
/// Test `Request` builder
///
@ -133,7 +133,7 @@ impl TestRequest {
}
/// Complete request creation and generate `HttpRequest` instance
pub fn request(mut self) -> HttpRequest {
pub fn to_request(mut self) -> HttpRequest {
let req = self.req.finish();
ServiceRequest::new(
@ -143,4 +143,16 @@ impl TestRequest {
)
.into_request()
}
/// Complete request creation and generate `ServiceFromRequest` instance
pub fn to_from(mut self) -> ServiceFromRequest<PayloadStream> {
let req = self.req.finish();
let req = ServiceRequest::new(
Path::new(Url::new(req.uri().clone())),
req,
Rc::new(self.extensions),
);
ServiceFromRequest::new(req, None)
}
}