From d301c6a1708c7d408b7f03ac46674a5f0edd3253 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Thu, 5 Jan 2017 15:57:16 -0800 Subject: [PATCH] refactor(client): remove experimental HTTP2 support It didn't really work, and it had dangerous optional dependencies (openssl), so for now, this is going away. HTTP2 should come back with full support in the not-too-distant future. BREAKING CHANGE: There is no more `hyper::http::h2`. --- Cargo.toml | 4 - examples/client_http2.rs | 34 -- src/error.rs | 14 - src/http/h2.rs | 810 --------------------------------------- src/http/mod.rs | 1 - src/lib.rs | 1 - src/mock.rs | 161 -------- 7 files changed, 1025 deletions(-) delete mode 100644 examples/client_http2.rs delete mode 100644 src/http/h2.rs diff --git a/Cargo.toml b/Cargo.toml index 03cac5bd1b..b6f6a68066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,10 +28,6 @@ url = "1.0" version = "0.5" default-features = false -[dependencies.solicit] -version = "0.4" -default-features = false - [dev-dependencies] env_logger = "0.3" diff --git a/examples/client_http2.rs b/examples/client_http2.rs deleted file mode 100644 index c12f4567f9..0000000000 --- a/examples/client_http2.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![deny(warnings)] -extern crate hyper; - -extern crate env_logger; - -use std::env; -use std::io; - -use hyper::Client; -use hyper::header::Connection; -use hyper::http::h2; - -fn main() { - env_logger::init().unwrap(); - - let url = match env::args().nth(1) { - Some(url) => url, - None => { - println!("Usage: client "); - return; - } - }; - - let client = Client::with_protocol(h2::new_protocol()); - - // `Connection: Close` is not a valid header for HTTP/2, but the client handles it gracefully. - let mut res = client.get(&*url) - .header(Connection::close()) - .send().unwrap(); - - println!("Response: {}", res.status); - println!("Headers:\n{}", res.headers); - io::copy(&mut res, &mut io::stdout()).unwrap(); -} diff --git a/src/error.rs b/src/error.rs index 9a4b1a934f..f89f210440 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,7 +7,6 @@ use std::string::FromUtf8Error; use httparse; use url; -use solicit::http::HttpError as Http2Error; #[cfg(feature = "openssl")] use openssl::ssl::error::SslError; @@ -21,7 +20,6 @@ use self::Error::{ Io, Ssl, TooLarge, - Http2, Utf8 }; @@ -49,8 +47,6 @@ pub enum Error { Io(IoError), /// An error from a SSL library. Ssl(Box), - /// An HTTP/2-specific error, coming from the `solicit` library. - Http2(Http2Error), /// Parsing a field as string failed Utf8(Utf8Error), @@ -90,7 +86,6 @@ impl StdError for Error { Uri(ref e) => e.description(), Io(ref e) => e.description(), Ssl(ref e) => e.description(), - Http2(ref e) => e.description(), Utf8(ref e) => e.description(), Error::__Nonexhaustive(ref void) => match *void {} } @@ -101,7 +96,6 @@ impl StdError for Error { Io(ref error) => Some(error), Ssl(ref error) => Some(&**error), Uri(ref error) => Some(error), - Http2(ref error) => Some(error), _ => None, } } @@ -155,18 +149,11 @@ impl From for Error { } } -impl From for Error { - fn from(err: Http2Error) -> Error { - Error::Http2(err) - } -} - #[cfg(test)] mod tests { use std::error::Error as StdError; use std::io; use httparse; - use solicit::http::HttpError as Http2Error; use url; use super::Error; use super::Error::*; @@ -208,7 +195,6 @@ mod tests { from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..)); from_and_cause!(url::ParseError::EmptyHost => Uri(..)); - from_and_cause!(Http2Error::UnknownStreamId => Http2(..)); from!(httparse::Error::HeaderName => Header); from!(httparse::Error::HeaderName => Header); diff --git a/src/http/h2.rs b/src/http/h2.rs deleted file mode 100644 index 95ac5e5242..0000000000 --- a/src/http/h2.rs +++ /dev/null @@ -1,810 +0,0 @@ -//! Adapts the `solicit`-provided HTTP/2 implementation into the `HttpMessage` API. - -use std::io::{self, Write, Read, Cursor}; -use std::net::Shutdown; -use std::ascii::AsciiExt; -use std::mem; -use std::time::Duration; - -use http::{ - Protocol, - HttpMessage, - RequestHead, - ResponseHead, - RawStatus, -}; -use net::{NetworkStream, NetworkConnector}; -use net::{HttpConnector, HttpStream}; -use url::Position as UrlPosition; -use header::Headers; - -use header; -use version; - -use solicit::http::Header as Http2Header; -use solicit::http::HttpScheme; -use solicit::http::HttpError as Http2Error; -use solicit::http::transport::TransportStream; -use solicit::http::client::{ClientStream, HttpConnect, HttpConnectError, write_preface}; -use solicit::client::SimpleClient; - -use httparse; - -/// A trait alias representing all types that are both `NetworkStream` and `Clone`. -pub trait CloneableStream: NetworkStream + Clone {} -impl CloneableStream for S {} - -/// A newtype wrapping any `CloneableStream` in order to provide an implementation of a -/// `TransportSream` trait for all types that are a `CloneableStream`. -#[derive(Clone)] -struct Http2Stream(S); - -impl Write for Http2Stream where S: CloneableStream { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) - } - #[inline] - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } -} - -impl Read for Http2Stream where S: CloneableStream { - #[inline] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) - } -} - -impl TransportStream for Http2Stream where S: CloneableStream { - fn try_split(&self) -> Result, io::Error> { - Ok(self.clone()) - } - - fn close(&mut self) -> Result<(), io::Error> { - self.0.close(Shutdown::Both) - } -} - -/// A helper struct that implements the `HttpConnect` trait from the `solicit` crate. -/// -/// This is used by the `Http2Protocol` when it needs to create a new `SimpleClient`. -struct Http2Connector where S: CloneableStream { - stream: S, - scheme: HttpScheme, - host: String, -} - -#[derive(Debug)] -struct Http2ConnectError(io::Error); - -impl ::std::fmt::Display for Http2ConnectError { - fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(fmt, "HTTP/2 connect error: {}", (self as &::std::error::Error).description()) - } -} - -impl ::std::error::Error for Http2ConnectError { - fn description(&self) -> &str { - self.0.description() - } - - fn cause(&self) -> Option<&::std::error::Error> { - self.0.cause() - } -} - -impl HttpConnectError for Http2ConnectError {} - -impl From for Http2ConnectError { - fn from(e: io::Error) -> Http2ConnectError { Http2ConnectError(e) } -} - -impl HttpConnect for Http2Connector where S: CloneableStream { - /// The type of the underlying transport stream that the `HttpConnection`s - /// produced by this `HttpConnect` implementation will be based on. - type Stream = Http2Stream; - /// The type of the error that can be produced by trying to establish the - /// connection (i.e. calling the `connect` method). - type Err = Http2ConnectError; - - /// Establishes a network connection that can be used by HTTP/2 connections. - fn connect(mut self) -> Result, Self::Err> { - try!(write_preface(&mut self.stream)); - Ok(ClientStream(Http2Stream(self.stream), self.scheme, self.host)) - } -} - -/// The `Protocol` implementation that provides HTTP/2 messages (i.e. `Http2Message`). -pub struct Http2Protocol where C: NetworkConnector + Send + 'static, - S: NetworkStream + Send + Clone { - connector: C, -} - -impl Http2Protocol where C: NetworkConnector + Send + 'static, - S: NetworkStream + Send + Clone { - /// Create a new `Http2Protocol` that will use the given `NetworkConnector` to establish TCP - /// connections to the server. - pub fn with_connector(connector: C) -> Http2Protocol { - Http2Protocol { - connector: connector, - } - } - - /// A private helper method that creates a new `SimpleClient` that will use the given - /// `NetworkStream` to communicate to the remote host. - fn new_client(&self, stream: S, host: String, scheme: HttpScheme) - -> ::Result>> { - Ok(try!(SimpleClient::with_connector(Http2Connector { - stream: stream, - scheme: scheme, - host: host, - }))) - } -} - -impl Protocol for Http2Protocol where C: NetworkConnector + Send + 'static, - S: NetworkStream + Send + Clone { - fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result> { - let stream = try!(self.connector.connect(host, port, scheme)).into(); - - let scheme = match scheme { - "http" => HttpScheme::Http, - "https" => HttpScheme::Https, - _ => return Err(From::from(Http2Error::from( - io::Error::new(io::ErrorKind::Other, "Invalid scheme")))), - }; - let client = try!(self.new_client(stream, host.into(), scheme)); - - Ok(Box::new(Http2Message::with_client(client))) - } -} - -/// Represents an HTTP/2 request, described by a `RequestHead` and the body of the request. -/// A convenience struct only in use by the `Http2Message`. -#[derive(Clone, Debug)] -struct Http2Request { - head: RequestHead, - body: Vec, -} - -/// Represents an HTTP/2 response. -/// A convenience struct only in use by the `Http2Message`. -#[derive(Clone, Debug)] -struct Http2Response { - body: Cursor>, -} - -/// The enum tracks the state of the `Http2Message`. -enum MessageState { - /// State corresponding to no message being set to outgoing yet. - Idle, - /// State corresponding to an outgoing message being written out. - Writing(Http2Request), - /// State corresponding to an incoming message being read. - Reading(Http2Response), -} - -impl MessageState { - fn take_request(&mut self) -> Option { - match *self { - MessageState::Idle | MessageState::Reading(_) => return None, - MessageState::Writing(_) => {}, - } - let old = mem::replace(self, MessageState::Idle); - - match old { - // These states are effectively unreachable since we already know the state - MessageState::Idle | MessageState::Reading(_) => None, - MessageState::Writing(req) => Some(req), - } - } -} - -/// An implementation of the `HttpMessage` trait for HTTP/2. -/// -/// Relies on the `solicit::http::SimpleClient` for HTTP/2 communication. Adapts both outgoing and -/// incoming messages to the API that `hyper` expects in order to be able to use the message in -/// the `hyper::client` module. -pub struct Http2Message where S: CloneableStream { - client: SimpleClient>, - state: MessageState, -} - -impl ::std::fmt::Debug for Http2Message where S: CloneableStream { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { - write!(f, "") - } -} - -impl Http2Message where S: CloneableStream { - /// Helper method that creates a new completely fresh `Http2Message`, which will use the given - /// `SimpleClient` for its HTTP/2 communication. - fn with_client(client: SimpleClient>) -> Http2Message { - Http2Message { - client: client, - state: MessageState::Idle, - } - } -} - -impl Write for Http2Message where S: CloneableStream { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - if let MessageState::Writing(ref mut req) = self.state { - req.body.write(buf) - } else { - Err(io::Error::new(io::ErrorKind::Other, - "Not in a writable state")) - } - } - #[inline] - fn flush(&mut self) -> io::Result<()> { - if let MessageState::Writing(ref mut req) = self.state { - req.body.flush() - } else { - Err(io::Error::new(io::ErrorKind::Other, - "Not in a writable state")) - } - } -} - -impl Read for Http2Message where S: CloneableStream { - #[inline] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - if let MessageState::Reading(ref mut res) = self.state { - res.body.read(buf) - } else { - Err(io::Error::new(io::ErrorKind::Other, - "Not in a readable state")) - } - } -} - -/// A helper function that prepares the headers that should be sent in an HTTP/2 message. -/// -/// Adapts the `Headers` into a list of octet string pairs. -fn prepare_headers(mut headers: Headers) -> Vec { - if headers.remove::() { - warn!("The `Connection` header is not valid for an HTTP/2 connection."); - } - let mut http2_headers: Vec<_> = headers.iter().filter_map(|h| { - if h.is::() { - None - } else { - // HTTP/2 header names MUST be lowercase. - Some((h.name().to_ascii_lowercase().into_bytes(), h.value_string().into_bytes())) - } - }).collect(); - - // Now separately add the cookies, as `hyper` considers `Set-Cookie` to be only a single - // header, even in the face of multiple cookies being set. - if let Some(set_cookie) = headers.get::() { - for cookie in set_cookie.iter() { - http2_headers.push((b"set-cookie".to_vec(), cookie.to_string().into_bytes())); - } - } - - http2_headers -} - -/// A helper function that prepares the body for sending in an HTTP/2 request. -#[inline] -fn prepare_body(body: Vec) -> Option> { - if body.is_empty() { - None - } else { - Some(body) - } -} - -/// Parses a set of HTTP/2 headers into a `hyper::header::Headers` struct. -fn parse_headers(http2_headers: Vec) -> ::Result { - // Adapt the header name from `Vec` to `String`, without making any copies. - let mut headers = Vec::new(); - for (name, value) in http2_headers.into_iter() { - let name = match String::from_utf8(name) { - Ok(name) => name, - Err(_) => return Err(From::from(Http2Error::MalformedResponse)), - }; - headers.push((name, value)); - } - - let mut raw_headers = Vec::new(); - for &(ref name, ref value) in &headers { - raw_headers.push(httparse::Header { name: &name, value: &value }); - } - - Headers::from_raw(&raw_headers) -} - -/// Parses the response, as returned by `solicit`, into a `ResponseHead` and the full response -/// body. -/// -/// Returns them as a two-tuple. -fn parse_response(response: ::solicit::http::Response) -> ::Result<(ResponseHead, Vec)> { - let status = try!(response.status_code()); - let headers = try!(parse_headers(response.headers)); - Ok((ResponseHead { - headers: headers, - raw_status: RawStatus(status, "".into()), - version: version::HttpVersion::Http20, - }, response.body)) -} - -impl HttpMessage for Http2Message where S: CloneableStream { - fn set_outgoing(&mut self, head: RequestHead) -> ::Result { - match self.state { - MessageState::Writing(_) | MessageState::Reading(_) => { - return Err(From::from(Http2Error::from( - io::Error::new(io::ErrorKind::Other, - "An outoging has already been set")))); - }, - MessageState::Idle => {}, - }; - self.state = MessageState::Writing(Http2Request { - head: head.clone(), - body: Vec::new(), - }); - - Ok(head) - } - - fn get_incoming(&mut self) -> ::Result { - // Prepare the request so that it can be passed off to the HTTP/2 client. - let request = match self.state.take_request() { - None => { - return Err(From::from(Http2Error::from( - io::Error::new(io::ErrorKind::Other, - "No request in progress")))); - }, - Some(req) => req, - }; - let (RequestHead { headers, method, url }, body) = (request.head, request.body); - - let method = method.as_ref().as_bytes(); - let path = url[UrlPosition::BeforePath..UrlPosition::AfterQuery].as_bytes(); - let extra_headers = prepare_headers(headers); - let body = prepare_body(body); - - // Finally, everything is ready and we issue the request. - let stream_id = try!(self.client.request(method, &path, &extra_headers, body)); - - // Wait for the response - let resp = try!(self.client.get_response(stream_id)); - - // Now that the response is back, adapt it to the structs that hyper expects/provides. - let (head, body) = try!(parse_response(resp)); - - // For now, since `solicit` has already read the full response, we just wrap the body into - // a `Cursor` to allow for the public interface to support `io::Read`. - let body = Cursor::new(body); - - // The body is saved so that it can be read out from the message. - self.state = MessageState::Reading(Http2Response { - body: body, - }); - - Ok(head) - } - - fn has_body(&self) -> bool { - true - } - - #[inline] - fn set_read_timeout(&self, _dur: Option) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn set_write_timeout(&self, _dur: Option) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn close_connection(&mut self) -> ::Result<()> { - Ok(()) - } -} - -/// A convenience method that creates a default `Http2Protocol` that uses a `net::HttpConnector` -/// (which produces an `HttpStream` for the underlying transport layer). -#[inline] -pub fn new_protocol() -> Http2Protocol { - Http2Protocol::with_connector(HttpConnector) -} - -#[cfg(test)] -mod tests { - use super::{Http2Protocol, prepare_headers, parse_headers, parse_response}; - - use std::io::{Read}; - - use mock::{MockHttp2Connector, MockStream}; - use http::{RequestHead, ResponseHead, Protocol}; - - use header::Headers; - use header; - use url::Url; - use method; - use cookie; - use version; - - use solicit::http::connection::{HttpFrame, ReceiveFrame}; - - /// Tests that the `Http2Message` correctly reads a response with no body. - #[test] - fn test_http2_response_no_body() { - let mut mock_connector = MockHttp2Connector::new(); - mock_connector.new_response_stream(b"200", &Headers::new(), None); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - message.set_outgoing(RequestHead { - headers: Headers::new(), - method: method::Method::Get, - url: Url::parse("http://127.0.0.1/hello").unwrap(), - }).unwrap(); - let resp = message.get_incoming().unwrap(); - - assert_eq!(resp.raw_status.0, 200); - let mut body = Vec::new(); - message.read_to_end(&mut body).unwrap(); - assert_eq!(body.len(), 0); - } - - /// Tests that the `Http2Message` correctly reads a response with a body. - #[test] - fn test_http2_response_with_body() { - let mut mock_connector = MockHttp2Connector::new(); - mock_connector.new_response_stream(b"200", &Headers::new(), Some(vec![1, 2, 3])); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - message.set_outgoing(RequestHead { - headers: Headers::new(), - method: method::Method::Get, - url: Url::parse("http://127.0.0.1/hello").unwrap(), - }).unwrap(); - let resp = message.get_incoming().unwrap(); - - assert_eq!(resp.raw_status.0, 200); - let mut body = Vec::new(); - message.read_to_end(&mut body).unwrap(); - assert_eq!(vec![1, 2, 3], body); - } - - /// Tests that the `Http2Message` correctly reads a response with an empty body. - #[test] - fn test_http2_response_empty_body() { - let mut mock_connector = MockHttp2Connector::new(); - mock_connector.new_response_stream(b"200", &Headers::new(), Some(vec![])); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - message.set_outgoing(RequestHead { - headers: Headers::new(), - method: method::Method::Get, - url: Url::parse("http://127.0.0.1/hello").unwrap(), - }).unwrap(); - let resp = message.get_incoming().unwrap(); - - assert_eq!(resp.raw_status.0, 200); - let mut body = Vec::new(); - message.read_to_end(&mut body).unwrap(); - assert_eq!(Vec::::new(), body); - } - - /// Tests that the `Http2Message` correctly parses out the headers into the `ResponseHead`. - #[test] - fn test_http2_response_headers() { - let mut mock_connector = MockHttp2Connector::new(); - let mut headers = Headers::new(); - headers.set(header::ContentLength(3)); - headers.set(header::ETag(header::EntityTag::new(true, "tag".into()))); - mock_connector.new_response_stream(b"200", &headers, Some(vec![1, 2, 3])); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - message.set_outgoing(RequestHead { - headers: Headers::new(), - method: method::Method::Get, - url: Url::parse("http://127.0.0.1/hello").unwrap(), - }).unwrap(); - let resp = message.get_incoming().unwrap(); - - assert_eq!(resp.raw_status.0, 200); - assert!(resp.headers.has::()); - let &header::ContentLength(len) = resp.headers.get::().unwrap(); - assert_eq!(3, len); - assert!(resp.headers.has::()); - let &header::ETag(ref tag) = resp.headers.get::().unwrap(); - assert_eq!(tag.tag(), "tag"); - } - - /// Tests that an error is returned when the `Http2Message` is not in a readable state. - #[test] - fn test_http2_message_not_readable() { - let mut mock_connector = MockHttp2Connector::new(); - mock_connector.new_response_stream(b"200", &Headers::new(), None); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - - // No outgoing set yet, so nothing can be read at this point. - assert!(message.read(&mut [0; 5]).is_err()); - } - - /// Tests that an error is returned when the `Http2Message` is not in a writable state. - #[test] - fn test_http2_message_not_writable() { - let mut mock_connector = MockHttp2Connector::new(); - mock_connector.new_response_stream(b"200", &Headers::new(), None); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - message.set_outgoing(RequestHead { - headers: Headers::new(), - method: method::Method::Get, - url: Url::parse("http://127.0.0.1/hello").unwrap(), - }).unwrap(); - let _ = message.get_incoming().unwrap(); - // Writes are invalid now - assert!(message.write(&[1]).is_err()); - } - - /// Asserts that the given stream contains the full expected client preface: the preface bytes, - /// settings frame, and settings ack frame. - fn assert_client_preface(server_stream: &mut MockStream) { - // Skip client preface - server_stream.read(&mut [0; 24]).unwrap(); - // The first frame are the settings - assert!(match server_stream.recv_frame().unwrap() { - HttpFrame::SettingsFrame(_) => true, - _ => false, - }); - // Now the ACK to the server's settings. - assert!(match server_stream.recv_frame().unwrap() { - HttpFrame::SettingsFrame(_) => true, - _ => false, - }); - } - - /// Tests that sending a request with no body works correctly. - #[test] - fn test_http2_request_no_body() { - let mut mock_connector = MockHttp2Connector::new(); - let stream = mock_connector.new_response_stream(b"200", &Headers::new(), Some(vec![])); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - message.set_outgoing(RequestHead { - headers: Headers::new(), - method: method::Method::Get, - url: Url::parse("http://127.0.0.1/hello").unwrap(), - }).unwrap(); - let _ = message.get_incoming().unwrap(); - - let stream = stream.inner.lock().unwrap(); - assert!(stream.write.len() > 0); - // The output stream of the client side gets flipped so that we can read the stream from - // the server's end. - let mut server_stream = MockStream::with_input(&stream.write); - assert_client_preface(&mut server_stream); - let frame = server_stream.recv_frame().unwrap(); - assert!(match frame { - HttpFrame::HeadersFrame(ref frame) => frame.is_end_of_stream(), - _ => false, - }); - } - - /// Tests that sending a request with a body works correctly. - #[test] - fn test_http2_request_with_body() { - let mut mock_connector = MockHttp2Connector::new(); - let stream = mock_connector.new_response_stream(b"200", &Headers::new(), None); - let protocol = Http2Protocol::with_connector(mock_connector); - - let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); - message.set_outgoing(RequestHead { - headers: Headers::new(), - method: method::Method::Get, - url: Url::parse("http://127.0.0.1/hello").unwrap(), - }).unwrap(); - // Write a few things to the request in multiple writes. - message.write(&[1]).unwrap(); - message.write(&[2, 3]).unwrap(); - let _ = message.get_incoming().unwrap(); - - let stream = stream.inner.lock().unwrap(); - assert!(stream.write.len() > 0); - // The output stream of the client side gets flipped so that we can read the stream from - // the server's end. - let mut server_stream = MockStream::with_input(&stream.write); - assert_client_preface(&mut server_stream); - let frame = server_stream.recv_frame().unwrap(); - assert!(match frame { - HttpFrame::HeadersFrame(ref frame) => !frame.is_end_of_stream(), - _ => false, - }); - assert!(match server_stream.recv_frame().unwrap() { - HttpFrame::DataFrame(ref frame) => frame.data == vec![1, 2, 3], - _ => false, - }); - } - - /// Tests that headers are correctly prepared when they include a `Set-Cookie` header. - #[test] - fn test_http2_prepare_headers_with_set_cookie() { - let cookies = header::SetCookie(vec![ - cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), - cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) - ]); - let mut headers = Headers::new(); - headers.set(cookies); - - let h2headers = prepare_headers(headers); - - assert_eq!(vec![ - (b"set-cookie".to_vec(), b"foo=bar".to_vec()), - (b"set-cookie".to_vec(), b"baz=quux".to_vec()), - ], h2headers); - } - - /// Tests that headers are correctly prepared when they include a `Cookie` header. - #[test] - fn test_http2_prepapre_headers_with_cookie() { - let cookies = header::Cookie(vec![ - cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), - cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) - ]); - let mut headers = Headers::new(); - headers.set(cookies); - - let h2headers = prepare_headers(headers); - - assert_eq!(vec![ - (b"cookie".to_vec(), b"foo=bar; baz=quux".to_vec()), - ], h2headers); - } - - /// Tests that HTTP/2 headers are correctly prepared. - #[test] - fn test_http2_prepare_headers() { - let mut headers = Headers::new(); - headers.set(header::ContentLength(3)); - let expected = vec![ - (b"content-length".to_vec(), b"3".to_vec()), - ]; - - assert_eq!(expected, prepare_headers(headers)); - } - - /// Tests that the headers of a response are correctly parsed when they include a `Set-Cookie` - /// header. - #[test] - fn test_http2_parse_headers_with_set_cookie() { - let h2headers = vec![ - (b"set-cookie".to_vec(), b"foo=bar".to_vec()), - (b"set-cookie".to_vec(), b"baz=quux".to_vec()), - ]; - let expected = header::SetCookie(vec![ - cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), - cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) - ]); - - let headers = parse_headers(h2headers).unwrap(); - - assert!(headers.has::()); - let set_cookie = headers.get::().unwrap(); - assert_eq!(expected, *set_cookie); - } - - /// Tests that parsing HTTP/2 headers with `Cookie` headers works correctly. - #[test] - fn test_http2_parse_headers_with_cookie() { - let expected = header::Cookie(vec![ - cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), - cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) - ]); - // HTTP/2 allows the `Cookie` header to be split into multiple ones to facilitate better - // compression. - let h2headers = vec![ - (b"cookie".to_vec(), b"foo=bar".to_vec()), - (b"cookie".to_vec(), b"baz=quux".to_vec()), - ]; - - let headers = parse_headers(h2headers).unwrap(); - - assert!(headers.has::()); - assert_eq!(*headers.get::().unwrap(), expected); - } - - /// Tests that the headers of a response are correctly parsed. - #[test] - fn test_http2_parse_headers() { - let h2headers = vec![ - (b":status".to_vec(), b"200".to_vec()), - (b"content-length".to_vec(), b"3".to_vec()), - ]; - - let headers = parse_headers(h2headers).unwrap(); - - assert!(headers.has::()); - let &header::ContentLength(len) = headers.get::().unwrap(); - assert_eq!(3, len); - } - - /// Tests that if a header name is not a valid utf8 byte sequence, an error is returned. - #[test] - fn test_http2_parse_headers_invalid_name() { - let h2headers = vec![ - (vec![0xfe], vec![]), - ]; - - assert!(parse_headers(h2headers).is_err()); - } - - /// Tests that a response with no pseudo-header for status is considered invalid. - #[test] - fn test_http2_parse_response_no_status_code() { - let response = ::solicit::http::Response { - body: Vec::new(), - headers: vec![ - (b"content-length".to_vec(), b"3".to_vec()), - ], - stream_id: 1, - }; - - assert!(parse_response(response).is_err()); - } - - /// Tests that an HTTP/2 response gets correctly parsed into a body and response head, when - /// the body is empty. - #[test] - fn test_http2_parse_response_no_body() { - let response = ::solicit::http::Response { - body: Vec::new(), - headers: vec![ - (b":status".to_vec(), b"200".to_vec()), - (b"content-length".to_vec(), b"0".to_vec()), - ], - stream_id: 1, - }; - - let (head, body) = parse_response(response).unwrap(); - - assert_eq!(body, vec![]); - let ResponseHead { headers, raw_status, version } = head; - assert_eq!(raw_status.0, 200); - assert_eq!(raw_status.1, ""); - assert!(headers.has::()); - assert_eq!(version, version::HttpVersion::Http20); - } - - /// Tests that an HTTP/2 response gets correctly parsed into a body and response head, when - /// the body is not empty. - #[test] - fn test_http2_parse_response_with_body() { - let expected_body = vec![1, 2, 3]; - let response = ::solicit::http::Response { - body: expected_body.clone(), - headers: vec![ - (b":status".to_vec(), b"200".to_vec()), - (b"content-length".to_vec(), b"3".to_vec()), - ], - stream_id: 1, - }; - - let (head, body) = parse_response(response).unwrap(); - - assert_eq!(body, expected_body); - let ResponseHead { headers, raw_status, version } = head; - assert_eq!(raw_status.0, 200); - assert_eq!(raw_status.1, ""); - assert!(headers.has::()); - assert_eq!(version, version::HttpVersion::Http20); - } -} diff --git a/src/http/mod.rs b/src/http/mod.rs index 729bc1a3d3..b4c97ec024 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -11,7 +11,6 @@ use version::HttpVersion::{Http10, Http11}; pub use self::message::{HttpMessage, RequestHead, ResponseHead, Protocol}; pub mod h1; -pub mod h2; pub mod message; /// The raw status code and reason-phrase. diff --git a/src/lib.rs b/src/lib.rs index 0cdbabec1c..90fbedce10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,7 +137,6 @@ extern crate httparse; extern crate num_cpus; extern crate traitobject; extern crate typeable; -extern crate solicit; #[macro_use] extern crate language_tags; diff --git a/src/mock.rs b/src/mock.rs index ac70a5159e..278425c482 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -1,17 +1,8 @@ -use std::ascii::AsciiExt; use std::io::{self, Read, Write, Cursor}; -use std::cell::RefCell; use std::net::{SocketAddr, Shutdown}; -use std::sync::{Arc, Mutex}; use std::time::Duration; use std::cell::Cell; -use solicit::http::HttpScheme; -use solicit::http::transport::TransportStream; -use solicit::http::frame::{SettingsFrame, Frame}; -use solicit::http::connection::{HttpConnection, EndStream, DataChunk}; - -use header::Headers; use net::{NetworkStream, NetworkConnector, SslClient}; #[derive(Clone, Debug)] @@ -110,65 +101,6 @@ impl NetworkStream for MockStream { } } -/// A wrapper around a `MockStream` that allows one to clone it and keep an independent copy to the -/// same underlying stream. -#[derive(Clone)] -pub struct CloneableMockStream { - pub inner: Arc>, -} - -impl Write for CloneableMockStream { - fn write(&mut self, msg: &[u8]) -> io::Result { - self.inner.lock().unwrap().write(msg) - } - - fn flush(&mut self) -> io::Result<()> { - self.inner.lock().unwrap().flush() - } -} - -impl Read for CloneableMockStream { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.inner.lock().unwrap().read(buf) - } -} - -impl TransportStream for CloneableMockStream { - fn try_split(&self) -> Result { - Ok(self.clone()) - } - - fn close(&mut self) -> Result<(), io::Error> { - Ok(()) - } -} - -impl NetworkStream for CloneableMockStream { - fn peer_addr(&mut self) -> io::Result { - self.inner.lock().unwrap().peer_addr() - } - - fn set_read_timeout(&self, dur: Option) -> io::Result<()> { - self.inner.lock().unwrap().set_read_timeout(dur) - } - - fn set_write_timeout(&self, dur: Option) -> io::Result<()> { - self.inner.lock().unwrap().set_write_timeout(dur) - } - - fn close(&mut self, how: Shutdown) -> io::Result<()> { - NetworkStream::close(&mut *self.inner.lock().unwrap(), how) - } -} - -impl CloneableMockStream { - pub fn with_stream(stream: MockStream) -> CloneableMockStream { - CloneableMockStream { - inner: Arc::new(Mutex::new(stream)), - } - } -} - pub struct MockConnector; impl NetworkConnector for MockConnector { @@ -223,99 +155,6 @@ macro_rules! mock_connector ( ); ); -impl TransportStream for MockStream { - fn try_split(&self) -> Result { - Ok(self.clone()) - } - - fn close(&mut self) -> Result<(), io::Error> { - Ok(()) - } -} - -impl MockStream { - /// Creates a new `MockStream` that will return the response described by the parameters as an - /// HTTP/2 response. This will also include the correct server preface. - pub fn new_http2_response(status: &[u8], headers: &Headers, body: Option>) - -> MockStream { - let resp_bytes = build_http2_response(status, headers, body); - MockStream::with_input(&resp_bytes) - } -} - -/// Builds up a sequence of bytes that represent a server's response based on the given parameters. -pub fn build_http2_response(status: &[u8], headers: &Headers, body: Option>) -> Vec { - let mut conn = HttpConnection::new(MockStream::new(), MockStream::new(), HttpScheme::Http); - // Server preface first - conn.sender.write(&SettingsFrame::new().serialize()).unwrap(); - - let mut resp_headers: Vec<_> = headers.iter().map(|h| { - (h.name().to_ascii_lowercase().into_bytes(), h.value_string().into_bytes()) - }).collect(); - resp_headers.insert(0, (b":status".to_vec(), status.into())); - - let end = if body.is_none() { - EndStream::Yes - } else { - EndStream::No - }; - conn.send_headers(resp_headers, 1, end).unwrap(); - if body.is_some() { - let chunk = DataChunk::new_borrowed(&body.as_ref().unwrap()[..], 1, EndStream::Yes); - conn.send_data(chunk).unwrap(); - } - - conn.sender.write -} - -/// A mock connector that produces `MockStream`s that are set to return HTTP/2 responses. -/// -/// This means that the streams' payloads are fairly opaque byte sequences (as HTTP/2 is a binary -/// protocol), which can be understood only be HTTP/2 clients. -pub struct MockHttp2Connector { - /// The list of streams that the connector returns, in the given order. - pub streams: RefCell>, -} - -impl MockHttp2Connector { - /// Creates a new `MockHttp2Connector` with no streams. - pub fn new() -> MockHttp2Connector { - MockHttp2Connector { - streams: RefCell::new(Vec::new()), - } - } - - /// Adds a new `CloneableMockStream` to the end of the connector's stream queue. - /// - /// Streams are returned in a FIFO manner. - pub fn add_stream(&mut self, stream: CloneableMockStream) { - self.streams.borrow_mut().push(stream); - } - - /// Adds a new response stream that will be placed to the end of the connector's stream queue. - /// - /// Returns a separate `CloneableMockStream` that allows the user to inspect what is written - /// into the original stream. - pub fn new_response_stream(&mut self, status: &[u8], headers: &Headers, body: Option>) - -> CloneableMockStream { - let stream = MockStream::new_http2_response(status, headers, body); - let stream = CloneableMockStream::with_stream(stream); - let ret = stream.clone(); - self.add_stream(stream); - - ret - } -} - -impl NetworkConnector for MockHttp2Connector { - type Stream = CloneableMockStream; - #[inline] - fn connect(&self, _host: &str, _port: u16, _scheme: &str) - -> ::Result { - Ok(self.streams.borrow_mut().remove(0)) - } -} - #[derive(Debug, Default)] pub struct MockSsl;