Files
hyper/src/client/request.rs
Sean McArthur 25010fc1fc feat(client): add Proxy support
This works by configuring proxy options on a `Client`, such as
`client.set_proxy("http", "127.0.0.1", "8018")`.

Closes #531
2016-04-25 15:49:57 -07:00

316 lines
9.5 KiB
Rust

//! Client Requests
use std::marker::PhantomData;
use std::io::{self, Write};
use std::time::Duration;
use url::Url;
use method::Method;
use header::Headers;
use header::Host;
use net::{NetworkStream, NetworkConnector, DefaultConnector, Fresh, Streaming};
use version;
use client::{Response, get_host_and_port};
use http::{HttpMessage, RequestHead};
use http::h1::Http11Message;
/// A client request to a remote server.
/// The W type tracks the state of the request, Fresh vs Streaming.
pub struct Request<W> {
/// The target URI for this request.
pub url: Url,
/// The HTTP version of this request.
pub version: version::HttpVersion,
message: Box<HttpMessage>,
headers: Headers,
method: Method,
_marker: PhantomData<W>,
}
impl<W> Request<W> {
/// Read the Request headers.
#[inline]
pub fn headers(&self) -> &Headers { &self.headers }
/// Read the Request method.
#[inline]
pub fn method(&self) -> Method { self.method.clone() }
/// Set the write timeout.
#[inline]
pub fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
self.message.set_write_timeout(dur)
}
/// Set the read timeout.
#[inline]
pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
self.message.set_read_timeout(dur)
}
}
impl Request<Fresh> {
/// Create a new `Request<Fresh>` that will use the given `HttpMessage` for its communication
/// with the server. This implies that the given `HttpMessage` instance has already been
/// properly initialized by the caller (e.g. a TCP connection's already established).
pub fn with_message(method: Method, url: Url, message: Box<HttpMessage>)
-> ::Result<Request<Fresh>> {
let mut headers = Headers::new();
{
let (host, port) = try!(get_host_and_port(&url));
headers.set(Host {
hostname: host.to_owned(),
port: Some(port),
});
}
Ok(Request::with_headers_and_message(method, url, headers, message))
}
#[doc(hidden)]
pub fn with_headers_and_message(method: Method, url: Url, headers: Headers, message: Box<HttpMessage>)
-> Request<Fresh> {
Request {
method: method,
headers: headers,
url: url,
version: version::HttpVersion::Http11,
message: message,
_marker: PhantomData,
}
}
/// Create a new client request.
pub fn new(method: Method, url: Url) -> ::Result<Request<Fresh>> {
let conn = DefaultConnector::default();
Request::with_connector(method, url, &conn)
}
/// Create a new client request with a specific underlying NetworkStream.
pub fn with_connector<C, S>(method: Method, url: Url, connector: &C)
-> ::Result<Request<Fresh>> where
C: NetworkConnector<Stream=S>,
S: Into<Box<NetworkStream + Send>> {
let stream = {
let (host, port) = try!(get_host_and_port(&url));
try!(connector.connect(host, port, url.scheme())).into()
};
Request::with_message(method, url, Box::new(Http11Message::with_stream(stream)))
}
/// Consume a Fresh Request, writing the headers and method,
/// returning a Streaming Request.
pub fn start(mut self) -> ::Result<Request<Streaming>> {
let head = match self.message.set_outgoing(RequestHead {
headers: self.headers,
method: self.method,
url: self.url,
}) {
Ok(head) => head,
Err(e) => {
let _ = self.message.close_connection();
return Err(From::from(e));
}
};
Ok(Request {
method: head.method,
headers: head.headers,
url: head.url,
version: self.version,
message: self.message,
_marker: PhantomData,
})
}
/// Get a mutable reference to the Request headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
}
impl Request<Streaming> {
/// Completes writing the request, and returns a response to read from.
///
/// Consumes the Request.
pub fn send(self) -> ::Result<Response> {
Response::with_message(self.url, self.message)
}
}
impl Write for Request<Streaming> {
#[inline]
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
match self.message.write(msg) {
Ok(n) => Ok(n),
Err(e) => {
let _ = self.message.close_connection();
Err(e)
}
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match self.message.flush() {
Ok(r) => Ok(r),
Err(e) => {
let _ = self.message.close_connection();
Err(e)
}
}
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use std::str::from_utf8;
use url::Url;
use method::Method::{Get, Head, Post};
use mock::{MockStream, MockConnector};
use net::Fresh;
use header::{ContentLength,TransferEncoding,Encoding};
use url::form_urlencoded;
use super::Request;
use http::h1::Http11Message;
fn run_request(req: Request<Fresh>) -> Vec<u8> {
let req = req.start().unwrap();
let message = req.message;
let mut message = message.downcast::<Http11Message>().ok().unwrap();
message.flush_outgoing().unwrap();
let stream = *message
.into_inner().downcast::<MockStream>().ok().unwrap();
stream.write
}
fn assert_no_body(s: &str) {
assert!(!s.contains("Content-Length:"));
assert!(!s.contains("Transfer-Encoding:"));
}
#[test]
fn test_get_empty_body() {
let req = Request::with_connector(
Get, Url::parse("http://example.dom").unwrap(), &mut MockConnector
).unwrap();
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
assert_no_body(s);
}
#[test]
fn test_head_empty_body() {
let req = Request::with_connector(
Head, Url::parse("http://example.dom").unwrap(), &mut MockConnector
).unwrap();
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
assert_no_body(s);
}
#[test]
fn test_url_query() {
let url = Url::parse("http://example.dom?q=value").unwrap();
let req = Request::with_connector(
Get, url, &mut MockConnector
).unwrap();
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
assert!(s.contains("?q=value"));
}
#[test]
fn test_post_content_length() {
let url = Url::parse("http://example.dom").unwrap();
let mut req = Request::with_connector(
Post, url, &mut MockConnector
).unwrap();
let mut body = String::new();
form_urlencoded::Serializer::new(&mut body).append_pair("q", "value");
req.headers_mut().set(ContentLength(body.len() as u64));
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
assert!(s.contains("Content-Length:"));
}
#[test]
fn test_post_chunked() {
let url = Url::parse("http://example.dom").unwrap();
let req = Request::with_connector(
Post, url, &mut MockConnector
).unwrap();
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
assert!(!s.contains("Content-Length:"));
}
#[test]
fn test_host_header() {
let url = Url::parse("http://example.dom").unwrap();
let req = Request::with_connector(
Get, url, &mut MockConnector
).unwrap();
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
assert!(s.contains("Host: example.dom"));
}
#[test]
fn test_proxy() {
let url = Url::parse("http://example.dom").unwrap();
let proxy_url = Url::parse("http://pro.xy").unwrap();
let mut req = Request::with_connector(
Get, proxy_url, &mut MockConnector
).unwrap();
req.url = url;
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
let request_line = "GET http://example.dom/ HTTP/1.1";
assert_eq!(&s[..request_line.len()], request_line);
assert!(s.contains("Host: pro.xy"));
}
#[test]
fn test_post_chunked_with_encoding() {
let url = Url::parse("http://example.dom").unwrap();
let mut req = Request::with_connector(
Post, url, &mut MockConnector
).unwrap();
req.headers_mut().set(TransferEncoding(vec![Encoding::Chunked]));
let bytes = run_request(req);
let s = from_utf8(&bytes[..]).unwrap();
assert!(!s.contains("Content-Length:"));
assert!(s.contains("Transfer-Encoding:"));
}
#[test]
fn test_write_error_closes() {
let url = Url::parse("http://hyper.rs").unwrap();
let req = Request::with_connector(
Get, url, &mut MockConnector
).unwrap();
let mut req = req.start().unwrap();
req.message.downcast_mut::<Http11Message>().unwrap()
.get_mut().downcast_mut::<MockStream>().unwrap()
.error_on_write = true;
req.write(b"foo").unwrap();
assert!(req.flush().is_err());
assert!(req.message.downcast_ref::<Http11Message>().unwrap()
.get_ref().downcast_ref::<MockStream>().unwrap()
.is_closed);
}
}