Merge pull request #49 from actix/master

#1465 #1473 #1476 #1481
This commit is contained in:
Zhang Zhongyu 2020-05-03 01:26:22 +08:00 committed by GitHub
commit c44548e434
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 321 additions and 194 deletions

View File

@ -7,6 +7,8 @@
* Implement `std::error::Error` for our custom errors [#1422]
* Remove `failure` support for `ResponseError` since that crate
will be deprecated in the near future.
* Fix a mistake in the encoding of websocket continuation messages wherein
Item::FirstText and Item::FirstBinary are each encoded as the other.
[#1422]: https://github.com/actix/actix-web/pull/1422

View File

@ -214,7 +214,7 @@ impl Date {
write!(
self,
"{}",
OffsetDateTime::now().format("%a, %d %b %Y %H:%M:%S GMT")
OffsetDateTime::now_utc().format("%a, %d %b %Y %H:%M:%S GMT")
)
.unwrap();
}

View File

@ -63,7 +63,7 @@ impl CookieBuilder {
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .expires(time::OffsetDateTime::now())
/// .expires(time::OffsetDateTime::now_utc())
/// .finish();
///
/// assert!(c.expires().is_some());

View File

@ -221,7 +221,7 @@ impl CookieJar {
if self.original_cookies.contains(cookie.name()) {
cookie.set_value("");
cookie.set_max_age(Duration::zero());
cookie.set_expires(OffsetDateTime::now() - Duration::days(365));
cookie.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
self.delta_cookies.replace(DeltaCookie::removed(cookie));
} else {
self.delta_cookies.remove(cookie.name());

View File

@ -733,7 +733,7 @@ impl<'c> Cookie<'c> {
pub fn make_permanent(&mut self) {
let twenty_years = Duration::days(365 * 20);
self.set_max_age(twenty_years);
self.set_expires(OffsetDateTime::now() + twenty_years);
self.set_expires(OffsetDateTime::now_utc() + twenty_years);
}
fn fmt_parameters(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -67,8 +67,12 @@ impl fmt::Debug for Extensions {
}
}
#[test]
fn test_remove() {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove() {
let mut map = Extensions::new();
map.insert::<i8>(123);
@ -76,10 +80,10 @@ fn test_remove() {
map.remove::<i8>();
assert!(map.get::<i8>().is_none());
}
}
#[test]
fn test_clear() {
#[test]
fn test_clear() {
let mut map = Extensions::new();
map.insert::<i8>(8);
@ -98,10 +102,10 @@ fn test_clear() {
map.insert::<i8>(10);
assert_eq!(*map.get::<i8>().unwrap(), 10);
}
}
#[test]
fn test_integers() {
#[test]
fn test_integers() {
let mut map = Extensions::new();
map.insert::<i8>(8);
@ -124,10 +128,10 @@ fn test_integers() {
assert!(map.get::<u32>().is_some());
assert!(map.get::<u64>().is_some());
assert!(map.get::<u128>().is_some());
}
}
#[test]
fn test_composition() {
#[test]
fn test_composition() {
struct Magi<T>(pub T);
struct Madoka {
@ -151,10 +155,10 @@ fn test_composition() {
assert!(!map.get::<Magi<Madoka>>().unwrap().0.god);
assert_eq!(0, map.get::<Magi<Homura>>().unwrap().0.attempts);
assert_eq!(999, map.get::<Magi<Mami>>().unwrap().0.guns);
}
}
#[test]
fn test_extensions() {
#[test]
fn test_extensions() {
#[derive(Debug, PartialEq)]
struct MyType(i32);
@ -171,4 +175,5 @@ fn test_extensions() {
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
}
}

View File

@ -19,7 +19,7 @@ fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
// we consider the year as part of this century if it's within the next 50 years,
// otherwise we consider as part of the previous century.
let now = OffsetDateTime::now();
let now = OffsetDateTime::now_utc();
let century_start_year = (now.year() / 100) * 100;
let mut expanded_year = century_start_year + dt.year();

View File

@ -137,7 +137,7 @@ impl Encoder for Codec {
Parser::write_message(
dst,
&data[..],
OpCode::Binary,
OpCode::Text,
false,
!self.flags.contains(Flags::SERVER),
)
@ -151,7 +151,7 @@ impl Encoder for Codec {
Parser::write_message(
dst,
&data[..],
OpCode::Text,
OpCode::Binary,
false,
!self.flags.contains(Flags::SERVER),
)

View File

@ -10,11 +10,11 @@ use actix_service::boxed::{self, BoxServiceFactory};
use actix_service::{
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform,
};
use futures::future::{FutureExt, LocalBoxFuture};
use futures::future::FutureExt;
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::ServiceConfig;
use crate::data::{Data, DataFactory};
use crate::data::{Data, DataFactory, FnDataFactory};
use crate::dev::ResourceDef;
use crate::error::Error;
use crate::resource::Resource;
@ -25,8 +25,6 @@ use crate::service::{
};
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
type FnDataFactory =
Box<dyn Fn() -> LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>;
/// Application builder - structure that follows the builder pattern
/// for building application instances.
@ -476,13 +474,13 @@ where
mod tests {
use actix_service::Service;
use bytes::Bytes;
use futures::future::ok;
use futures::future::{ok, err};
use super::*;
use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::middleware::DefaultHeaders;
use crate::service::ServiceRequest;
use crate::test::{call_service, init_service, read_body, TestRequest};
use crate::test::{call_service, init_service, try_init_service, read_body, TestRequest};
use crate::{web, HttpRequest, HttpResponse};
#[actix_rt::test]
@ -551,6 +549,17 @@ mod tests {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[actix_rt::test]
async fn test_data_factory_errors() {
let srv =
try_init_service(App::new().data_factory(|| err::<u32, _>(())).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
assert!(srv.is_err());
}
#[actix_rt::test]
async fn test_extension() {
let mut srv = init_service(App::new().app_data(10usize).service(

View File

@ -9,10 +9,10 @@ use actix_http::{Extensions, Request, Response};
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
use actix_service::{fn_service, Service, ServiceFactory};
use futures::future::{ok, FutureExt, LocalBoxFuture};
use futures::future::{join_all, ok, FutureExt, LocalBoxFuture};
use crate::config::{AppConfig, AppService};
use crate::data::DataFactory;
use crate::data::{FnDataFactory, DataFactory};
use crate::error::Error;
use crate::guard::Guard;
use crate::request::{HttpRequest, HttpRequestPool};
@ -23,8 +23,6 @@ type Guards = Vec<Box<dyn Guard>>;
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
type BoxResponse = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
type FnDataFactory =
Box<dyn Fn() -> LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>;
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
/// It also executes data factories.
@ -109,12 +107,15 @@ where
let rmap = Rc::new(rmap);
rmap.finish(rmap.clone());
// start all data factory futures
let factory_futs = join_all(self.data_factories.iter().map(|f| f()));
AppInitResult {
endpoint: None,
endpoint_fut: self.endpoint.new_service(()),
data: self.data.clone(),
data_factories: Vec::new(),
data_factories_fut: self.data_factories.iter().map(|f| f()).collect(),
data_factories: None,
data_factories_fut: factory_futs.boxed_local(),
extensions: Some(
self.extensions
.borrow_mut()
@ -133,15 +134,21 @@ pub struct AppInitResult<T, B>
where
T: ServiceFactory,
{
endpoint: Option<T::Service>,
#[pin]
endpoint_fut: T::Future,
// a Some signals completion of endpoint creation
endpoint: Option<T::Service>,
#[pin]
data_factories_fut: LocalBoxFuture<'static, Vec<Result<Box<dyn DataFactory>, ()>>>,
// a Some signals completion of factory futures
data_factories: Option<Vec<Box<dyn DataFactory>>>,
rmap: Rc<ResourceMap>,
config: AppConfig,
data: Rc<Vec<Box<dyn DataFactory>>>,
data_factories: Vec<Box<dyn DataFactory>>,
data_factories_fut: Vec<LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>,
extensions: Option<Extensions>,
_t: PhantomData<B>,
}
@ -161,44 +168,46 @@ where
let this = self.project();
// async data factories
let mut idx = 0;
while idx < this.data_factories_fut.len() {
match Pin::new(&mut this.data_factories_fut[idx]).poll(cx)? {
Poll::Ready(f) => {
this.data_factories.push(f);
let _ = this.data_factories_fut.remove(idx);
}
Poll::Pending => idx += 1,
if let Poll::Ready(factories) = this.data_factories_fut.poll(cx) {
let factories: Result<Vec<_>, ()> = factories.into_iter().collect();
if let Ok(factories) = factories {
this.data_factories.replace(factories);
} else {
return Poll::Ready(Err(()));
}
}
// app service and middleware
if this.endpoint.is_none() {
if let Poll::Ready(srv) = this.endpoint_fut.poll(cx)? {
*this.endpoint = Some(srv);
}
}
if this.endpoint.is_some() && this.data_factories_fut.is_empty() {
// not using if let so condition only needs shared ref
if this.endpoint.is_some() && this.data_factories.is_some() {
// create app data container
let mut data = this.extensions.take().unwrap();
for f in this.data.iter() {
f.create(&mut data);
}
for f in this.data_factories.iter() {
for f in this.data_factories.take().unwrap().iter() {
f.create(&mut data);
}
Poll::Ready(Ok(AppInitService {
return Poll::Ready(Ok(AppInitService {
service: this.endpoint.take().unwrap(),
rmap: this.rmap.clone(),
config: this.config.clone(),
data: Rc::new(data),
pool: HttpRequestPool::create(),
}))
} else {
Poll::Pending
}));
}
Poll::Pending
}
}

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use actix_http::error::{Error, ErrorInternalServerError};
use actix_http::Extensions;
use futures::future::{err, ok, Ready};
use futures::future::{err, ok, LocalBoxFuture, Ready};
use crate::dev::Payload;
use crate::extract::FromRequest;
@ -14,6 +14,9 @@ pub(crate) trait DataFactory {
fn create(&self, extensions: &mut Extensions) -> bool;
}
pub(crate) type FnDataFactory =
Box<dyn Fn() -> LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>;
/// Application data.
///
/// Application data is an arbitrary data attached to the app.

View File

@ -163,11 +163,11 @@ where
LoggerResponse {
fut: self.service.call(req),
format: None,
time: OffsetDateTime::now(),
time: OffsetDateTime::now_utc(),
_t: PhantomData,
}
} else {
let now = OffsetDateTime::now();
let now = OffsetDateTime::now_utc();
let mut format = self.inner.format.clone();
for unit in &mut format.0 {
@ -380,12 +380,12 @@ impl FormatText {
FormatText::Percent => "%".fmt(fmt),
FormatText::ResponseSize => size.fmt(fmt),
FormatText::Time => {
let rt = OffsetDateTime::now() - entry_time;
let rt = OffsetDateTime::now_utc() - entry_time;
let rt = rt.as_seconds_f64();
fmt.write_fmt(format_args!("{:.6}", rt))
}
FormatText::TimeMillis => {
let rt = OffsetDateTime::now() - entry_time;
let rt = OffsetDateTime::now_utc() - entry_time;
let rt = (rt.whole_nanoseconds() as f64) / 1_000_000.0;
fmt.write_fmt(format_args!("{:.6}", rt))
}
@ -520,7 +520,7 @@ mod tests {
.uri("/test/route/yeah")
.to_srv_request();
let now = OffsetDateTime::now();
let now = OffsetDateTime::now_utc();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
@ -551,7 +551,7 @@ mod tests {
)
.to_srv_request();
let now = OffsetDateTime::now();
let now = OffsetDateTime::now_utc();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
@ -561,7 +561,7 @@ mod tests {
unit.render_response(&resp);
}
let entry_time = OffsetDateTime::now();
let entry_time = OffsetDateTime::now_utc();
let render = |fmt: &mut Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?;
@ -579,7 +579,7 @@ mod tests {
let mut format = Format::new("%t");
let req = TestRequest::default().to_srv_request();
let now = OffsetDateTime::now();
let now = OffsetDateTime::now_utc();
for unit in &mut format.0 {
unit.render_request(now, &req);
}

View File

@ -196,9 +196,11 @@ where
self.app_data(Data::new(data))
}
/// Set or override application data.
/// Add resource data.
///
/// This method overrides data stored with [`App::app_data()`](#method.app_data)
/// If used, this method will create a new data context used for extracting
/// from requests. Data added here is *not* merged with data added on App
/// or containing scopes.
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
@ -393,6 +395,7 @@ where
if let Some(ref mut ext) = self.data {
config.set_service_data(ext);
}
config.register_service(rdef, guards, self, None)
}
}
@ -587,13 +590,14 @@ mod tests {
use actix_rt::time::delay_for;
use actix_service::Service;
use bytes::Bytes;
use futures::future::ok;
use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::middleware::DefaultHeaders;
use crate::service::ServiceRequest;
use crate::test::{call_service, init_service, TestRequest};
use crate::{guard, web, App, Error, HttpResponse};
use crate::test::{call_service, init_service, read_body, TestRequest};
use crate::{guard, web, App, Error, HttpRequest, HttpResponse};
#[actix_rt::test]
async fn test_middleware() {
@ -619,6 +623,79 @@ mod tests {
);
}
#[actix_rt::test]
async fn test_overwritten_data() {
#[allow(dead_code)]
fn echo_usize(req: HttpRequest) -> HttpResponse {
let num = req.app_data::<usize>().unwrap();
HttpResponse::Ok().body(format!("{}", num))
}
#[allow(dead_code)]
fn echo_u32(req: HttpRequest) -> HttpResponse {
let num = req.app_data::<u32>().unwrap();
HttpResponse::Ok().body(format!("{}", num))
}
#[allow(dead_code)]
fn echo_both(req: HttpRequest) -> HttpResponse {
let num = req.app_data::<usize>().unwrap();
let num2 = req.app_data::<u32>().unwrap();
HttpResponse::Ok().body(format!("{}-{}", num, num2))
}
let mut srv = init_service(
App::new()
.app_data(88usize)
.service(web::resource("/").route(web::get().to(echo_usize)))
.service(
web::resource("/one")
.app_data(1usize)
.route(web::get().to(echo_usize)),
)
.service(
web::resource("/two")
.app_data(2usize)
.route(web::get().to(echo_usize)),
)
.service(
web::resource("/three")
.app_data(3u32)
// this doesnt work because app_data "overrides" the
// entire data field potentially passed down
// .route(web::get().to(echo_both)),
.route(web::get().to(echo_u32)),
)
.service(web::resource("/eight").route(web::get().to(echo_usize))),
)
.await;
let req = TestRequest::get().uri("/").to_request();
let resp = srv.call(req).await.unwrap();
let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"88"));
let req = TestRequest::get().uri("/one").to_request();
let resp = srv.call(req).await.unwrap();
let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"1"));
let req = TestRequest::get().uri("/two").to_request();
let resp = srv.call(req).await.unwrap();
let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"2"));
// let req = TestRequest::get().uri("/three").to_request();
// let resp = srv.call(req).await.unwrap();
// let body = read_body(resp).await;
// assert_eq!(body, Bytes::from_static(b"88-3"));
let req = TestRequest::get().uri("/eight").to_request();
let resp = srv.call(req).await.unwrap();
let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"88"));
}
#[actix_rt::test]
async fn test_middleware_fn() {
let mut srv = init_service(

View File

@ -151,9 +151,11 @@ where
self.app_data(Data::new(data))
}
/// Set or override application data.
/// Add scope data.
///
/// This method overrides data stored with [`App::app_data()`](#method.app_data)
/// If used, this method will create a new data context used for extracting
/// from requests. Data added here is *not* merged with data added on App
/// or containing scopes.
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());

View File

@ -78,6 +78,26 @@ pub fn default_service(
pub async fn init_service<R, S, B, E>(
app: R,
) -> impl Service<Request = Request, Response = ServiceResponse<B>, Error = E>
where
R: IntoServiceFactory<S>,
S: ServiceFactory<
Config = AppConfig,
Request = Request,
Response = ServiceResponse<B>,
Error = E,
>,
S::InitError: std::fmt::Debug,
{
try_init_service(app).await.expect("service initilization failed")
}
/// Fallible version of init_service that allows testing data factory errors.
pub(crate) async fn try_init_service<R, S, B, E>(
app: R,
) -> Result<
impl Service<Request = Request, Response = ServiceResponse<B>, Error = E>,
S::InitError,
>
where
R: IntoServiceFactory<S>,
S: ServiceFactory<
@ -89,7 +109,7 @@ where
S::InitError: std::fmt::Debug,
{
let srv = app.into_factory();
srv.new_service(AppConfig::default()).await.unwrap()
srv.new_service(AppConfig::default()).await
}
/// Calls service and waits for response future completion.
@ -580,7 +600,7 @@ impl TestRequest {
pub async fn send_request<S, B, E>(self, app: &mut S) -> S::Response
where
S: Service<Request = Request, Response = ServiceResponse<B>, Error = E>,
E: std::fmt::Debug
E: std::fmt::Debug,
{
let req = self.to_request();
call_service(app, req).await
@ -1125,8 +1145,8 @@ mod tests {
#[actix_rt::test]
async fn test_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
async { HttpResponse::Ok().json(person.into_inner()) }
web::post().to(|person: web::Json<Person>| async {
HttpResponse::Ok().json(person.into_inner())
}),
)))
.await;
@ -1146,8 +1166,8 @@ mod tests {
#[actix_rt::test]
async fn test_body_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
async { HttpResponse::Ok().json(person.into_inner()) }
web::post().to(|person: web::Json<Person>| async {
HttpResponse::Ok().json(person.into_inner())
}),
)))
.await;
@ -1168,8 +1188,8 @@ mod tests {
#[actix_rt::test]
async fn test_request_response_form() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Form<Person>| {
async { HttpResponse::Ok().json(person.into_inner()) }
web::post().to(|person: web::Form<Person>| async {
HttpResponse::Ok().json(person.into_inner())
}),
)))
.await;
@ -1194,8 +1214,8 @@ mod tests {
#[actix_rt::test]
async fn test_request_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
async { HttpResponse::Ok().json(person.into_inner()) }
web::post().to(|person: web::Json<Person>| async {
HttpResponse::Ok().json(person.into_inner())
}),
)))
.await;
@ -1259,7 +1279,7 @@ mod tests {
assert!(res.status().is_success());
}
/*
/*
Comment out until actix decoupled of actix-http:
https://github.com/actix/actix/issues/321
@ -1307,5 +1327,5 @@ mod tests {
let res = app.call(req).await.unwrap();
assert!(res.status().is_success());
}
*/
*/
}