feat(lib): remove extern Url type usage
BREAKING CHANGE: The `Url` type is no longer used. Any instance in the `Client` API has had it replaced with `hyper::Uri`. This also means `Error::Uri` has changed types to `hyper::error::UriError`. The type `hyper::header::parsing::HTTP_VALUE` has been made private, as an implementation detail. The function `http_percent_encoding` should be used instead.
This commit is contained in:
@@ -28,7 +28,7 @@ fn get_one_at_a_time(b: &mut test::Bencher) {
|
|||||||
|
|
||||||
let client = hyper::Client::new(&handle);
|
let client = hyper::Client::new(&handle);
|
||||||
|
|
||||||
let url: hyper::Url = format!("http://{}/get", addr).parse().unwrap();
|
let url: hyper::Uri = format!("http://{}/get", addr).parse().unwrap();
|
||||||
|
|
||||||
b.bytes = 160 * 2 + PHRASE.len() as u64;
|
b.bytes = 160 * 2 + PHRASE.len() as u64;
|
||||||
b.iter(move || {
|
b.iter(move || {
|
||||||
@@ -51,7 +51,7 @@ fn post_one_at_a_time(b: &mut test::Bencher) {
|
|||||||
|
|
||||||
let client = hyper::Client::new(&handle);
|
let client = hyper::Client::new(&handle);
|
||||||
|
|
||||||
let url: hyper::Url = format!("http://{}/get", addr).parse().unwrap();
|
let url: hyper::Uri = format!("http://{}/get", addr).parse().unwrap();
|
||||||
|
|
||||||
let post = "foo bar baz quux";
|
let post = "foo bar baz quux";
|
||||||
b.bytes = 180 * 2 + post.len() as u64 + PHRASE.len() as u64;
|
b.bytes = 180 * 2 + post.len() as u64 + PHRASE.len() as u64;
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = hyper::Url::parse(&url).unwrap();
|
let url = url.parse::<hyper::Uri>().unwrap();
|
||||||
if url.scheme() != "http" {
|
if url.scheme() != Some("http") {
|
||||||
println!("This example only works with 'http' URLs.");
|
println!("This example only works with 'http' URLs.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use tokio_io::{AsyncRead, AsyncWrite};
|
|||||||
use tokio::reactor::Handle;
|
use tokio::reactor::Handle;
|
||||||
use tokio::net::{TcpStream, TcpStreamNew};
|
use tokio::net::{TcpStream, TcpStreamNew};
|
||||||
use tokio_service::Service;
|
use tokio_service::Service;
|
||||||
use Url;
|
use Uri;
|
||||||
|
|
||||||
use super::dns;
|
use super::dns;
|
||||||
|
|
||||||
@@ -15,25 +15,25 @@ use super::dns;
|
|||||||
///
|
///
|
||||||
/// This trait is not implemented directly, and only exists to make
|
/// This trait is not implemented directly, and only exists to make
|
||||||
/// the intent clearer. A connector should implement `Service` with
|
/// the intent clearer. A connector should implement `Service` with
|
||||||
/// `Request=Url` and `Response: Io` instead.
|
/// `Request=Uri` and `Response: Io` instead.
|
||||||
pub trait Connect: Service<Request=Url, Error=io::Error> + 'static {
|
pub trait Connect: Service<Request=Uri, Error=io::Error> + 'static {
|
||||||
/// The connected Io Stream.
|
/// The connected Io Stream.
|
||||||
type Output: AsyncRead + AsyncWrite + 'static;
|
type Output: AsyncRead + AsyncWrite + 'static;
|
||||||
/// A Future that will resolve to the connected Stream.
|
/// A Future that will resolve to the connected Stream.
|
||||||
type Future: Future<Item=Self::Output, Error=io::Error> + 'static;
|
type Future: Future<Item=Self::Output, Error=io::Error> + 'static;
|
||||||
/// Connect to a remote address.
|
/// Connect to a remote address.
|
||||||
fn connect(&self, Url) -> <Self as Connect>::Future;
|
fn connect(&self, Uri) -> <Self as Connect>::Future;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Connect for T
|
impl<T> Connect for T
|
||||||
where T: Service<Request=Url, Error=io::Error> + 'static,
|
where T: Service<Request=Uri, Error=io::Error> + 'static,
|
||||||
T::Response: AsyncRead + AsyncWrite,
|
T::Response: AsyncRead + AsyncWrite,
|
||||||
T::Future: Future<Error=io::Error>,
|
T::Future: Future<Error=io::Error>,
|
||||||
{
|
{
|
||||||
type Output = T::Response;
|
type Output = T::Response;
|
||||||
type Future = T::Future;
|
type Future = T::Future;
|
||||||
|
|
||||||
fn connect(&self, url: Url) -> <Self as Connect>::Future {
|
fn connect(&self, url: Uri) -> <Self as Connect>::Future {
|
||||||
self.call(url)
|
self.call(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,21 +66,21 @@ impl fmt::Debug for HttpConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Service for HttpConnector {
|
impl Service for HttpConnector {
|
||||||
type Request = Url;
|
type Request = Uri;
|
||||||
type Response = TcpStream;
|
type Response = TcpStream;
|
||||||
type Error = io::Error;
|
type Error = io::Error;
|
||||||
type Future = HttpConnecting;
|
type Future = HttpConnecting;
|
||||||
|
|
||||||
fn call(&self, url: Url) -> Self::Future {
|
fn call(&self, url: Uri) -> Self::Future {
|
||||||
debug!("Http::connect({:?})", url);
|
debug!("Http::connect({:?})", url);
|
||||||
let host = match url.host_str() {
|
let host = match url.host() {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return HttpConnecting {
|
None => return HttpConnecting {
|
||||||
state: State::Error(Some(io::Error::new(io::ErrorKind::InvalidInput, "invalid url"))),
|
state: State::Error(Some(io::Error::new(io::ErrorKind::InvalidInput, "invalid url"))),
|
||||||
handle: self.handle.clone(),
|
handle: self.handle.clone(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let port = url.port_or_known_default().unwrap_or(80);
|
let port = url.port().unwrap_or(80);
|
||||||
|
|
||||||
HttpConnecting {
|
HttpConnecting {
|
||||||
state: State::Resolving(self.dns.resolve(host.into(), port)),
|
state: State::Resolving(self.dns.resolve(host.into(), port)),
|
||||||
@@ -185,13 +185,12 @@ impl<S: SslClient> HttpsConnector<S> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::io;
|
use std::io;
|
||||||
use tokio::reactor::Core;
|
use tokio::reactor::Core;
|
||||||
use Url;
|
|
||||||
use super::{Connect, HttpConnector};
|
use super::{Connect, HttpConnector};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_non_http_url() {
|
fn test_non_http_url() {
|
||||||
let mut core = Core::new().unwrap();
|
let mut core = Core::new().unwrap();
|
||||||
let url = Url::parse("file:///home/sean/foo.txt").unwrap();
|
let url = "/foo/bar?baz".parse().unwrap();
|
||||||
let connector = HttpConnector::new(1, &core.handle());
|
let connector = HttpConnector::new(1, &core.handle());
|
||||||
|
|
||||||
assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
|
assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::marker::PhantomData;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::{Poll, Async, Future, Stream};
|
use futures::{future, Poll, Async, Future, Stream};
|
||||||
use futures::unsync::oneshot;
|
use futures::unsync::oneshot;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio::reactor::Handle;
|
use tokio::reactor::Handle;
|
||||||
@@ -24,7 +24,7 @@ use header::{Headers, Host};
|
|||||||
use http::{self, TokioBody};
|
use http::{self, TokioBody};
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use self::pool::{Pool, Pooled};
|
use self::pool::{Pool, Pooled};
|
||||||
use Url;
|
use uri::{self, Uri};
|
||||||
|
|
||||||
pub use self::connect::{HttpConnector, Connect};
|
pub use self::connect::{HttpConnector, Connect};
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
@@ -95,7 +95,7 @@ where C: Connect,
|
|||||||
{
|
{
|
||||||
/// Send a GET Request using this Client.
|
/// Send a GET Request using this Client.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get(&self, url: Url) -> FutureResponse {
|
pub fn get(&self, url: Uri) -> FutureResponse {
|
||||||
self.request(Request::new(Method::Get, url))
|
self.request(Request::new(Method::Get, url))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,18 +135,30 @@ where C: Connect,
|
|||||||
type Future = FutureResponse;
|
type Future = FutureResponse;
|
||||||
|
|
||||||
fn call(&self, req: Self::Request) -> Self::Future {
|
fn call(&self, req: Self::Request) -> Self::Future {
|
||||||
let url = req.url().clone();
|
let url = req.uri().clone();
|
||||||
|
let domain = match uri::scheme_and_authority(&url) {
|
||||||
|
Some(uri) => uri,
|
||||||
|
None => {
|
||||||
|
return FutureResponse(Box::new(future::err(::Error::Io(
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"invalid URI for Client Request"
|
||||||
|
)
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let host = Host::new(domain.host().expect("authority implies host").to_owned(), domain.port());
|
||||||
let (mut head, body) = request::split(req);
|
let (mut head, body) = request::split(req);
|
||||||
let mut headers = Headers::new();
|
let mut headers = Headers::new();
|
||||||
headers.set(Host::new(url.host_str().unwrap().to_owned(), url.port()));
|
headers.set(host);
|
||||||
headers.extend(head.headers.iter());
|
headers.extend(head.headers.iter());
|
||||||
head.headers = headers;
|
head.headers = headers;
|
||||||
|
|
||||||
let checkout = self.pool.checkout(&url[..::url::Position::BeforePath]);
|
let checkout = self.pool.checkout(domain.as_ref());
|
||||||
let connect = {
|
let connect = {
|
||||||
let handle = self.handle.clone();
|
let handle = self.handle.clone();
|
||||||
let pool = self.pool.clone();
|
let pool = self.pool.clone();
|
||||||
let pool_key = Rc::new(url[..::url::Position::BeforePath].to_owned());
|
let pool_key = Rc::new(domain.to_string());
|
||||||
self.connector.connect(url)
|
self.connector.connect(url)
|
||||||
.map(move |io| {
|
.map(move |io| {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use Url;
|
|
||||||
|
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use http::{Body, RequestHead};
|
use http::{Body, RequestHead};
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use uri::Uri;
|
use uri::{self, Uri};
|
||||||
use version::HttpVersion;
|
use version::HttpVersion;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
/// A client request to a remote server.
|
/// A client request to a remote server.
|
||||||
pub struct Request<B = Body> {
|
pub struct Request<B = Body> {
|
||||||
method: Method,
|
method: Method,
|
||||||
url: Url,
|
uri: Uri,
|
||||||
version: HttpVersion,
|
version: HttpVersion,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
body: Option<B>,
|
body: Option<B>,
|
||||||
@@ -22,10 +19,10 @@ pub struct Request<B = Body> {
|
|||||||
impl<B> Request<B> {
|
impl<B> Request<B> {
|
||||||
/// Construct a new Request.
|
/// Construct a new Request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(method: Method, url: Url) -> Request<B> {
|
pub fn new(method: Method, uri: Uri) -> Request<B> {
|
||||||
Request {
|
Request {
|
||||||
method: method,
|
method: method,
|
||||||
url: url,
|
uri: uri,
|
||||||
version: HttpVersion::default(),
|
version: HttpVersion::default(),
|
||||||
headers: Headers::new(),
|
headers: Headers::new(),
|
||||||
body: None,
|
body: None,
|
||||||
@@ -33,9 +30,9 @@ impl<B> Request<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the Request Url.
|
/// Read the Request Uri.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn url(&self) -> &Url { &self.url }
|
pub fn uri(&self) -> &Uri { &self.uri }
|
||||||
|
|
||||||
/// Read the Request Version.
|
/// Read the Request Version.
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -57,9 +54,9 @@ impl<B> Request<B> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
|
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
|
||||||
|
|
||||||
/// Set the `Url` of this request.
|
/// Set the `Uri` of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_url(&mut self, url: Url) { self.url = url; }
|
pub fn set_uri(&mut self, uri: Uri) { self.uri = uri; }
|
||||||
|
|
||||||
/// Set the `HttpVersion` of this request.
|
/// Set the `HttpVersion` of this request.
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -81,7 +78,7 @@ impl<B> fmt::Debug for Request<B> {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.debug_struct("Request")
|
f.debug_struct("Request")
|
||||||
.field("method", &self.method)
|
.field("method", &self.method)
|
||||||
.field("url", &self.url)
|
.field("uri", &self.uri)
|
||||||
.field("version", &self.version)
|
.field("version", &self.version)
|
||||||
.field("headers", &self.headers)
|
.field("headers", &self.headers)
|
||||||
.finish()
|
.finish()
|
||||||
@@ -90,9 +87,9 @@ impl<B> fmt::Debug for Request<B> {
|
|||||||
|
|
||||||
pub fn split<B>(req: Request<B>) -> (RequestHead, Option<B>) {
|
pub fn split<B>(req: Request<B>) -> (RequestHead, Option<B>) {
|
||||||
let uri = if req.is_proxy {
|
let uri = if req.is_proxy {
|
||||||
Uri::from(req.url)
|
req.uri
|
||||||
} else {
|
} else {
|
||||||
Uri::from_str(&req.url[::url::Position::BeforePath..::url::Position::AfterQuery]).expect("url is not uri")
|
uri::origin_form(&req.uri)
|
||||||
};
|
};
|
||||||
let head = RequestHead {
|
let head = RequestHead {
|
||||||
subject: ::http::RequestLine(req.method, uri),
|
subject: ::http::RequestLine(req.method, uri),
|
||||||
|
|||||||
31
src/error.rs
31
src/error.rs
@@ -6,7 +6,8 @@ use std::str::Utf8Error;
|
|||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
use httparse;
|
use httparse;
|
||||||
use url;
|
|
||||||
|
pub use uri::UriError;
|
||||||
|
|
||||||
use self::Error::{
|
use self::Error::{
|
||||||
Method,
|
Method,
|
||||||
@@ -21,8 +22,6 @@ use self::Error::{
|
|||||||
Utf8
|
Utf8
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use url::ParseError;
|
|
||||||
|
|
||||||
/// Result type often returned from methods that can have hyper `Error`s.
|
/// Result type often returned from methods that can have hyper `Error`s.
|
||||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ pub enum Error {
|
|||||||
/// An invalid `Method`, such as `GE,T`.
|
/// An invalid `Method`, such as `GE,T`.
|
||||||
Method,
|
Method,
|
||||||
/// An invalid `Uri`, such as `exam ple.domain`.
|
/// An invalid `Uri`, such as `exam ple.domain`.
|
||||||
Uri(url::ParseError),
|
Uri(UriError),
|
||||||
/// An invalid `HttpVersion`, such as `HTP/1.1`
|
/// An invalid `HttpVersion`, such as `HTP/1.1`
|
||||||
Version,
|
Version,
|
||||||
/// An invalid `Header`.
|
/// An invalid `Header`.
|
||||||
@@ -102,15 +101,15 @@ impl StdError for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IoError> for Error {
|
impl From<UriError> for Error {
|
||||||
fn from(err: IoError) -> Error {
|
fn from(err: UriError) -> Error {
|
||||||
Io(err)
|
Uri(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<url::ParseError> for Error {
|
impl From<IoError> for Error {
|
||||||
fn from(err: url::ParseError) -> Error {
|
fn from(err: IoError) -> Error {
|
||||||
Uri(err)
|
Io(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +144,6 @@ mod tests {
|
|||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::io;
|
use std::io;
|
||||||
use httparse;
|
use httparse;
|
||||||
use url;
|
|
||||||
use super::Error;
|
use super::Error;
|
||||||
use super::Error::*;
|
use super::Error::*;
|
||||||
|
|
||||||
@@ -185,7 +183,6 @@ mod tests {
|
|||||||
fn test_from() {
|
fn test_from() {
|
||||||
|
|
||||||
from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..));
|
from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..));
|
||||||
from_and_cause!(url::ParseError::EmptyHost => Uri(..));
|
|
||||||
|
|
||||||
from!(httparse::Error::HeaderName => Header);
|
from!(httparse::Error::HeaderName => Header);
|
||||||
from!(httparse::Error::HeaderName => Header);
|
from!(httparse::Error::HeaderName => Header);
|
||||||
@@ -196,14 +193,4 @@ mod tests {
|
|||||||
from!(httparse::Error::TooManyHeaders => TooLarge);
|
from!(httparse::Error::TooManyHeaders => TooLarge);
|
||||||
from!(httparse::Error::Version => Version);
|
from!(httparse::Error::Version => Version);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
|
||||||
#[test]
|
|
||||||
fn test_from_ssl() {
|
|
||||||
use openssl::ssl::error::SslError;
|
|
||||||
|
|
||||||
from!(SslError::StreamError(
|
|
||||||
io::Error::new(io::ErrorKind::Other, "ssl negotiation")) => Io(..));
|
|
||||||
from_and_cause!(SslError::SslSessionClosed => Ssl(..));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,9 @@
|
|||||||
use language_tags::LanguageTag;
|
use language_tags::LanguageTag;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
use url::percent_encoding;
|
|
||||||
|
|
||||||
use header::{Header, Raw, parsing};
|
use header::{Header, Raw, parsing};
|
||||||
use header::parsing::{parse_extended_value, HTTP_VALUE};
|
use header::parsing::{parse_extended_value, http_percent_encode};
|
||||||
use header::shared::Charset;
|
use header::shared::Charset;
|
||||||
|
|
||||||
/// The implied disposition of the content of the HTTP body
|
/// The implied disposition of the content of the HTTP body
|
||||||
@@ -182,8 +181,7 @@ impl fmt::Display for ContentDisposition {
|
|||||||
try!(write!(f, "{}", lang));
|
try!(write!(f, "{}", lang));
|
||||||
};
|
};
|
||||||
try!(write!(f, "'"));
|
try!(write!(f, "'"));
|
||||||
try!(f.write_str(
|
try!(http_percent_encode(f, bytes))
|
||||||
&percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)),
|
DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use url::percent_encoding;
|
|||||||
use header::Raw;
|
use header::Raw;
|
||||||
use header::shared::Charset;
|
use header::shared::Charset;
|
||||||
|
|
||||||
|
|
||||||
/// Reads a single raw string when parsing a header.
|
/// Reads a single raw string when parsing a header.
|
||||||
pub fn from_one_raw_str<T: str::FromStr>(raw: &Raw) -> ::Result<T> {
|
pub fn from_one_raw_str<T: str::FromStr>(raw: &Raw) -> ::Result<T> {
|
||||||
if let Some(line) = raw.one() {
|
if let Some(line) = raw.one() {
|
||||||
@@ -132,25 +133,11 @@ pub fn parse_extended_value(val: &str) -> ::Result<ExtendedValue> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
define_encode_set! {
|
|
||||||
/// This encode set is used for HTTP header values and is defined at
|
|
||||||
/// https://tools.ietf.org/html/rfc5987#section-3.2
|
|
||||||
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
|
|
||||||
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
|
|
||||||
'[', '\\', ']', '{', '}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for HTTP_VALUE {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.pad("HTTP_VALUE")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ExtendedValue {
|
impl Display for ExtendedValue {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let encoded_value =
|
let encoded_value =
|
||||||
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
percent_encoding::percent_encode(&self.value[..], self::percent_encoding_http::HTTP_VALUE);
|
||||||
if let Some(ref lang) = self.language_tag {
|
if let Some(ref lang) = self.language_tag {
|
||||||
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
||||||
} else {
|
} else {
|
||||||
@@ -159,6 +146,33 @@ impl Display for ExtendedValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Percent encode a sequence of bytes with a character set defined in
|
||||||
|
/// https://tools.ietf.org/html/rfc5987#section-3.2
|
||||||
|
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result {
|
||||||
|
let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE);
|
||||||
|
fmt::Display::fmt(&encoded, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod percent_encoding_http {
|
||||||
|
use std::fmt;
|
||||||
|
use url::percent_encoding;
|
||||||
|
|
||||||
|
define_encode_set! {
|
||||||
|
/// This encode set is used for HTTP header values and is defined at
|
||||||
|
/// https://tools.ietf.org/html/rfc5987#section-3.2
|
||||||
|
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
|
||||||
|
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
|
||||||
|
'[', '\\', ']', '{', '}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for HTTP_VALUE {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.pad("HTTP_VALUE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use header::shared::Charset;
|
use header::shared::Charset;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ impl Http1Transaction for ServerTransaction {
|
|||||||
let path = unsafe { ByteStr::from_utf8_unchecked(path) };
|
let path = unsafe { ByteStr::from_utf8_unchecked(path) };
|
||||||
let subject = RequestLine(
|
let subject = RequestLine(
|
||||||
method,
|
method,
|
||||||
try!(::uri::from_mem_str(path)),
|
try!(::uri::from_byte_str(path)),
|
||||||
);
|
);
|
||||||
|
|
||||||
headers.extend(HeadersAsBytesIter {
|
headers.extend(HeadersAsBytesIter {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ByteStr(Bytes);
|
pub struct ByteStr(Bytes);
|
||||||
|
|
||||||
impl ByteStr {
|
impl ByteStr {
|
||||||
@@ -10,7 +11,35 @@ impl ByteStr {
|
|||||||
ByteStr(slice)
|
ByteStr(slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_static(s: &'static str) -> ByteStr {
|
||||||
|
ByteStr(Bytes::from_static(s.as_bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice(&self, from: usize, to: usize) -> ByteStr {
|
||||||
|
assert!(self.as_str().is_char_boundary(from));
|
||||||
|
assert!(self.as_str().is_char_boundary(to));
|
||||||
|
ByteStr(self.0.slice(from, to))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice_to(&self, idx: usize) -> ByteStr {
|
||||||
|
assert!(self.as_str().is_char_boundary(idx));
|
||||||
|
ByteStr(self.0.slice_to(idx))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
unsafe { str::from_utf8_unchecked(self.0.as_ref()) }
|
unsafe { str::from_utf8_unchecked(self.0.as_ref()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for ByteStr {
|
||||||
|
type Target = str;
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for ByteStr {
|
||||||
|
fn from(s: &'a str) -> ByteStr {
|
||||||
|
ByteStr(Bytes::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#![doc(html_root_url = "https://hyperium.github.io/hyper/")]
|
#![doc(html_root_url = "https://hyperium.github.io/hyper/")]
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
//#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
#![deny(missing_debug_implementations)]
|
#![deny(missing_debug_implementations)]
|
||||||
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
|
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
|
||||||
|
|
||||||
@@ -35,7 +35,6 @@ extern crate test;
|
|||||||
|
|
||||||
|
|
||||||
pub use uri::Uri;
|
pub use uri::Uri;
|
||||||
pub use url::Url;
|
|
||||||
pub use client::Client;
|
pub use client::Client;
|
||||||
pub use error::{Result, Error};
|
pub use error::{Result, Error};
|
||||||
pub use header::Headers;
|
pub use header::Headers;
|
||||||
|
|||||||
148
src/uri.rs
148
src/uri.rs
@@ -1,13 +1,8 @@
|
|||||||
use std::borrow::Cow;
|
use std::error::Error as StdError;
|
||||||
use std::fmt::{Display, self};
|
use std::fmt::{Display, self};
|
||||||
use std::ops::Deref;
|
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
|
|
||||||
use http::ByteStr;
|
use http::ByteStr;
|
||||||
use Url;
|
|
||||||
use url::ParseError as UrlError;
|
|
||||||
|
|
||||||
use Error;
|
|
||||||
|
|
||||||
/// The Request-URI of a Request's StartLine.
|
/// The Request-URI of a Request's StartLine.
|
||||||
///
|
///
|
||||||
@@ -32,9 +27,9 @@ use Error;
|
|||||||
/// | | | | |
|
/// | | | | |
|
||||||
/// scheme authority path query fragment
|
/// scheme authority path query fragment
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Uri {
|
pub struct Uri {
|
||||||
source: InternalUri,
|
source: ByteStr,
|
||||||
scheme_end: Option<usize>,
|
scheme_end: Option<usize>,
|
||||||
authority_end: Option<usize>,
|
authority_end: Option<usize>,
|
||||||
query: Option<usize>,
|
query: Option<usize>,
|
||||||
@@ -43,20 +38,23 @@ pub struct Uri {
|
|||||||
|
|
||||||
impl Uri {
|
impl Uri {
|
||||||
/// Parse a string into a `Uri`.
|
/// Parse a string into a `Uri`.
|
||||||
fn new(s: InternalUri) -> Result<Uri, Error> {
|
fn new(s: ByteStr) -> Result<Uri, UriError> {
|
||||||
if s.len() == 0 {
|
if s.len() == 0 {
|
||||||
Err(Error::Uri(UrlError::RelativeUrlWithoutBase))
|
Err(UriError(ErrorKind::Empty))
|
||||||
} else if s.as_bytes() == b"*" {
|
} else if s.as_bytes() == b"*" {
|
||||||
|
// asterisk-form
|
||||||
Ok(Uri {
|
Ok(Uri {
|
||||||
source: InternalUri::Cow("*".into()),
|
source: ByteStr::from_static("*"),
|
||||||
scheme_end: None,
|
scheme_end: None,
|
||||||
authority_end: None,
|
authority_end: None,
|
||||||
query: None,
|
query: None,
|
||||||
fragment: None,
|
fragment: None,
|
||||||
})
|
})
|
||||||
} else if s.as_bytes() == b"/" {
|
} else if s.as_bytes() == b"/" {
|
||||||
|
// shortcut for '/'
|
||||||
Ok(Uri::default())
|
Ok(Uri::default())
|
||||||
} else if s.as_bytes()[0] == b'/' {
|
} else if s.as_bytes()[0] == b'/' {
|
||||||
|
// origin-form
|
||||||
let query = parse_query(&s);
|
let query = parse_query(&s);
|
||||||
let fragment = parse_fragment(&s);
|
let fragment = parse_fragment(&s);
|
||||||
Ok(Uri {
|
Ok(Uri {
|
||||||
@@ -67,22 +65,14 @@ impl Uri {
|
|||||||
fragment: fragment,
|
fragment: fragment,
|
||||||
})
|
})
|
||||||
} else if s.contains("://") {
|
} else if s.contains("://") {
|
||||||
|
// absolute-form
|
||||||
let scheme = parse_scheme(&s);
|
let scheme = parse_scheme(&s);
|
||||||
let auth = parse_authority(&s);
|
let auth = parse_authority(&s);
|
||||||
if let Some(end) = scheme {
|
let scheme_end = scheme.expect("just checked for ':' above");
|
||||||
match &s[..end] {
|
let auth_end = auth.expect("just checked for ://");
|
||||||
"ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {},
|
if scheme_end + 3 == auth_end {
|
||||||
"blob" | "file" => return Err(Error::Method),
|
// authority was empty
|
||||||
_ => return Err(Error::Method),
|
return Err(UriError(ErrorKind::MissingAuthority));
|
||||||
}
|
|
||||||
match auth {
|
|
||||||
Some(a) => {
|
|
||||||
if (end + 3) == a {
|
|
||||||
return Err(Error::Method);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => return Err(Error::Method),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let query = parse_query(&s);
|
let query = parse_query(&s);
|
||||||
let fragment = parse_fragment(&s);
|
let fragment = parse_fragment(&s);
|
||||||
@@ -94,8 +84,10 @@ impl Uri {
|
|||||||
fragment: fragment,
|
fragment: fragment,
|
||||||
})
|
})
|
||||||
} else if (s.contains("/") || s.contains("?")) && !s.contains("://") {
|
} else if (s.contains("/") || s.contains("?")) && !s.contains("://") {
|
||||||
return Err(Error::Method)
|
// last possibility is authority-form, above are illegal characters
|
||||||
|
return Err(UriError(ErrorKind::Malformed))
|
||||||
} else {
|
} else {
|
||||||
|
// authority-form
|
||||||
let len = s.len();
|
let len = s.len();
|
||||||
Ok(Uri {
|
Ok(Uri {
|
||||||
source: s,
|
source: s,
|
||||||
@@ -220,25 +212,12 @@ fn parse_fragment(s: &str) -> Option<usize> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Uri {
|
impl FromStr for Uri {
|
||||||
type Err = Error;
|
type Err = UriError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Uri, Error> {
|
fn from_str(s: &str) -> Result<Uri, UriError> {
|
||||||
//TODO: refactor such that the to_owned() is only required at the end
|
//TODO: refactor such that the to_owned() is only required at the end
|
||||||
//of successful parsing, so an Err doesn't needlessly clone the string.
|
//of successful parsing, so an Err doesn't needlessly clone the string.
|
||||||
Uri::new(InternalUri::Cow(Cow::Owned(s.to_owned())))
|
Uri::new(ByteStr::from(s))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Url> for Uri {
|
|
||||||
fn from(url: Url) -> Uri {
|
|
||||||
let s = InternalUri::Cow(Cow::Owned(url.into_string()));
|
|
||||||
// TODO: we could build the indices with some simple subtraction,
|
|
||||||
// instead of re-parsing an already parsed string.
|
|
||||||
// For example:
|
|
||||||
// let scheme_end = url[..url::Position::AfterScheme].as_ptr() as usize
|
|
||||||
// - url.as_str().as_ptr() as usize;
|
|
||||||
// etc
|
|
||||||
Uri::new(s).expect("Uri::From<Url> failed")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +238,7 @@ impl AsRef<str> for Uri {
|
|||||||
impl Default for Uri {
|
impl Default for Uri {
|
||||||
fn default() -> Uri {
|
fn default() -> Uri {
|
||||||
Uri {
|
Uri {
|
||||||
source: InternalUri::Cow("/".into()),
|
source: ByteStr::from_static("/"),
|
||||||
scheme_end: None,
|
scheme_end: None,
|
||||||
authority_end: None,
|
authority_end: None,
|
||||||
query: None,
|
query: None,
|
||||||
@@ -280,34 +259,67 @@ impl Display for Uri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_mem_str(s: ByteStr) -> Result<Uri, Error> {
|
pub fn from_byte_str(s: ByteStr) -> Result<Uri, UriError> {
|
||||||
Uri::new(InternalUri::Shared(s))
|
Uri::new(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub fn scheme_and_authority(uri: &Uri) -> Option<Uri> {
|
||||||
enum InternalUri {
|
if uri.scheme_end.is_some() {
|
||||||
Cow(Cow<'static, str>),
|
Some(Uri {
|
||||||
Shared(ByteStr),
|
source: uri.source.slice_to(uri.authority_end.expect("scheme without authority")),
|
||||||
|
scheme_end: uri.scheme_end,
|
||||||
|
authority_end: uri.authority_end,
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InternalUri {
|
pub fn origin_form(uri: &Uri) -> Uri {
|
||||||
fn as_str(&self) -> &str {
|
let start = uri.authority_end.unwrap_or(uri.scheme_end.unwrap_or(0));
|
||||||
match *self {
|
let end = if let Some(f) = uri.fragment {
|
||||||
InternalUri::Cow(ref s) => s.as_ref(),
|
uri.source.len() - f - 1
|
||||||
InternalUri::Shared(ref m) => m.as_str(),
|
} else {
|
||||||
|
uri.source.len()
|
||||||
|
};
|
||||||
|
Uri {
|
||||||
|
source: uri.source.slice(start, end),
|
||||||
|
scheme_end: None,
|
||||||
|
authority_end: None,
|
||||||
|
query: uri.query,
|
||||||
|
fragment: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error parsing a `Uri`.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct UriError(ErrorKind);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum ErrorKind {
|
||||||
|
Empty,
|
||||||
|
Malformed,
|
||||||
|
MissingAuthority,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for UriError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.pad(self.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for UriError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match self.0 {
|
||||||
|
ErrorKind::Empty => "empty Uri string",
|
||||||
|
ErrorKind::Malformed => "invalid character in Uri authority",
|
||||||
|
ErrorKind::MissingAuthority => "absolute Uri missing authority segment",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for InternalUri {
|
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &str {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
macro_rules! test_parse {
|
macro_rules! test_parse {
|
||||||
(
|
(
|
||||||
$test_name:ident,
|
$test_name:ident,
|
||||||
@@ -443,13 +455,3 @@ fn test_uri_parse_error() {
|
|||||||
err("localhost/");
|
err("localhost/");
|
||||||
err("localhost?key=val");
|
err("localhost?key=val");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_uri_from_url() {
|
|
||||||
let uri = Uri::from(Url::parse("http://test.com/nazghul?test=3").unwrap());
|
|
||||||
assert_eq!(uri.path(), "/nazghul");
|
|
||||||
assert_eq!(uri.authority(), Some("test.com"));
|
|
||||||
assert_eq!(uri.scheme(), Some("http"));
|
|
||||||
assert_eq!(uri.query(), Some("test=3"));
|
|
||||||
assert_eq!(uri.as_ref(), "http://test.com/nazghul?test=3");
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user