1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

added HttpRequest::url_for

This commit is contained in:
Nikolay Kim 2017-12-06 16:26:27 -08:00
parent 8d52e2bbd9
commit 0dd27bd224
9 changed files with 179 additions and 88 deletions

View File

@ -1,59 +1,15 @@
use std::rc::Rc;
use std::collections::HashMap;
use error::UriGenerationError;
use handler::{Reply, RouteHandler};
use router::Router;
use resource::Resource;
use recognizer::{RouteRecognizer, check_pattern, PatternElement};
use recognizer::check_pattern;
use httprequest::HttpRequest;
use channel::{HttpHandler, IntoHttpHandler};
use pipeline::Pipeline;
use middlewares::Middleware;
pub struct Router<S>(Rc<RouteRecognizer<Resource<S>>>);
impl<S: 'static> Router<S> {
pub fn new(prefix: String, map: HashMap<String, Resource<S>>) -> Router<S>
{
let mut resources = Vec::new();
for (path, resource) in map {
resources.push((path, resource.get_name(), resource))
}
Router(Rc::new(RouteRecognizer::new(prefix, resources)))
}
pub fn has_route(&self, path: &str) -> bool {
self.0.recognize(path).is_some()
}
pub fn resource_path<'a, U>(&self, prefix: &str, name: &str, elements: U)
-> Result<String, UriGenerationError>
where U: IntoIterator<Item=&'a str>
{
if let Some(pattern) = self.0.get_pattern(name) {
let mut iter = elements.into_iter();
let mut vec = vec![prefix];
for el in pattern.elements() {
match *el {
PatternElement::Str(ref s) => vec.push(s),
PatternElement::Var(_) => {
if let Some(val) = iter.next() {
vec.push(val)
} else {
return Err(UriGenerationError::NotEnoughElements)
}
}
}
}
let s = vec.join("/").to_owned();
Ok(s)
} else {
Err(UriGenerationError::ResourceNotFound)
}
}
}
/// Application
pub struct HttpApplication<S> {
state: Rc<S>,
@ -66,12 +22,12 @@ pub struct HttpApplication<S> {
impl<S: 'static> HttpApplication<S> {
fn run(&self, req: HttpRequest) -> Reply {
let mut req = req.with_state(Rc::clone(&self.state));
let mut req = req.with_state(Rc::clone(&self.state), self.router.clone());
if let Some((params, h)) = self.router.0.recognize(req.path()) {
if let Some((params, h)) = self.router.query(req.path()) {
if let Some(params) = params {
req.set_match_info(params);
req.set_prefix(self.router.0.prefix());
req.set_prefix(self.router.prefix().len());
}
h.handle(req)
} else {
@ -214,14 +170,10 @@ impl<S> Application<S> where S: 'static {
/// Finish application configuration and create HttpHandler object
pub fn finish(&mut self) -> HttpApplication<S> {
let parts = self.parts.take().expect("Use after finish");
let prefix = if parts.prefix.ends_with('/') {
parts.prefix
} else {
parts.prefix + "/"
};
let prefix = parts.prefix.trim().trim_right_matches('/');
HttpApplication {
state: Rc::new(parts.state),
prefix: prefix.clone(),
prefix: prefix.to_owned(),
default: parts.default,
router: Router::new(prefix, parts.resources),
middlewares: Rc::new(parts.middlewares),

View File

@ -11,6 +11,7 @@
// dev specific
pub use info::ConnectionInfo;
pub use handler::Handler;
pub use router::Router;
pub use pipeline::Pipeline;
pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler};
pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement};

View File

@ -15,6 +15,7 @@ use http::{header, StatusCode, Error as HttpError};
use http::uri::InvalidUriBytes;
use http_range::HttpRangeParseError;
use serde_json::error::Error as JsonError;
use url::ParseError as UrlParseError;
// re-exports
pub use cookie::{ParseError as CookieParseError};
@ -405,11 +406,24 @@ impl ResponseError for UriSegmentError {
/// Errors which can occur when attempting to generate resource uri.
#[derive(Fail, Debug, PartialEq)]
pub enum UriGenerationError {
pub enum UrlGenerationError {
#[fail(display="Resource not found")]
ResourceNotFound,
#[fail(display="Not all path pattern covered")]
NotEnoughElements,
#[fail(display="Router is not available")]
RouterNotAvailable,
#[fail(display="{}", _0)]
ParseError(#[cause] UrlParseError),
}
/// `InternalServerError` for `UrlGeneratorError`
impl ResponseError for UrlGenerationError {}
impl From<UrlParseError> for UrlGenerationError {
fn from(err: UrlParseError) -> Self {
UrlGenerationError::ParseError(err)
}
}
#[cfg(test)]

View File

@ -5,15 +5,16 @@ use std::net::SocketAddr;
use std::collections::HashMap;
use bytes::BytesMut;
use futures::{Async, Future, Stream, Poll};
use url::form_urlencoded;
use url::{Url, form_urlencoded};
use http::{header, Uri, Method, Version, HeaderMap, Extensions};
use {Cookie, HttpRange};
use info::ConnectionInfo;
use router::Router;
use recognizer::Params;
use payload::Payload;
use multipart::Multipart;
use error::{ParseError, PayloadError,
use error::{ParseError, PayloadError, UrlGenerationError,
MultipartError, CookieParseError, HttpRangeError, UrlencodedError};
@ -30,6 +31,7 @@ struct HttpMessage {
addr: Option<SocketAddr>,
payload: Payload,
info: Option<ConnectionInfo<'static>>,
}
impl Default for HttpMessage {
@ -53,7 +55,7 @@ impl Default for HttpMessage {
}
/// An HTTP Request
pub struct HttpRequest<S=()>(Rc<HttpMessage>, Rc<S>);
pub struct HttpRequest<S=()>(Rc<HttpMessage>, Rc<S>, Option<Router<S>>);
impl HttpRequest<()> {
/// Construct a new Request.
@ -76,13 +78,14 @@ impl HttpRequest<()> {
extensions: Extensions::new(),
info: None,
}),
Rc::new(())
Rc::new(()),
None,
)
}
/// Construct new http request with state.
pub fn with_state<S>(self, state: Rc<S>) -> HttpRequest<S> {
HttpRequest(self.0, state)
pub fn with_state<S>(self, state: Rc<S>, router: Router<S>) -> HttpRequest<S> {
HttpRequest(self.0, state, Some(router))
}
}
@ -90,10 +93,11 @@ impl<S> HttpRequest<S> {
/// Construct new http request without state.
pub fn clone_without_state(&self) -> HttpRequest {
HttpRequest(Rc::clone(&self.0), Rc::new(()))
HttpRequest(Rc::clone(&self.0), Rc::new(()), None)
}
/// get mutable reference for inner message
#[inline]
fn as_mut(&mut self) -> &mut HttpMessage {
let r: &HttpMessage = self.0.as_ref();
#[allow(mutable_transmutes)]
@ -101,6 +105,7 @@ impl<S> HttpRequest<S> {
}
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
&self.1
}
@ -111,6 +116,7 @@ impl<S> HttpRequest<S> {
&mut self.as_mut().extensions
}
#[inline]
pub(crate) fn set_prefix(&mut self, idx: usize) {
self.as_mut().prefix = idx;
}
@ -162,7 +168,6 @@ impl<S> HttpRequest<S> {
}
/// Load *ConnectionInfo* for currect request.
#[inline]
pub fn load_connection_info(&mut self) -> &ConnectionInfo {
if self.0.info.is_none() {
let info: ConnectionInfo<'static> = unsafe{
@ -172,17 +177,35 @@ impl<S> HttpRequest<S> {
self.0.info.as_ref().unwrap()
}
pub fn url_for<U, I>(&mut self, name: &str, elements: U) -> Result<Url, UrlGenerationError>
where U: IntoIterator<Item=I>,
I: AsRef<str>,
{
if self.router().is_none() {
Err(UrlGenerationError::RouterNotAvailable)
} else {
let path = self.router().unwrap().resource_path(name, elements)?;
let conn = self.load_connection_info();
Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?)
}
}
#[inline]
pub fn router(&self) -> Option<&Router<S>> {
self.2.as_ref()
}
#[inline]
pub fn peer_addr(&self) -> Option<&SocketAddr> {
self.0.addr.as_ref()
}
#[inline]
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
self.as_mut().addr = addr
}
/// Return a new iterator that yields pairs of `Cow<str>` for query parameters
#[inline]
pub fn query(&self) -> HashMap<String, String> {
let mut q: HashMap<String, String> = HashMap::new();
if let Some(query) = self.0.uri.query().as_ref() {
@ -206,6 +229,7 @@ impl<S> HttpRequest<S> {
}
/// Return request cookies.
#[inline]
pub fn cookies(&self) -> &Vec<Cookie<'static>> {
&self.0.cookies
}
@ -245,6 +269,7 @@ impl<S> HttpRequest<S> {
pub fn match_info(&self) -> &Params { &self.0.params }
/// Set request Params.
#[inline]
pub fn set_match_info(&mut self, params: Params) {
self.as_mut().params = params;
}
@ -324,6 +349,7 @@ impl<S> HttpRequest<S> {
}
/// Return payload
#[inline]
pub fn take_payload(&mut self) -> Payload {
mem::replace(&mut self.as_mut().payload, Payload::empty())
}
@ -387,13 +413,13 @@ impl Default for HttpRequest<()> {
/// Construct default request
fn default() -> HttpRequest {
HttpRequest(Rc::new(HttpMessage::default()), Rc::new(()))
HttpRequest(Rc::new(HttpMessage::default()), Rc::new(()), None)
}
}
impl<S> Clone for HttpRequest<S> {
fn clone(&self) -> HttpRequest<S> {
HttpRequest(Rc::clone(&self.0), Rc::clone(&self.1))
HttpRequest(Rc::clone(&self.0), Rc::clone(&self.1), None)
}
}
@ -454,9 +480,10 @@ impl Future for UrlEncoded {
#[cfg(test)]
mod tests {
use super::*;
use http::Uri;
use std::str::FromStr;
use payload::Payload;
use http::Uri;
use resource::Resource;
#[test]
fn test_urlencoded_error() {
@ -502,4 +529,32 @@ mod tests {
assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::ContentType);
}
#[test]
fn test_url_for() {
let mut headers = HeaderMap::new();
headers.insert(header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"));
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, Payload::empty());
let mut resource = Resource::default();
resource.name("index");
let mut map = HashMap::new();
map.insert("/user/{name}.{ext}".to_owned(), resource);
let router = Router::new("", map);
assert_eq!(req.url_for("unknown", &["test"]),
Err(UrlGenerationError::RouterNotAvailable));
let mut req = req.with_state(Rc::new(()), router);
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");
}
}

View File

@ -57,6 +57,7 @@ mod httpresponse;
mod payload;
mod info;
mod route;
mod router;
mod resource;
mod recognizer;
mod handler;

View File

@ -204,16 +204,17 @@ FROM_STR!(std::net::SocketAddrV6);
pub struct RouteRecognizer<T> {
re: RegexSet,
prefix: usize,
prefix: String,
routes: Vec<(Pattern, T)>,
patterns: HashMap<String, Pattern>,
}
impl<T> RouteRecognizer<T> {
pub fn new<P: Into<String>, U, K>(prefix: P, routes: U) -> Self
pub fn new<P, U, K>(prefix: P, routes: U) -> Self
where U: IntoIterator<Item=(K, Option<String>, T)>,
K: Into<String>,
P: Into<String>,
{
let mut paths = Vec::new();
let mut handlers = Vec::new();
@ -231,7 +232,7 @@ impl<T> RouteRecognizer<T> {
RouteRecognizer {
re: regset.unwrap(),
prefix: prefix.into().len() - 1,
prefix: prefix.into(),
routes: handlers,
patterns: patterns,
}
@ -242,29 +243,20 @@ impl<T> RouteRecognizer<T> {
}
/// Length of the prefix
pub fn prefix(&self) -> usize {
self.prefix
}
pub fn set_prefix<P: Into<String>>(&mut self, prefix: P) {
let p = prefix.into();
if p.ends_with('/') {
self.prefix = p.len() - 1;
} else {
self.prefix = p.len();
}
pub fn prefix(&self) -> &str {
&self.prefix
}
pub fn recognize(&self, path: &str) -> Option<(Option<Params>, &T)> {
let p = &path[self.prefix..];
let p = &path[self.prefix.len()..];
if p.is_empty() {
if let Some(idx) = self.re.matches("/").into_iter().next() {
let (ref pattern, ref route) = self.routes[idx];
return Some((pattern.match_info(&path[self.prefix..]), route))
return Some((pattern.match_info(&path[self.prefix.len()..]), route))
}
} else if let Some(idx) = self.re.matches(p).into_iter().next() {
let (ref pattern, ref route) = self.routes[idx];
return Some((pattern.match_info(&path[self.prefix..]), route))
return Some((pattern.match_info(&path[self.prefix.len()..]), route))
}
None
}
@ -400,7 +392,7 @@ mod tests {
("/v{val}/{val2}/index.html", None, 4),
("/v/{tail:.*}", None, 5),
];
let rec = RouteRecognizer::new("/", routes);
let rec = RouteRecognizer::new("", routes);
let (params, val) = rec.recognize("/name").unwrap();
assert_eq!(*val, 1);

View File

@ -44,7 +44,7 @@ impl<S> Default for Resource<S> {
}
}
impl<S> Resource<S> where S: 'static {
impl<S> Resource<S> {
pub(crate) fn default_not_found() -> Self {
Resource {
@ -62,6 +62,9 @@ impl<S> Resource<S> where S: 'static {
pub(crate) fn get_name(&self) -> Option<String> {
if self.name.is_empty() { None } else { Some(self.name.clone()) }
}
}
impl<S: 'static> Resource<S> {
/// Register a new route and return mutable reference to *Route* object.
/// *Route* is used for route configuration, i.e. adding predicates, setting up handler.

73
src/router.rs Normal file
View File

@ -0,0 +1,73 @@
use std::rc::Rc;
use std::collections::HashMap;
use error::UrlGenerationError;
use resource::Resource;
use recognizer::{Params, RouteRecognizer, PatternElement};
/// Interface for application router.
pub struct Router<S>(Rc<RouteRecognizer<Resource<S>>>);
impl<S> Router<S> {
pub(crate) fn new(prefix: &str, map: HashMap<String, Resource<S>>) -> Router<S>
{
let prefix = prefix.trim().trim_right_matches('/').to_owned();
let mut resources = Vec::new();
for (path, resource) in map {
resources.push((path, resource.get_name(), resource))
}
Router(Rc::new(RouteRecognizer::new(prefix, resources)))
}
/// Router prefix
#[inline]
pub(crate) fn prefix(&self) -> &str {
self.0.prefix()
}
/// Query for matched resource
pub fn query(&self, path: &str) -> Option<(Option<Params>, &Resource<S>)> {
self.0.recognize(path)
}
/// Check if application contains matching route.
pub fn has_route(&self, path: &str) -> bool {
self.0.recognize(path).is_some()
}
/// Build named resource path
pub fn resource_path<U, I>(&self, name: &str, elements: U)
-> Result<String, UrlGenerationError>
where U: IntoIterator<Item=I>,
I: AsRef<str>,
{
if let Some(pattern) = self.0.get_pattern(name) {
let mut path = String::from(self.prefix());
path.push('/');
let mut iter = elements.into_iter();
for el in pattern.elements() {
match *el {
PatternElement::Str(ref s) => path.push_str(s),
PatternElement::Var(_) => {
if let Some(val) = iter.next() {
path.push_str(val.as_ref())
} else {
return Err(UrlGenerationError::NotEnoughElements)
}
}
}
}
Ok(path)
} else {
Err(UrlGenerationError::ResourceNotFound)
}
}
}
impl<S: 'static> Clone for Router<S> {
fn clone(&self) -> Router<S> {
Router(Rc::clone(&self.0))
}
}

View File

@ -92,7 +92,7 @@ fn test_request_match_info() {
let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(),
Version::HTTP_11, HeaderMap::new(), Payload::empty());
let rec = RouteRecognizer::new("/".to_owned(), vec![("/{key}/".to_owned(), None, 1)]);
let rec = RouteRecognizer::new("", vec![("/{key}/".to_owned(), None, 1)]);
let (params, _) = rec.recognize(req.path()).unwrap();
let params = params.unwrap();