refactor(http): move h1 and h2 into http module
This commit is contained in:
@@ -8,7 +8,7 @@ use std::io;
|
|||||||
|
|
||||||
use hyper::Client;
|
use hyper::Client;
|
||||||
use hyper::header::Connection;
|
use hyper::header::Connection;
|
||||||
use hyper::http2;
|
use hyper::http::h2;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init().unwrap();
|
env_logger::init().unwrap();
|
||||||
@@ -21,7 +21,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut client = Client::with_protocol(http2::new_protocol());
|
let mut client = Client::with_protocol(h2::new_protocol());
|
||||||
|
|
||||||
// `Connection: Close` is not a valid header for HTTP/2, but the client handles it gracefully.
|
// `Connection: Close` is not a valid header for HTTP/2, but the client handles it gracefully.
|
||||||
let mut res = client.get(&*url)
|
let mut res = client.get(&*url)
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ pub mod pool;
|
|||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
use message::Protocol;
|
use http::Protocol;
|
||||||
use http11::Http11Protocol;
|
use http::h1::Http11Protocol;
|
||||||
|
|
||||||
/// A Client to use additional features with Requests.
|
/// A Client to use additional features with Requests.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use net::{NetworkStream, NetworkConnector, HttpConnector, Fresh, Streaming};
|
|||||||
use version;
|
use version;
|
||||||
use client::{Response, get_host_and_port};
|
use client::{Response, get_host_and_port};
|
||||||
|
|
||||||
use message::{HttpMessage, RequestHead};
|
use http::{HttpMessage, RequestHead};
|
||||||
use http11::Http11Message;
|
use http::h1::Http11Message;
|
||||||
|
|
||||||
|
|
||||||
/// A client request to a remote server.
|
/// A client request to a remote server.
|
||||||
@@ -136,7 +136,7 @@ mod tests {
|
|||||||
use header::{ContentLength,TransferEncoding,Encoding};
|
use header::{ContentLength,TransferEncoding,Encoding};
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
use super::Request;
|
use super::Request;
|
||||||
use http11::Http11Message;
|
use http::h1::Http11Message;
|
||||||
|
|
||||||
fn run_request(req: Request<Fresh>) -> Vec<u8> {
|
fn run_request(req: Request<Fresh>) -> Vec<u8> {
|
||||||
let req = req.start().unwrap();
|
let req = req.start().unwrap();
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ use std::io::{self, Read};
|
|||||||
|
|
||||||
use header;
|
use header;
|
||||||
use net::NetworkStream;
|
use net::NetworkStream;
|
||||||
use http::{self, RawStatus};
|
use http::{self, RawStatus, ResponseHead, HttpMessage};
|
||||||
use status;
|
use status;
|
||||||
use version;
|
use version;
|
||||||
use message::{ResponseHead, HttpMessage};
|
use http::h1::Http11Message;
|
||||||
use http11::Http11Message;
|
|
||||||
|
|
||||||
/// A response for a client request to a remote server.
|
/// A response for a client request to a remote server.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -82,7 +81,7 @@ mod tests {
|
|||||||
use mock::MockStream;
|
use mock::MockStream;
|
||||||
use status;
|
use status;
|
||||||
use version;
|
use version;
|
||||||
use http11::Http11Message;
|
use http::h1::Http11Message;
|
||||||
|
|
||||||
use super::Response;
|
use super::Response;
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,318 @@
|
|||||||
//! Pieces pertaining to the HTTP message protocol.
|
//! Adapts the HTTP/1.1 implementation into the `HttpMessage` API.
|
||||||
use std::borrow::{Cow, ToOwned};
|
use std::borrow::Cow;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::io::{self, Read, Write, BufRead};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::io::{self, Write, BufWriter, BufRead, Read};
|
||||||
|
use std::net::Shutdown;
|
||||||
|
|
||||||
use httparse;
|
use httparse;
|
||||||
|
|
||||||
use buffer::BufReader;
|
use buffer::BufReader;
|
||||||
use header::{Headers, Connection};
|
use Error;
|
||||||
use header::ConnectionOption::{Close, KeepAlive};
|
use header::{Headers, ContentLength, TransferEncoding};
|
||||||
use method::Method;
|
use header::Encoding::Chunked;
|
||||||
|
use method::{Method};
|
||||||
|
use net::{NetworkConnector, NetworkStream, ContextVerifier};
|
||||||
use status::StatusCode;
|
use status::StatusCode;
|
||||||
|
use version::HttpVersion;
|
||||||
|
use version::HttpVersion::{Http10, Http11};
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
use version::HttpVersion::{self, Http10, Http11};
|
|
||||||
use {Error};
|
|
||||||
|
|
||||||
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
|
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
|
||||||
use self::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
|
use self::HttpWriter::{ChunkedWriter, SizedWriter, EmptyWriter, ThroughWriter};
|
||||||
|
|
||||||
|
use http::{
|
||||||
|
RawStatus,
|
||||||
|
Protocol,
|
||||||
|
HttpMessage,
|
||||||
|
RequestHead,
|
||||||
|
ResponseHead,
|
||||||
|
};
|
||||||
|
use header;
|
||||||
|
use version;
|
||||||
|
|
||||||
|
/// An implementation of the `HttpMessage` trait for HTTP/1.1.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Http11Message {
|
||||||
|
stream: Option<Box<NetworkStream + Send>>,
|
||||||
|
writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>,
|
||||||
|
reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Http11Message {
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
match self.writer {
|
||||||
|
None => Err(io::Error::new(io::ErrorKind::Other,
|
||||||
|
"Not in a writable state")),
|
||||||
|
Some(ref mut writer) => writer.write(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match self.writer {
|
||||||
|
None => Err(io::Error::new(io::ErrorKind::Other,
|
||||||
|
"Not in a writable state")),
|
||||||
|
Some(ref mut writer) => writer.flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Http11Message {
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self.reader {
|
||||||
|
None => Err(io::Error::new(io::ErrorKind::Other,
|
||||||
|
"Not in a readable state")),
|
||||||
|
Some(ref mut reader) => reader.read(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpMessage for Http11Message {
|
||||||
|
fn set_outgoing(&mut self, mut head: RequestHead) -> ::Result<RequestHead> {
|
||||||
|
if self.stream.is_none() {
|
||||||
|
return Err(From::from(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Message not idle, cannot start new outgoing")));
|
||||||
|
}
|
||||||
|
let mut stream = BufWriter::new(self.stream.take().unwrap());
|
||||||
|
|
||||||
|
let mut uri = head.url.serialize_path().unwrap();
|
||||||
|
if let Some(ref q) = head.url.query {
|
||||||
|
uri.push('?');
|
||||||
|
uri.push_str(&q[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let version = version::HttpVersion::Http11;
|
||||||
|
debug!("request line: {:?} {:?} {:?}", head.method, uri, version);
|
||||||
|
try!(write!(&mut stream, "{} {} {}{}",
|
||||||
|
head.method, uri, version, LINE_ENDING));
|
||||||
|
|
||||||
|
let stream = match head.method {
|
||||||
|
Method::Get | Method::Head => {
|
||||||
|
debug!("headers={:?}", head.headers);
|
||||||
|
try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING));
|
||||||
|
EmptyWriter(stream)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let mut chunked = true;
|
||||||
|
let mut len = 0;
|
||||||
|
|
||||||
|
match head.headers.get::<header::ContentLength>() {
|
||||||
|
Some(cl) => {
|
||||||
|
chunked = false;
|
||||||
|
len = **cl;
|
||||||
|
},
|
||||||
|
None => ()
|
||||||
|
};
|
||||||
|
|
||||||
|
// can't do in match above, thanks borrowck
|
||||||
|
if chunked {
|
||||||
|
let encodings = match head.headers.get_mut::<header::TransferEncoding>() {
|
||||||
|
Some(&mut header::TransferEncoding(ref mut encodings)) => {
|
||||||
|
//TODO: check if chunked is already in encodings. use HashSet?
|
||||||
|
encodings.push(header::Encoding::Chunked);
|
||||||
|
false
|
||||||
|
},
|
||||||
|
None => true
|
||||||
|
};
|
||||||
|
|
||||||
|
if encodings {
|
||||||
|
head.headers.set::<header::TransferEncoding>(
|
||||||
|
header::TransferEncoding(vec![header::Encoding::Chunked]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("headers={:?}", head.headers);
|
||||||
|
try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING));
|
||||||
|
|
||||||
|
if chunked {
|
||||||
|
ChunkedWriter(stream)
|
||||||
|
} else {
|
||||||
|
SizedWriter(stream, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.writer = Some(stream);
|
||||||
|
|
||||||
|
Ok(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_incoming(&mut self) -> ::Result<ResponseHead> {
|
||||||
|
try!(self.flush_outgoing());
|
||||||
|
if self.stream.is_none() {
|
||||||
|
// The message was already in the reading state...
|
||||||
|
// TODO Decide what happens in case we try to get a new incoming at that point
|
||||||
|
return Err(From::from(
|
||||||
|
io::Error::new(io::ErrorKind::Other,
|
||||||
|
"Read already in progress")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stream = self.stream.take().unwrap();
|
||||||
|
let mut stream = BufReader::new(stream);
|
||||||
|
|
||||||
|
let head = try!(parse_response(&mut stream));
|
||||||
|
let raw_status = head.subject;
|
||||||
|
let headers = head.headers;
|
||||||
|
|
||||||
|
let body = if headers.has::<TransferEncoding>() {
|
||||||
|
match headers.get::<TransferEncoding>() {
|
||||||
|
Some(&TransferEncoding(ref codings)) => {
|
||||||
|
if codings.len() > 1 {
|
||||||
|
trace!("TODO: #2 handle other codings: {:?}", codings);
|
||||||
|
};
|
||||||
|
|
||||||
|
if codings.contains(&Chunked) {
|
||||||
|
ChunkedReader(stream, None)
|
||||||
|
} else {
|
||||||
|
trace!("not chuncked. read till eof");
|
||||||
|
EofReader(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => unreachable!()
|
||||||
|
}
|
||||||
|
} else if headers.has::<ContentLength>() {
|
||||||
|
match headers.get::<ContentLength>() {
|
||||||
|
Some(&ContentLength(len)) => SizedReader(stream, len),
|
||||||
|
None => unreachable!()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trace!("neither Transfer-Encoding nor Content-Length");
|
||||||
|
EofReader(stream)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.reader = Some(body);
|
||||||
|
|
||||||
|
Ok(ResponseHead {
|
||||||
|
headers: headers,
|
||||||
|
raw_status: raw_status,
|
||||||
|
version: head.version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_connection(&mut self) -> ::Result<()> {
|
||||||
|
try!(self.get_mut().close(Shutdown::Both));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Http11Message {
|
||||||
|
/// Consumes the `Http11Message` and returns the underlying `NetworkStream`.
|
||||||
|
pub fn into_inner(mut self) -> Box<NetworkStream + Send> {
|
||||||
|
if self.stream.is_some() {
|
||||||
|
self.stream.take().unwrap()
|
||||||
|
} else if self.writer.is_some() {
|
||||||
|
self.writer.take().unwrap().into_inner().into_inner().unwrap()
|
||||||
|
} else if self.reader.is_some() {
|
||||||
|
self.reader.take().unwrap().into_inner().into_inner()
|
||||||
|
} else {
|
||||||
|
panic!("Http11Message lost its underlying stream somehow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the underlying `NetworkStream`, regardless of the state of the
|
||||||
|
/// `Http11Message`.
|
||||||
|
pub fn get_mut(&mut self) -> &mut Box<NetworkStream + Send> {
|
||||||
|
if self.stream.is_some() {
|
||||||
|
self.stream.as_mut().unwrap()
|
||||||
|
} else if self.writer.is_some() {
|
||||||
|
self.writer.as_mut().unwrap().get_mut().get_mut()
|
||||||
|
} else if self.reader.is_some() {
|
||||||
|
self.reader.as_mut().unwrap().get_mut().get_mut()
|
||||||
|
} else {
|
||||||
|
panic!("Http11Message lost its underlying stream somehow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `Http11Message` that will use the given `NetworkStream` for communicating to
|
||||||
|
/// the peer.
|
||||||
|
pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message {
|
||||||
|
Http11Message {
|
||||||
|
stream: Some(stream),
|
||||||
|
writer: None,
|
||||||
|
reader: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flushes the current outgoing content and moves the stream into the `stream` property.
|
||||||
|
///
|
||||||
|
/// TODO It might be sensible to lift this up to the `HttpMessage` trait itself...
|
||||||
|
pub fn flush_outgoing(&mut self) -> ::Result<()> {
|
||||||
|
match self.writer {
|
||||||
|
None => return Ok(()),
|
||||||
|
Some(_) => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let writer = self.writer.take().unwrap();
|
||||||
|
let raw = try!(writer.end()).into_inner().unwrap(); // end() already flushes
|
||||||
|
self.stream = Some(raw);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `Protocol` implementation provides HTTP/1.1 messages.
|
||||||
|
pub struct Http11Protocol {
|
||||||
|
connector: Connector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Protocol for Http11Protocol {
|
||||||
|
fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result<Box<HttpMessage>> {
|
||||||
|
let stream = try!(self.connector.connect(host, port, scheme)).into();
|
||||||
|
|
||||||
|
Ok(Box::new(Http11Message::with_stream(stream)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
|
||||||
|
self.connector.set_ssl_verifier(verifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Http11Protocol {
|
||||||
|
/// Creates a new `Http11Protocol` instance that will use the given `NetworkConnector` for
|
||||||
|
/// establishing HTTP connections.
|
||||||
|
pub fn with_connector<C, S>(c: C) -> Http11Protocol
|
||||||
|
where C: NetworkConnector<Stream=S> + Send + 'static,
|
||||||
|
S: NetworkStream + Send {
|
||||||
|
Http11Protocol {
|
||||||
|
connector: Connector(Box::new(ConnAdapter(c))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConnAdapter<C: NetworkConnector + Send>(C);
|
||||||
|
|
||||||
|
impl<C: NetworkConnector<Stream=S> + Send, S: NetworkStream + Send> NetworkConnector for ConnAdapter<C> {
|
||||||
|
type Stream = Box<NetworkStream + Send>;
|
||||||
|
#[inline]
|
||||||
|
fn connect(&self, host: &str, port: u16, scheme: &str)
|
||||||
|
-> ::Result<Box<NetworkStream + Send>> {
|
||||||
|
Ok(try!(self.0.connect(host, port, scheme)).into())
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
|
||||||
|
self.0.set_ssl_verifier(verifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Connector(Box<NetworkConnector<Stream=Box<NetworkStream + Send>> + Send>);
|
||||||
|
|
||||||
|
impl NetworkConnector for Connector {
|
||||||
|
type Stream = Box<NetworkStream + Send>;
|
||||||
|
#[inline]
|
||||||
|
fn connect(&self, host: &str, port: u16, scheme: &str)
|
||||||
|
-> ::Result<Box<NetworkStream + Send>> {
|
||||||
|
Ok(try!(self.0.connect(host, port, scheme)).into())
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
|
||||||
|
self.0.set_ssl_verifier(verifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Readers to handle different Transfer-Encodings.
|
/// Readers to handle different Transfer-Encodings.
|
||||||
///
|
///
|
||||||
@@ -451,19 +747,6 @@ pub const LF: u8 = b'\n';
|
|||||||
pub const STAR: u8 = b'*';
|
pub const STAR: u8 = b'*';
|
||||||
pub const LINE_ENDING: &'static str = "\r\n";
|
pub const LINE_ENDING: &'static str = "\r\n";
|
||||||
|
|
||||||
/// The raw status code and reason-phrase.
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub struct RawStatus(pub u16, pub Cow<'static, str>);
|
|
||||||
|
|
||||||
/// Checks if a connection should be kept alive.
|
|
||||||
pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool {
|
|
||||||
match (version, headers.get::<Connection>()) {
|
|
||||||
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
|
|
||||||
(Http11, Some(conn)) if conn.contains(&Close) => false,
|
|
||||||
_ => true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
@@ -5,16 +5,16 @@ use std::net::Shutdown;
|
|||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use message::{
|
use http::{
|
||||||
Protocol,
|
Protocol,
|
||||||
HttpMessage,
|
HttpMessage,
|
||||||
RequestHead,
|
RequestHead,
|
||||||
ResponseHead,
|
ResponseHead,
|
||||||
|
RawStatus,
|
||||||
};
|
};
|
||||||
use net::{NetworkStream, NetworkConnector, ContextVerifier};
|
use net::{NetworkStream, NetworkConnector, ContextVerifier};
|
||||||
use net::{HttpConnector, HttpStream};
|
use net::{HttpConnector, HttpStream};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use http::RawStatus;
|
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
|
|
||||||
use header;
|
use header;
|
||||||
@@ -397,7 +397,7 @@ mod tests {
|
|||||||
use std::io::{Read};
|
use std::io::{Read};
|
||||||
|
|
||||||
use mock::{MockHttp2Connector, MockStream};
|
use mock::{MockHttp2Connector, MockStream};
|
||||||
use message::{RequestHead, ResponseHead, Protocol};
|
use http::{RequestHead, ResponseHead, Protocol};
|
||||||
|
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use header;
|
use header;
|
||||||
28
src/http/mod.rs
Normal file
28
src/http/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//! Pieces pertaining to the HTTP message protocol.
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use header::Connection;
|
||||||
|
use header::ConnectionOption::{KeepAlive, Close};
|
||||||
|
use header::Headers;
|
||||||
|
use version::HttpVersion;
|
||||||
|
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.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct RawStatus(pub u16, pub Cow<'static, str>);
|
||||||
|
|
||||||
|
/// Checks if a connection should be kept alive.
|
||||||
|
#[inline]
|
||||||
|
pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool {
|
||||||
|
match (version, headers.get::<Connection>()) {
|
||||||
|
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
|
||||||
|
(Http11, Some(conn)) if conn.contains(&Close) => false,
|
||||||
|
_ => true
|
||||||
|
}
|
||||||
|
}
|
||||||
305
src/http11.rs
305
src/http11.rs
@@ -1,305 +0,0 @@
|
|||||||
//! Adapts the HTTP/1.1 implementation into the `HttpMessage` API.
|
|
||||||
use std::io::{self, Write, BufWriter, Read};
|
|
||||||
use std::net::Shutdown;
|
|
||||||
|
|
||||||
use method::{Method};
|
|
||||||
use header::{ContentLength, TransferEncoding};
|
|
||||||
use header::Encoding::Chunked;
|
|
||||||
|
|
||||||
use net::{NetworkConnector, NetworkStream, ContextVerifier};
|
|
||||||
use http::{HttpWriter, LINE_ENDING};
|
|
||||||
use http::HttpReader::{SizedReader, ChunkedReader, EofReader};
|
|
||||||
use http::HttpWriter::{ChunkedWriter, SizedWriter, EmptyWriter};
|
|
||||||
use buffer::BufReader;
|
|
||||||
use http::{self, HttpReader};
|
|
||||||
|
|
||||||
use message::{
|
|
||||||
Protocol,
|
|
||||||
HttpMessage,
|
|
||||||
RequestHead,
|
|
||||||
ResponseHead,
|
|
||||||
};
|
|
||||||
use header;
|
|
||||||
use version;
|
|
||||||
|
|
||||||
/// An implementation of the `HttpMessage` trait for HTTP/1.1.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Http11Message {
|
|
||||||
stream: Option<Box<NetworkStream + Send>>,
|
|
||||||
writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>,
|
|
||||||
reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for Http11Message {
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
match self.writer {
|
|
||||||
None => Err(io::Error::new(io::ErrorKind::Other,
|
|
||||||
"Not in a writable state")),
|
|
||||||
Some(ref mut writer) => writer.write(buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
match self.writer {
|
|
||||||
None => Err(io::Error::new(io::ErrorKind::Other,
|
|
||||||
"Not in a writable state")),
|
|
||||||
Some(ref mut writer) => writer.flush(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for Http11Message {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
match self.reader {
|
|
||||||
None => Err(io::Error::new(io::ErrorKind::Other,
|
|
||||||
"Not in a readable state")),
|
|
||||||
Some(ref mut reader) => reader.read(buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpMessage for Http11Message {
|
|
||||||
fn set_outgoing(&mut self, mut head: RequestHead) -> ::Result<RequestHead> {
|
|
||||||
if self.stream.is_none() {
|
|
||||||
return Err(From::from(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Message not idle, cannot start new outgoing")));
|
|
||||||
}
|
|
||||||
let mut stream = BufWriter::new(self.stream.take().unwrap());
|
|
||||||
|
|
||||||
let mut uri = head.url.serialize_path().unwrap();
|
|
||||||
if let Some(ref q) = head.url.query {
|
|
||||||
uri.push('?');
|
|
||||||
uri.push_str(&q[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let version = version::HttpVersion::Http11;
|
|
||||||
debug!("request line: {:?} {:?} {:?}", head.method, uri, version);
|
|
||||||
try!(write!(&mut stream, "{} {} {}{}",
|
|
||||||
head.method, uri, version, LINE_ENDING));
|
|
||||||
|
|
||||||
let stream = match head.method {
|
|
||||||
Method::Get | Method::Head => {
|
|
||||||
debug!("headers={:?}", head.headers);
|
|
||||||
try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING));
|
|
||||||
EmptyWriter(stream)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
let mut chunked = true;
|
|
||||||
let mut len = 0;
|
|
||||||
|
|
||||||
match head.headers.get::<header::ContentLength>() {
|
|
||||||
Some(cl) => {
|
|
||||||
chunked = false;
|
|
||||||
len = **cl;
|
|
||||||
},
|
|
||||||
None => ()
|
|
||||||
};
|
|
||||||
|
|
||||||
// can't do in match above, thanks borrowck
|
|
||||||
if chunked {
|
|
||||||
let encodings = match head.headers.get_mut::<header::TransferEncoding>() {
|
|
||||||
Some(&mut header::TransferEncoding(ref mut encodings)) => {
|
|
||||||
//TODO: check if chunked is already in encodings. use HashSet?
|
|
||||||
encodings.push(header::Encoding::Chunked);
|
|
||||||
false
|
|
||||||
},
|
|
||||||
None => true
|
|
||||||
};
|
|
||||||
|
|
||||||
if encodings {
|
|
||||||
head.headers.set::<header::TransferEncoding>(
|
|
||||||
header::TransferEncoding(vec![header::Encoding::Chunked]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("headers={:?}", head.headers);
|
|
||||||
try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING));
|
|
||||||
|
|
||||||
if chunked {
|
|
||||||
ChunkedWriter(stream)
|
|
||||||
} else {
|
|
||||||
SizedWriter(stream, len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.writer = Some(stream);
|
|
||||||
|
|
||||||
Ok(head)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_incoming(&mut self) -> ::Result<ResponseHead> {
|
|
||||||
try!(self.flush_outgoing());
|
|
||||||
if self.stream.is_none() {
|
|
||||||
// The message was already in the reading state...
|
|
||||||
// TODO Decide what happens in case we try to get a new incoming at that point
|
|
||||||
return Err(From::from(
|
|
||||||
io::Error::new(io::ErrorKind::Other,
|
|
||||||
"Read already in progress")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream = self.stream.take().unwrap();
|
|
||||||
let mut stream = BufReader::new(stream);
|
|
||||||
|
|
||||||
let head = try!(http::parse_response(&mut stream));
|
|
||||||
let raw_status = head.subject;
|
|
||||||
let headers = head.headers;
|
|
||||||
|
|
||||||
let body = if headers.has::<TransferEncoding>() {
|
|
||||||
match headers.get::<TransferEncoding>() {
|
|
||||||
Some(&TransferEncoding(ref codings)) => {
|
|
||||||
if codings.len() > 1 {
|
|
||||||
trace!("TODO: #2 handle other codings: {:?}", codings);
|
|
||||||
};
|
|
||||||
|
|
||||||
if codings.contains(&Chunked) {
|
|
||||||
ChunkedReader(stream, None)
|
|
||||||
} else {
|
|
||||||
trace!("not chuncked. read till eof");
|
|
||||||
EofReader(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => unreachable!()
|
|
||||||
}
|
|
||||||
} else if headers.has::<ContentLength>() {
|
|
||||||
match headers.get::<ContentLength>() {
|
|
||||||
Some(&ContentLength(len)) => SizedReader(stream, len),
|
|
||||||
None => unreachable!()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
trace!("neither Transfer-Encoding nor Content-Length");
|
|
||||||
EofReader(stream)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.reader = Some(body);
|
|
||||||
|
|
||||||
Ok(ResponseHead {
|
|
||||||
headers: headers,
|
|
||||||
raw_status: raw_status,
|
|
||||||
version: head.version,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_connection(&mut self) -> ::Result<()> {
|
|
||||||
try!(self.get_mut().close(Shutdown::Both));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Http11Message {
|
|
||||||
/// Consumes the `Http11Message` and returns the underlying `NetworkStream`.
|
|
||||||
pub fn into_inner(mut self) -> Box<NetworkStream + Send> {
|
|
||||||
if self.stream.is_some() {
|
|
||||||
self.stream.take().unwrap()
|
|
||||||
} else if self.writer.is_some() {
|
|
||||||
self.writer.take().unwrap().into_inner().into_inner().unwrap()
|
|
||||||
} else if self.reader.is_some() {
|
|
||||||
self.reader.take().unwrap().into_inner().into_inner()
|
|
||||||
} else {
|
|
||||||
panic!("Http11Message lost its underlying stream somehow");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a mutable reference to the underlying `NetworkStream`, regardless of the state of the
|
|
||||||
/// `Http11Message`.
|
|
||||||
pub fn get_mut(&mut self) -> &mut Box<NetworkStream + Send> {
|
|
||||||
if self.stream.is_some() {
|
|
||||||
self.stream.as_mut().unwrap()
|
|
||||||
} else if self.writer.is_some() {
|
|
||||||
self.writer.as_mut().unwrap().get_mut().get_mut()
|
|
||||||
} else if self.reader.is_some() {
|
|
||||||
self.reader.as_mut().unwrap().get_mut().get_mut()
|
|
||||||
} else {
|
|
||||||
panic!("Http11Message lost its underlying stream somehow");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new `Http11Message` that will use the given `NetworkStream` for communicating to
|
|
||||||
/// the peer.
|
|
||||||
pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message {
|
|
||||||
Http11Message {
|
|
||||||
stream: Some(stream),
|
|
||||||
writer: None,
|
|
||||||
reader: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flushes the current outgoing content and moves the stream into the `stream` property.
|
|
||||||
///
|
|
||||||
/// TODO It might be sensible to lift this up to the `HttpMessage` trait itself...
|
|
||||||
pub fn flush_outgoing(&mut self) -> ::Result<()> {
|
|
||||||
match self.writer {
|
|
||||||
None => return Ok(()),
|
|
||||||
Some(_) => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
let writer = self.writer.take().unwrap();
|
|
||||||
let raw = try!(writer.end()).into_inner().unwrap(); // end() already flushes
|
|
||||||
self.stream = Some(raw);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `Protocol` implementation provides HTTP/1.1 messages.
|
|
||||||
pub struct Http11Protocol {
|
|
||||||
connector: Connector,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Protocol for Http11Protocol {
|
|
||||||
fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result<Box<HttpMessage>> {
|
|
||||||
let stream = try!(self.connector.connect(host, port, scheme)).into();
|
|
||||||
|
|
||||||
Ok(Box::new(Http11Message::with_stream(stream)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
|
|
||||||
self.connector.set_ssl_verifier(verifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Http11Protocol {
|
|
||||||
/// Creates a new `Http11Protocol` instance that will use the given `NetworkConnector` for
|
|
||||||
/// establishing HTTP connections.
|
|
||||||
pub fn with_connector<C, S>(c: C) -> Http11Protocol
|
|
||||||
where C: NetworkConnector<Stream=S> + Send + 'static,
|
|
||||||
S: NetworkStream + Send {
|
|
||||||
Http11Protocol {
|
|
||||||
connector: Connector(Box::new(ConnAdapter(c))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ConnAdapter<C: NetworkConnector + Send>(C);
|
|
||||||
|
|
||||||
impl<C: NetworkConnector<Stream=S> + Send, S: NetworkStream + Send> NetworkConnector for ConnAdapter<C> {
|
|
||||||
type Stream = Box<NetworkStream + Send>;
|
|
||||||
#[inline]
|
|
||||||
fn connect(&self, host: &str, port: u16, scheme: &str)
|
|
||||||
-> ::Result<Box<NetworkStream + Send>> {
|
|
||||||
Ok(try!(self.0.connect(host, port, scheme)).into())
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
|
|
||||||
self.0.set_ssl_verifier(verifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Connector(Box<NetworkConnector<Stream=Box<NetworkStream + Send>> + Send>);
|
|
||||||
|
|
||||||
impl NetworkConnector for Connector {
|
|
||||||
type Stream = Box<NetworkStream + Send>;
|
|
||||||
#[inline]
|
|
||||||
fn connect(&self, host: &str, port: u16, scheme: &str)
|
|
||||||
-> ::Result<Box<NetworkStream + Send>> {
|
|
||||||
Ok(try!(self.0.connect(host, port, scheme)).into())
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
|
|
||||||
self.0.set_ssl_verifier(verifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -186,10 +186,6 @@ pub mod server;
|
|||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod uri;
|
pub mod uri;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
pub mod message;
|
|
||||||
pub mod http11;
|
|
||||||
pub mod http2;
|
|
||||||
|
|
||||||
|
|
||||||
/// Re-exporting the mime crate, for convenience.
|
/// Re-exporting the mime crate, for convenience.
|
||||||
pub mod mime {
|
pub mod mime {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use net::NetworkStream;
|
|||||||
use version::{HttpVersion};
|
use version::{HttpVersion};
|
||||||
use method::Method::{self, Get, Head};
|
use method::Method::{self, Get, Head};
|
||||||
use header::{Headers, ContentLength, TransferEncoding};
|
use header::{Headers, ContentLength, TransferEncoding};
|
||||||
use http::{self, Incoming, HttpReader};
|
use http::h1::{self, Incoming, HttpReader};
|
||||||
use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
|
use http::h1::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
|
|
||||||
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
|
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
|
||||||
@@ -36,7 +36,7 @@ impl<'a, 'b: 'a> Request<'a, 'b> {
|
|||||||
pub fn new(mut stream: &'a mut BufReader<&'b mut NetworkStream>, addr: SocketAddr)
|
pub fn new(mut stream: &'a mut BufReader<&'b mut NetworkStream>, addr: SocketAddr)
|
||||||
-> ::Result<Request<'a, 'b>> {
|
-> ::Result<Request<'a, 'b>> {
|
||||||
|
|
||||||
let Incoming { version, subject: (method, uri), headers } = try!(http::parse_request(stream));
|
let Incoming { version, subject: (method, uri), headers } = try!(h1::parse_request(stream));
|
||||||
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
||||||
debug!("{:?}", headers);
|
debug!("{:?}", headers);
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use std::ptr;
|
|||||||
use time::now_utc;
|
use time::now_utc;
|
||||||
|
|
||||||
use header;
|
use header;
|
||||||
use http::{CR, LF, LINE_ENDING, HttpWriter};
|
use http::h1::{CR, LF, LINE_ENDING, HttpWriter};
|
||||||
use http::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter};
|
use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter};
|
||||||
use status;
|
use status;
|
||||||
use net::{Fresh, Streaming};
|
use net::{Fresh, Streaming};
|
||||||
use version;
|
use version;
|
||||||
|
|||||||
Reference in New Issue
Block a user