1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-23 23:51:06 +01:00

propogate app config with http request; add tests for url_for

This commit is contained in:
Nikolay Kim 2019-03-09 14:06:24 -08:00
parent c0ce7f0bae
commit 54678308d0
18 changed files with 397 additions and 215 deletions

View File

@ -70,9 +70,10 @@ actix-service = { git = "https://github.com/actix/actix-net.git" }
actix-utils = { git = "https://github.com/actix/actix-net.git" }
actix-http = { git = "https://github.com/actix/actix-http.git" }
actix-router = { git = "https://github.com/actix/actix-net.git" }
#actix-router = { git = "https://github.com/actix/actix-net.git" }
actix-server = { git = "https://github.com/actix/actix-net.git" }
actix-server-config = { git = "https://github.com/actix/actix-net.git" }
actix-router = { path = "../actix-net/router" }
bytes = "0.4"
derive_more = "0.14"

View File

@ -17,7 +17,7 @@ use v_htmlescape::escape as escape_html_entity;
use actix_http::error::{Error, ErrorInternalServerError};
use actix_service::{boxed::BoxedNewService, NewService, Service};
use actix_web::dev::{self, AppConfig, HttpServiceFactory, ResourceDef, Url};
use actix_web::dev::{HttpServiceFactory, ResourceDef, ServiceConfig};
use actix_web::{
blocking, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest,
ServiceRequest, ServiceResponse,
@ -305,7 +305,7 @@ where
P: 'static,
C: StaticFileConfig + 'static,
{
fn register(self, config: &mut AppConfig<P>) {
fn register(self, config: &mut ServiceConfig<P>) {
if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service());
}
@ -314,11 +314,12 @@ where
} else {
ResourceDef::prefix(&self.path)
};
config.register_service(rdef, None, self)
config.register_service(rdef, None, self, None)
}
}
impl<P, C: StaticFileConfig + 'static> NewService<ServiceRequest<P>> for Files<P, C> {
impl<P, C: StaticFileConfig + 'static> NewService for Files<P, C> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = ();
type Service = FilesService<P, C>;
@ -350,7 +351,8 @@ pub struct FilesService<S, C = DefaultConfig> {
_cd_map: PhantomData<C>,
}
impl<P, C: StaticFileConfig> Service<ServiceRequest<P>> for FilesService<P, C> {
impl<P, C: StaticFileConfig> Service for FilesService<P, C> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = ();
type Future = FutureResult<Self::Response, Self::Error>;
@ -362,7 +364,7 @@ impl<P, C: StaticFileConfig> Service<ServiceRequest<P>> for FilesService<P, C> {
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let (req, _) = req.into_parts();
let real_path = match PathBufWrp::get_pathbuf(req.match_info()) {
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
Ok(item) => item,
Err(e) => return ok(ServiceResponse::from_err(e, req.clone())),
};
@ -409,13 +411,13 @@ impl<P, C: StaticFileConfig> Service<ServiceRequest<P>> for FilesService<P, C> {
}
}
#[derive(Debug)]
struct PathBufWrp(PathBuf);
impl PathBufWrp {
fn get_pathbuf(path: &dev::Path<Url>) -> Result<Self, UriSegmentError> {
let path_str = path.path();
fn get_pathbuf(path: &str) -> Result<Self, UriSegmentError> {
let mut buf = PathBuf::new();
for segment in path_str.split('/') {
for segment in path.split('/') {
if segment == ".." {
buf.pop();
} else if segment.starts_with('.') {
@ -447,13 +449,14 @@ impl<P> FromRequest<P> for PathBufWrp {
type Config = ();
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
PathBufWrp::get_pathbuf(req.match_info())
PathBufWrp::get_pathbuf(req.match_info().path())
}
}
#[cfg(test)]
mod tests {
use std::fs;
use std::iter::FromIterator;
use std::ops::Add;
use std::time::{Duration, SystemTime};
@ -1093,32 +1096,32 @@ mod tests {
#[test]
fn test_path_buf() {
assert_eq!(
PathBuf::from_param("/test/.tt"),
PathBufWrp::get_pathbuf("/test/.tt").map(|t| t.0),
Err(UriSegmentError::BadStart('.'))
);
assert_eq!(
PathBuf::from_param("/test/*tt"),
PathBufWrp::get_pathbuf("/test/*tt").map(|t| t.0),
Err(UriSegmentError::BadStart('*'))
);
assert_eq!(
PathBuf::from_param("/test/tt:"),
PathBufWrp::get_pathbuf("/test/tt:").map(|t| t.0),
Err(UriSegmentError::BadEnd(':'))
);
assert_eq!(
PathBuf::from_param("/test/tt<"),
PathBufWrp::get_pathbuf("/test/tt<").map(|t| t.0),
Err(UriSegmentError::BadEnd('<'))
);
assert_eq!(
PathBuf::from_param("/test/tt>"),
PathBufWrp::get_pathbuf("/test/tt>").map(|t| t.0),
Err(UriSegmentError::BadEnd('>'))
);
assert_eq!(
PathBuf::from_param("/seg1/seg2/"),
Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))
PathBufWrp::get_pathbuf("/seg1/seg2/").unwrap().0,
PathBuf::from_iter(vec!["seg1", "seg2"])
);
assert_eq!(
PathBuf::from_param("/seg1/../seg2/"),
Ok(PathBuf::from_iter(vec!["seg2"]))
PathBufWrp::get_pathbuf("/seg1/../seg2/").unwrap().0,
PathBuf::from_iter(vec!["seg2"])
);
}
}

View File

@ -304,8 +304,6 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
type Future = Result<HttpResponse, Error>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
println!("RESP: {:?}", req);
if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code);
resp.set(header::ContentType(self.content_type.clone()))

View File

@ -255,12 +255,13 @@ impl CookieSession {
}
}
impl<S, P, B: 'static> Transform<S, ServiceRequest<P>> for CookieSession
impl<S, P, B: 'static> Transform<S> for CookieSession
where
S: Service<ServiceRequest<P>, Response = ServiceResponse<B>>,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
@ -281,12 +282,13 @@ pub struct CookieSessionMiddleware<S> {
inner: Rc<CookieSessionInner>,
}
impl<S, P, B: 'static> Service<ServiceRequest<P>> for CookieSessionMiddleware<S>
impl<S, P, B: 'static> Service for CookieSessionMiddleware<S>
where
S: Service<ServiceRequest<P>, Response = ServiceResponse<B>>,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

View File

@ -31,7 +31,7 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::AppConfig<P>) {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
@ -68,7 +68,7 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::AppConfig<P>) {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
@ -105,7 +105,7 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::AppConfig<P>) {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)

View File

@ -3,7 +3,8 @@ use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::body::{Body, MessageBody};
use actix_http::{Extensions, PayloadStream};
use actix_http::PayloadStream;
use actix_router::ResourceDef;
use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService};
use actix_service::{
@ -12,6 +13,7 @@ use actix_service::{
use futures::IntoFuture;
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner};
use crate::resource::Resource;
use crate::route::Route;
use crate::service::{
@ -29,9 +31,8 @@ where
T: NewService<Request = ServiceRequest, Response = ServiceRequest<P>>,
{
chain: T,
extensions: Extensions,
state: Vec<Box<StateFactory>>,
host: String,
config: AppConfigInner,
_t: PhantomData<(P,)>,
}
@ -41,9 +42,8 @@ impl App<PayloadStream, AppChain> {
pub fn new() -> Self {
App {
chain: AppChain,
extensions: Extensions::new(),
state: Vec::new(),
host: "localhost:8080".to_string(),
config: AppConfigInner::default(),
_t: PhantomData,
}
}
@ -141,8 +141,8 @@ where
services: Vec::new(),
default: None,
factory_ref: fref,
extensions: self.extensions,
host: self.host,
config: self.config,
external: Vec::new(),
_t: PhantomData,
}
}
@ -174,8 +174,7 @@ where
App {
chain,
state: self.state,
extensions: self.extensions,
host: self.host,
config: self.config,
_t: PhantomData,
}
}
@ -223,10 +222,10 @@ where
default: None,
endpoint: AppEntry::new(fref.clone()),
factory_ref: fref,
extensions: self.extensions,
state: self.state,
host: self.host,
config: self.config,
services: vec![Box::new(ServiceFactoryWrapper::new(service))],
external: Vec::new(),
_t: PhantomData,
}
}
@ -239,7 +238,7 @@ where
///
/// By default host name is set to a "localhost" value.
pub fn hostname(mut self, val: &str) -> Self {
self.host = val.to_owned();
self.config.host = val.to_owned();
self
}
}
@ -252,9 +251,9 @@ pub struct AppRouter<C, P, B, T> {
services: Vec<Box<ServiceFactory<P>>>,
default: Option<Rc<HttpNewService<P>>>,
factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
extensions: Extensions,
state: Vec<Box<StateFactory>>,
host: String,
config: AppConfigInner,
external: Vec<ResourceDef>,
_t: PhantomData<(P, B)>,
}
@ -348,8 +347,8 @@ where
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
extensions: self.extensions,
host: self.host,
config: self.config,
external: self.external,
_t: PhantomData,
}
}
@ -382,33 +381,30 @@ where
/// and are never considered for matching at request time. Calls to
/// `HttpRequest::url_for()` will work as expected.
///
/// ```rust,ignore
/// # extern crate actix_web;
/// use actix_web::{App, HttpRequest, HttpResponse, Result};
/// ```rust
/// use actix_web::{web, App, HttpRequest, HttpResponse, Result};
///
/// fn index(req: &HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["asdlkjqme"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/asdlkjqme");
/// Ok(HttpResponse::Ok().into())
/// }
///
/// fn main() {
/// let app = App::new()
/// .resource("/index.html", |r| r.get().f(index))
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}")
/// .finish();
/// .service(web::resource("/index.html").route(
/// web::get().to(index)))
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}");
/// }
/// ```
pub fn external_resource<N, U>(self, _name: N, _url: U) -> Self
pub fn external_resource<N, U>(mut self, name: N, url: U) -> Self
where
N: AsRef<str>,
U: AsRef<str>,
{
// self.parts
// .as_mut()
// .expect("Use after finish")
// .router
// .register_external(name.as_ref(), ResourceDef::external(url.as_ref()));
let mut rdef = ResourceDef::new(url.as_ref());
*rdef.name_mut() = name.as_ref().to_string();
self.external.push(rdef);
self
}
}
@ -435,9 +431,10 @@ where
state: self.state,
endpoint: self.endpoint,
services: RefCell::new(self.services),
external: RefCell::new(self.external),
default: self.default,
factory_ref: self.factory_ref,
extensions: Rc::new(RefCell::new(Rc::new(self.extensions))),
config: RefCell::new(AppConfig(Rc::new(self.config))),
}
}
}

View File

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::{Extensions, Request, Response};
use actix_http::{Request, Response};
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService, BoxedService};
@ -10,7 +10,7 @@ use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll};
use crate::config::AppConfig;
use crate::config::{AppConfig, ServiceConfig};
use crate::guard::Guard;
use crate::rmap::ResourceMap;
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
@ -36,10 +36,11 @@ where
pub(crate) chain: C,
pub(crate) endpoint: T,
pub(crate) state: Vec<Box<StateFactory>>,
pub(crate) extensions: Rc<RefCell<Rc<Extensions>>>,
pub(crate) config: RefCell<AppConfig>,
pub(crate) services: RefCell<Vec<Box<ServiceFactory<P>>>>,
pub(crate) default: Option<Rc<HttpNewService<P>>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
pub(crate) external: RefCell<Vec<ResourceDef>>,
}
impl<C, T, P: 'static, B> NewService<ServerConfig> for AppInit<C, T, P, B>
@ -64,7 +65,7 @@ where
type Service = AndThen<AppInitService<C::Service, P>, T::Service>;
type Future = AppInitResult<C, T, P, B>;
fn new_service(&self, _: &ServerConfig) -> Self::Future {
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
// update resource default service
let default = self.default.clone().unwrap_or_else(|| {
Rc::new(boxed::new_service(fn_service(|req: ServiceRequest<P>| {
@ -72,12 +73,15 @@ where
})))
});
let mut config = AppConfig::new(
"127.0.0.1:8080".parse().unwrap(),
"localhost:8080".to_owned(),
false,
default.clone(),
);
{
let mut c = self.config.borrow_mut();
let loc_cfg = Rc::get_mut(&mut c.0).unwrap();
loc_cfg.secure = cfg.secure();
loc_cfg.addr = cfg.local_addr();
}
let mut config =
ServiceConfig::new(self.config.borrow().clone(), default.clone());
// register services
std::mem::replace(&mut *self.services.borrow_mut(), Vec::new())
@ -101,6 +105,11 @@ where
),
});
// external resources
for mut rdef in std::mem::replace(&mut *self.external.borrow_mut(), Vec::new()) {
rmap.add(&mut rdef, None);
}
// complete ResourceMap tree creation
let rmap = Rc::new(rmap);
rmap.finish(rmap.clone());
@ -111,7 +120,7 @@ where
endpoint: None,
endpoint_fut: self.endpoint.new_service(&()),
state: self.state.iter().map(|s| s.construct()).collect(),
extensions: self.extensions.clone(),
config: self.config.borrow().clone(),
rmap,
_t: PhantomData,
}
@ -129,7 +138,7 @@ where
endpoint_fut: T::Future,
rmap: Rc<ResourceMap>,
state: Vec<Box<StateFactoryResult>>,
extensions: Rc<RefCell<Rc<Extensions>>>,
config: AppConfig,
_t: PhantomData<(P, B)>,
}
@ -152,21 +161,15 @@ where
type Error = C::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) {
let mut idx = 0;
let mut extensions = self.config.0.extensions.borrow_mut();
while idx < self.state.len() {
if let Async::Ready(_) = self.state[idx].poll_result(extensions)? {
if let Async::Ready(_) = self.state[idx].poll_result(&mut extensions)? {
self.state.remove(idx);
} else {
idx += 1;
}
}
if !self.state.is_empty() {
return Ok(Async::NotReady);
}
} else {
log::warn!("Multiple copies of app extensions exists");
}
if self.chain.is_none() {
if let Async::Ready(srv) = self.chain_fut.poll()? {
@ -185,7 +188,7 @@ where
AppInitService {
chain: self.chain.take().unwrap(),
rmap: self.rmap.clone(),
extensions: self.extensions.borrow().clone(),
config: self.config.clone(),
}
.and_then(self.endpoint.take().unwrap()),
))
@ -202,7 +205,7 @@ where
{
chain: C,
rmap: Rc<ResourceMap>,
extensions: Rc<Extensions>,
config: AppConfig,
}
impl<C, P> Service for AppInitService<C, P>
@ -223,7 +226,7 @@ where
Path::new(Url::new(req.uri().clone())),
req,
self.rmap.clone(),
self.extensions.clone(),
self.config.clone(),
);
self.chain.call(req)
}

View File

@ -1,6 +1,8 @@
use std::cell::{Ref, RefCell};
use std::net::SocketAddr;
use std::rc::Rc;
use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService};
@ -13,10 +15,8 @@ type HttpNewService<P> =
boxed::BoxedNewService<(), ServiceRequest<P>, ServiceResponse, (), ()>;
/// Application configuration
pub struct AppConfig<P> {
addr: SocketAddr,
secure: bool,
host: String,
pub struct ServiceConfig<P> {
config: AppConfig,
root: bool,
default: Rc<HttpNewService<P>>,
services: Vec<(
@ -27,18 +27,11 @@ pub struct AppConfig<P> {
)>,
}
impl<P: 'static> AppConfig<P> {
impl<P: 'static> ServiceConfig<P> {
/// Crate server settings instance
pub(crate) fn new(
addr: SocketAddr,
host: String,
secure: bool,
default: Rc<HttpNewService<P>>,
) -> Self {
AppConfig {
addr,
secure,
host,
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService<P>>) -> Self {
ServiceConfig {
config,
default,
root: true,
services: Vec::new(),
@ -62,31 +55,20 @@ impl<P: 'static> AppConfig<P> {
}
pub(crate) fn clone_config(&self) -> Self {
AppConfig {
addr: self.addr,
secure: self.secure,
host: self.host.clone(),
ServiceConfig {
config: self.config.clone(),
default: self.default.clone(),
services: Vec::new(),
root: false,
}
}
/// Returns the socket address of the local half of this TCP connection
pub fn local_addr(&self) -> SocketAddr {
self.addr
}
/// Returns true if connection is secure(https)
pub fn secure(&self) -> bool {
self.secure
}
/// Returns host header value
pub fn host(&self) -> &str {
&self.host
/// Service configuration
pub fn config(&self) -> &AppConfig {
&self.config
}
/// Default resource
pub fn default_service(&self) -> Rc<HttpNewService<P>> {
self.default.clone()
}
@ -114,3 +96,63 @@ impl<P: 'static> AppConfig<P> {
));
}
}
#[derive(Clone)]
pub struct AppConfig(pub(crate) Rc<AppConfigInner>);
impl AppConfig {
pub(crate) fn new(inner: AppConfigInner) -> Self {
AppConfig(Rc::new(inner))
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
///
/// By default host name is set to a "localhost" value.
pub fn host(&self) -> &str {
&self.0.host
}
/// Returns true if connection is secure(https)
pub fn secure(&self) -> bool {
self.0.secure
}
/// Returns the socket address of the local half of this TCP connection
pub fn local_addr(&self) -> SocketAddr {
self.0.addr
}
/// Resource map
pub fn rmap(&self) -> &ResourceMap {
&self.0.rmap
}
/// Application extensions
pub fn extensions(&self) -> Ref<Extensions> {
self.0.extensions.borrow()
}
}
pub(crate) struct AppConfigInner {
pub(crate) secure: bool,
pub(crate) host: String,
pub(crate) addr: SocketAddr,
pub(crate) rmap: ResourceMap,
pub(crate) extensions: RefCell<Extensions>,
}
impl Default for AppConfigInner {
fn default() -> AppConfigInner {
AppConfigInner {
secure: false,
addr: "127.0.0.1:8080".parse().unwrap(),
host: "localhost:8080".to_owned(),
rmap: ResourceMap::new(ResourceDef::new("")),
extensions: RefCell::new(Extensions::new()),
}
}
}

View File

@ -1,3 +1,5 @@
//! Error and Result module
pub use actix_http::error::*;
use derive_more::{Display, From};
use url::ParseError as UrlParseError;

View File

@ -1,19 +1,14 @@
use std::cell::Ref;
use actix_http::http::header::{self, HeaderName};
use actix_http::RequestHead;
use crate::dev::{AppConfig, RequestHead};
use crate::http::header::{self, HeaderName};
const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for";
const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host";
const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto";
pub enum ConnectionInfoError {
UnknownHost,
UnknownScheme,
}
/// `HttpRequest` connection information
#[derive(Clone, Default)]
#[derive(Debug, Clone, Default)]
pub struct ConnectionInfo {
scheme: String,
host: String,
@ -23,19 +18,19 @@ pub struct ConnectionInfo {
impl ConnectionInfo {
/// Create *ConnectionInfo* instance for a request.
pub fn get(req: &RequestHead) -> Ref<Self> {
pub fn get<'a>(req: &'a RequestHead, cfg: &AppConfig) -> Ref<'a, Self> {
if !req.extensions().contains::<ConnectionInfo>() {
req.extensions_mut().insert(ConnectionInfo::new(req));
req.extensions_mut().insert(ConnectionInfo::new(req, cfg));
}
Ref::map(req.extensions(), |e| e.get().unwrap())
}
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
fn new(req: &RequestHead) -> ConnectionInfo {
fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
let mut host = None;
let mut scheme = None;
let mut remote = None;
let mut peer = None;
let peer = None;
// load forwarded header
for hdr in req.headers.get_all(header::FORWARDED) {
@ -82,7 +77,7 @@ impl ConnectionInfo {
}
if scheme.is_none() {
scheme = req.uri.scheme_part().map(|a| a.as_str());
if scheme.is_none() && req.server_settings().secure() {
if scheme.is_none() && cfg.secure() {
scheme = Some("https")
}
}
@ -105,7 +100,7 @@ impl ConnectionInfo {
if host.is_none() {
host = req.uri.authority_part().map(|a| a.as_str());
if host.is_none() {
host = Some(req.server_settings().host());
host = Some(cfg.host());
}
}
}
@ -121,10 +116,10 @@ impl ConnectionInfo {
remote = h.split(',').next().map(|v| v.trim());
}
}
if remote.is_none() {
// if remote.is_none() {
// get peeraddr from socketaddr
peer = req.peer_addr().map(|addr| format!("{}", addr));
}
// peer = req.peer_addr().map(|addr| format!("{}", addr));
// }
}
ConnectionInfo {
@ -186,9 +181,8 @@ mod tests {
#[test]
fn test_forwarded() {
let req = TestRequest::default().request();
let mut info = ConnectionInfo::default();
info.update(&req);
let req = TestRequest::default().to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "localhost:8080");
@ -197,44 +191,39 @@ mod tests {
header::FORWARDED,
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
)
.request();
.to_http_request();
let mut info = ConnectionInfo::default();
info.update(&req);
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(header::HOST, "rust-lang.org")
.request();
.to_http_request();
let mut info = ConnectionInfo::default();
info.update(&req);
let info = req.connection_info();
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), None);
let req = TestRequest::default()
.header(X_FORWARDED_FOR, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
.to_http_request();
let info = req.connection_info();
assert_eq!(info.remote(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(X_FORWARDED_HOST, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
.to_http_request();
let info = req.connection_info();
assert_eq!(info.host(), "192.0.2.60");
assert_eq!(info.remote(), None);
let req = TestRequest::default()
.header(X_FORWARDED_PROTO, "https")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
}
}

View File

@ -2,13 +2,13 @@
mod app;
mod app_service;
mod extract;
mod handler;
// mod info;
pub mod blocking;
mod config;
pub mod error;
mod extract;
pub mod guard;
mod handler;
mod info;
pub mod middleware;
mod request;
mod resource;
@ -54,7 +54,9 @@ pub mod dev {
//! ```
pub use crate::app::AppRouter;
pub use crate::config::AppConfig;
pub use crate::config::{AppConfig, ServiceConfig};
pub use crate::info::ConnectionInfo;
pub use crate::rmap::ResourceMap;
pub use crate::service::HttpServiceFactory;
pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody};
@ -62,7 +64,7 @@ pub mod dev {
pub use actix_http::{
Extensions, Payload, PayloadStream, RequestHead, ResponseHead,
};
pub use actix_router::{Path, ResourceDef, Url};
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
pub(crate) fn insert_slash(path: &str) -> String {
let mut path = path.to_owned();

View File

@ -7,8 +7,10 @@ use actix_http::http::{HeaderMap, Method, Uri, Version};
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
use actix_router::{Path, Url};
use crate::config::AppConfig;
use crate::error::UrlGenerationError;
use crate::extract::FromRequest;
use crate::info::ConnectionInfo;
use crate::rmap::ResourceMap;
use crate::service::ServiceFromRequest;
@ -18,7 +20,7 @@ pub struct HttpRequest {
pub(crate) head: Message<RequestHead>,
pub(crate) path: Path<Url>,
rmap: Rc<ResourceMap>,
extensions: Rc<Extensions>,
config: AppConfig,
}
impl HttpRequest {
@ -27,13 +29,13 @@ impl HttpRequest {
head: Message<RequestHead>,
path: Path<Url>,
rmap: Rc<ResourceMap>,
extensions: Rc<Extensions>,
config: AppConfig,
) -> HttpRequest {
HttpRequest {
head,
path,
rmap,
extensions,
config,
}
}
}
@ -92,17 +94,17 @@ impl HttpRequest {
&self.path
}
/// Application extensions
/// App config
#[inline]
pub fn app_extensions(&self) -> &Extensions {
&self.extensions
pub fn config(&self) -> &AppConfig {
&self.config
}
/// Generate url for named resource
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::{App, HttpRequest, HttpResponse, http};
/// # use actix_web::{web, App, HttpRequest, HttpResponse};
/// #
/// fn index(req: HttpRequest) -> HttpResponse {
/// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
@ -111,11 +113,10 @@ impl HttpRequest {
///
/// fn main() {
/// let app = App::new()
/// .resource("/test/{one}/{two}/{three}", |r| {
/// r.name("foo"); // <- set resource name, then it could be used in `url_for`
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
/// .finish();
/// .service(web::resource("/test/{one}/{two}/{three}")
/// .name("foo") // <- set resource name, then it could be used in `url_for`
/// .route(web::get().to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
pub fn url_for<U, I>(
@ -139,11 +140,11 @@ impl HttpRequest {
self.url_for(name, &NO_PARAMS)
}
// /// Get *ConnectionInfo* for the correct request.
// #[inline]
// pub fn connection_info(&self) -> Ref<ConnectionInfo> {
// ConnectionInfo::get(&*self)
// }
/// Get *ConnectionInfo* for the current request.
#[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
ConnectionInfo::get(&*self, &*self.config())
}
}
impl Deref for HttpRequest {
@ -234,3 +235,124 @@ impl fmt::Debug for HttpRequest {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dev::{ResourceDef, ResourceMap};
use crate::http::header;
use crate::test::TestRequest;
#[test]
fn test_debug() {
let req =
TestRequest::with_header("content-type", "text/plain").to_http_request();
let dbg = format!("{:?}", req);
assert!(dbg.contains("HttpRequest"));
}
#[test]
fn test_no_request_cookies() {
let req = TestRequest::default().to_http_request();
assert!(req.cookies().unwrap().is_empty());
}
#[test]
fn test_request_cookies() {
let req = TestRequest::default()
.header(header::COOKIE, "cookie1=value1")
.header(header::COOKIE, "cookie2=value2")
.to_http_request();
{
let cookies = req.cookies().unwrap();
assert_eq!(cookies.len(), 2);
assert_eq!(cookies[0].name(), "cookie1");
assert_eq!(cookies[0].value(), "value1");
assert_eq!(cookies[1].name(), "cookie2");
assert_eq!(cookies[1].value(), "value2");
}
let cookie = req.cookie("cookie1");
assert!(cookie.is_some());
let cookie = cookie.unwrap();
assert_eq!(cookie.name(), "cookie1");
assert_eq!(cookie.value(), "value1");
let cookie = req.cookie("cookie-unknown");
assert!(cookie.is_none());
}
#[test]
fn test_request_query() {
let req = TestRequest::with_uri("/?id=test").to_http_request();
assert_eq!(req.query_string(), "id=test");
}
#[test]
fn test_url_for() {
let mut res = ResourceDef::new("/user/{name}.{ext}");
*res.name_mut() = "index".to_string();
let mut rmap = ResourceMap::new(ResourceDef::new(""));
rmap.add(&mut res, None);
assert!(rmap.has_resource("/user/test.html"));
assert!(!rmap.has_resource("/test/unknown"));
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
.rmap(rmap)
.to_http_request();
assert_eq!(
req.url_for("unknown", &["test"]),
Err(UrlGenerationError::ResourceNotFound)
);
assert_eq!(
req.url_for("index", &["test"]),
Err(UrlGenerationError::NotEnoughElements)
);
let url = req.url_for("index", &["test", "html"]);
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/user/test.html"
);
}
#[test]
fn test_url_for_static() {
let mut rdef = ResourceDef::new("/index.html");
*rdef.name_mut() = "index".to_string();
let mut rmap = ResourceMap::new(ResourceDef::new(""));
rmap.add(&mut rdef, None);
assert!(rmap.has_resource("/index.html"));
let req = TestRequest::with_uri("/test")
.header(header::HOST, "www.rust-lang.org")
.rmap(rmap)
.to_http_request();
let url = req.url_for_static("index");
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/index.html"
);
}
#[test]
fn test_url_for_external() {
let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}");
*rdef.name_mut() = "youtube".to_string();
let mut rmap = ResourceMap::new(ResourceDef::new(""));
rmap.add(&mut rdef, None);
assert!(rmap.has_resource("https://youtube.com/watch/unknown"));
let req = TestRequest::default().rmap(rmap).to_http_request();
let url = req.url_for("youtube", &["oHg5SJYRHA0"]);
assert_eq!(
url.ok().unwrap().as_str(),
"https://youtube.com/watch/oHg5SJYRHA0"
);
}
}

View File

@ -9,7 +9,7 @@ use actix_service::{
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
use crate::dev::{insert_slash, AppConfig, HttpServiceFactory, ResourceDef};
use crate::dev::{insert_slash, HttpServiceFactory, ResourceDef, ServiceConfig};
use crate::extract::FromRequest;
use crate::guard::Guard;
use crate::handler::{AsyncFactory, Factory};
@ -41,6 +41,7 @@ type HttpNewService<P> = BoxedNewService<(), ServiceRequest<P>, ServiceResponse,
pub struct Resource<P, T = ResourceEndpoint<P>> {
endpoint: T,
rdef: String,
name: Option<String>,
routes: Vec<Route<P>>,
guards: Vec<Box<Guard>>,
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>,
@ -54,6 +55,7 @@ impl<P> Resource<P> {
Resource {
routes: Vec::new(),
rdef: path.to_string(),
name: None,
endpoint: ResourceEndpoint::new(fref.clone()),
factory_ref: fref,
guards: Vec::new(),
@ -72,6 +74,14 @@ where
InitError = (),
>,
{
/// Set resource name.
///
/// Name is used for url generation.
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
/// Add match guard to a resource.
///
/// ```rust
@ -240,6 +250,7 @@ where
Resource {
endpoint,
rdef: self.rdef,
name: self.name,
guards: self.guards,
routes: self.routes,
default: self.default,
@ -277,7 +288,7 @@ where
InitError = (),
> + 'static,
{
fn register(mut self, config: &mut AppConfig<P>) {
fn register(mut self, config: &mut ServiceConfig<P>) {
if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service());
}
@ -286,11 +297,14 @@ where
} else {
Some(std::mem::replace(&mut self.guards, Vec::new()))
};
let rdef = if config.is_root() || !self.rdef.is_empty() {
let mut rdef = if config.is_root() || !self.rdef.is_empty() {
ResourceDef::new(&insert_slash(&self.rdef))
} else {
ResourceDef::new(&self.rdef)
};
if let Some(ref name) = self.name {
*rdef.name_mut() = name.clone();
}
config.register_service(rdef, guards, self, None)
}
}

View File

@ -64,14 +64,13 @@ impl ResourceMap {
if self.patterns_for(name, &mut path, &mut elements)?.is_some() {
if path.starts_with('/') {
// let conn = req.connection_info();
// Ok(Url::parse(&format!(
// "{}://{}{}",
// conn.scheme(),
// conn.host(),
// path
// ))?)
unimplemented!()
let conn = req.connection_info();
Ok(Url::parse(&format!(
"{}://{}{}",
conn.scheme(),
conn.host(),
path
))?)
} else {
Ok(Url::parse(&path)?)
}

View File

@ -10,7 +10,7 @@ use actix_service::{
use futures::future::{ok, Either, Future, FutureResult};
use futures::{Async, Poll};
use crate::dev::{AppConfig, HttpServiceFactory};
use crate::dev::{HttpServiceFactory, ServiceConfig};
use crate::guard::Guard;
use crate::resource::Resource;
use crate::rmap::ResourceMap;
@ -237,7 +237,7 @@ where
InitError = (),
> + 'static,
{
fn register(self, config: &mut AppConfig<P>) {
fn register(self, config: &mut ServiceConfig<P>) {
// update default resource if needed
if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service());

View File

@ -13,16 +13,16 @@ use actix_http::{
use actix_router::{Path, Resource, Url};
use futures::future::{ok, FutureResult, IntoFuture};
use crate::config::AppConfig;
use crate::config::{AppConfig, ServiceConfig};
use crate::request::HttpRequest;
use crate::rmap::ResourceMap;
pub trait HttpServiceFactory<P> {
fn register(self, config: &mut AppConfig<P>);
fn register(self, config: &mut ServiceConfig<P>);
}
pub(crate) trait ServiceFactory<P> {
fn register(&mut self, config: &mut AppConfig<P>);
fn register(&mut self, config: &mut ServiceConfig<P>);
}
pub(crate) struct ServiceFactoryWrapper<T, P> {
@ -43,7 +43,7 @@ impl<T, P> ServiceFactory<P> for ServiceFactoryWrapper<T, P>
where
T: HttpServiceFactory<P>,
{
fn register(&mut self, config: &mut AppConfig<P>) {
fn register(&mut self, config: &mut ServiceConfig<P>) {
if let Some(item) = self.factory.take() {
item.register(config)
}
@ -60,12 +60,12 @@ impl<P> ServiceRequest<P> {
path: Path<Url>,
request: Request<P>,
rmap: Rc<ResourceMap>,
extensions: Rc<Extensions>,
config: AppConfig,
) -> Self {
let (head, payload) = request.into_parts();
ServiceRequest {
payload,
req: HttpRequest::new(head, path, rmap, extensions),
req: HttpRequest::new(head, path, rmap, config),
}
}
@ -156,10 +156,10 @@ impl<P> ServiceRequest<P> {
&mut self.req.path
}
/// Application extensions
/// Service configuration
#[inline]
pub fn app_extensions(&self) -> &Extensions {
self.req.app_extensions()
pub fn app_config(&self) -> &AppConfig {
self.req.config()
}
/// Deconstruct request into parts

View File

@ -52,7 +52,7 @@ impl<T: 'static, P> FromRequest<P> for State<T> {
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
if let Some(st) = req.app_extensions().get::<State<T>>() {
if let Some(st) = req.config().extensions().get::<State<T>>() {
Ok(st.clone())
} else {
Err(ErrorInternalServerError(

View File

@ -5,7 +5,7 @@ use std::rc::Rc;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, Version};
use actix_http::test::TestRequest as HttpTestRequest;
use actix_http::{Extensions, PayloadStream, Request};
use actix_http::{PayloadStream, Request};
use actix_router::{Path, ResourceDef, Url};
use actix_rt::Runtime;
use actix_server_config::ServerConfig;
@ -13,6 +13,7 @@ use actix_service::{IntoNewService, NewService, Service};
use bytes::Bytes;
use futures::Future;
use crate::config::{AppConfig, AppConfigInner};
use crate::request::HttpRequest;
use crate::rmap::ResourceMap;
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
@ -142,16 +143,16 @@ where
/// ```
pub struct TestRequest {
req: HttpTestRequest,
extensions: Extensions,
rmap: ResourceMap,
config: AppConfigInner,
}
impl Default for TestRequest {
fn default() -> TestRequest {
TestRequest {
req: HttpTestRequest::default(),
extensions: Extensions::new(),
rmap: ResourceMap::new(ResourceDef::new("")),
config: AppConfigInner::default(),
}
}
}
@ -161,8 +162,8 @@ impl TestRequest {
pub fn with_uri(path: &str) -> TestRequest {
TestRequest {
req: HttpTestRequest::default().uri(path).take(),
extensions: Extensions::new(),
rmap: ResourceMap::new(ResourceDef::new("")),
config: AppConfigInner::default(),
}
}
@ -170,7 +171,7 @@ impl TestRequest {
pub fn with_hdr<H: Header>(hdr: H) -> TestRequest {
TestRequest {
req: HttpTestRequest::default().set(hdr).take(),
extensions: Extensions::new(),
config: AppConfigInner::default(),
rmap: ResourceMap::new(ResourceDef::new("")),
}
}
@ -183,7 +184,7 @@ impl TestRequest {
{
TestRequest {
req: HttpTestRequest::default().header(key, value).take(),
extensions: Extensions::new(),
config: AppConfigInner::default(),
rmap: ResourceMap::new(ResourceDef::new("")),
}
}
@ -192,7 +193,7 @@ impl TestRequest {
pub fn get() -> TestRequest {
TestRequest {
req: HttpTestRequest::default().method(Method::GET).take(),
extensions: Extensions::new(),
config: AppConfigInner::default(),
rmap: ResourceMap::new(ResourceDef::new("")),
}
}
@ -201,7 +202,7 @@ impl TestRequest {
pub fn post() -> TestRequest {
TestRequest {
req: HttpTestRequest::default().method(Method::POST).take(),
extensions: Extensions::new(),
config: AppConfigInner::default(),
rmap: ResourceMap::new(ResourceDef::new("")),
}
}
@ -247,8 +248,15 @@ impl TestRequest {
}
/// Set request config
pub fn config<T: 'static>(mut self, data: T) -> Self {
self.extensions.insert(data);
pub fn config<T: 'static>(self, data: T) -> Self {
self.config.extensions.borrow_mut().insert(data);
self
}
#[cfg(test)]
/// Set request config
pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self {
self.rmap = rmap;
self
}
@ -260,7 +268,7 @@ impl TestRequest {
Path::new(Url::new(req.uri().clone())),
req,
Rc::new(self.rmap),
Rc::new(self.extensions),
AppConfig::new(self.config),
)
}
@ -277,7 +285,7 @@ impl TestRequest {
Path::new(Url::new(req.uri().clone())),
req,
Rc::new(self.rmap),
Rc::new(self.extensions),
AppConfig::new(self.config),
)
.into_request()
}
@ -290,7 +298,7 @@ impl TestRequest {
Path::new(Url::new(req.uri().clone())),
req,
Rc::new(self.rmap),
Rc::new(self.extensions),
AppConfig::new(self.config),
);
ServiceFromRequest::new(req, None)
}