feat(http2): implement message API for HTTP/2

This commit is contained in:
Marko Lalic
2015-05-28 18:38:34 +02:00
parent 48e9ca2f70
commit f0fe2c5a83
3 changed files with 938 additions and 0 deletions

View File

@@ -1,8 +1,17 @@
use std::fmt;
use std::ascii::AsciiExt;
use std::io::{self, Read, Write, Cursor};
use std::cell::RefCell;
use std::net::SocketAddr;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
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, ContextVerifier};
pub struct MockStream {
@@ -69,6 +78,53 @@ 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<Mutex<MockStream>>,
}
impl Write for CloneableMockStream {
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
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<usize> {
self.inner.lock().unwrap().read(buf)
}
}
impl TransportStream for CloneableMockStream {
fn try_split(&self) -> Result<CloneableMockStream, io::Error> {
Ok(self.clone())
}
fn close(&mut self) -> Result<(), io::Error> {
Ok(())
}
}
impl NetworkStream for CloneableMockStream {
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
self.inner.lock().unwrap().peer_addr()
}
}
impl CloneableMockStream {
pub fn with_stream(stream: MockStream) -> CloneableMockStream {
CloneableMockStream {
inner: Arc::new(Mutex::new(stream)),
}
}
}
pub struct MockConnector;
impl NetworkConnector for MockConnector {
@@ -149,3 +205,100 @@ macro_rules! mock_connector (
)
);
impl TransportStream for MockStream {
fn try_split(&self) -> Result<MockStream, io::Error> {
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<Vec<u8>>)
-> 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<u8>>) -> Vec<u8> {
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<Vec<CloneableMockStream>>,
}
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<Vec<u8>>)
-> 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<CloneableMockStream> {
Ok(self.streams.borrow_mut().remove(0))
}
#[inline]
fn set_ssl_verifier(&mut self, _verifier: ContextVerifier) {
// pass
}
}