solicit/http/
mod.rs

1//! The module implements the framing layer of HTTP/2 and exposes an API for using it.
2use std::io;
3use std::fmt;
4use std::convert::From;
5use std::error::Error;
6
7use hpack::decoder::DecoderError;
8
9pub mod frame;
10pub mod transport;
11pub mod connection;
12pub mod session;
13pub mod priority;
14
15pub mod client;
16pub mod server;
17
18/// An alias for the type that represents the ID of an HTTP/2 stream
19pub type StreamId = u32;
20/// An alias for the type that represents HTTP/2 headers. For now we only alias
21/// the tuple of byte vectors instead of going with a full struct representation.
22pub type Header = (Vec<u8>, Vec<u8>);
23
24/// A set of protocol names that the library should use to indicate that HTTP/2
25/// is supported during protocol negotiation (NPN or ALPN).
26/// We include some of the drafts' protocol names, since there is basically no
27/// difference for all intents and purposes (and some servers out there still
28/// only officially advertise draft support).
29/// TODO: Eventually only use "h2".
30pub const ALPN_PROTOCOLS: &'static [&'static [u8]] = &[
31    b"h2",
32    b"h2-16",
33    b"h2-15",
34    b"h2-14",
35];
36
37/// An enum representing errors that can arise when performing operations
38/// involving an HTTP/2 connection.
39#[derive(Debug)]
40pub enum HttpError {
41    IoError(io::Error),
42    InvalidFrame,
43    CompressionError(DecoderError),
44    UnknownStreamId,
45    UnableToConnect,
46    // TODO This variant should be split into actual reasons for the response being malformed
47    MalformedResponse,
48    Other(Box<Error + Send + Sync>),
49}
50
51/// Implement the trait that allows us to automatically convert `io::Error`s
52/// into an `HttpError` by wrapping the given `io::Error` into an `HttpError::IoError` variant.
53impl From<io::Error> for HttpError {
54    fn from(err: io::Error) -> HttpError {
55        HttpError::IoError(err)
56    }
57}
58
59impl fmt::Display for HttpError {
60    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
61        write!(fmt, "HTTP/2 Error: {}", self.description())
62    }
63}
64
65impl Error for HttpError {
66    fn description(&self) -> &str {
67        match *self {
68            HttpError::IoError(_) => "Encountered an IO error",
69            HttpError::InvalidFrame => "Encountered an invalid HTTP/2 frame",
70            HttpError::CompressionError(_) => "Encountered an error with HPACK compression",
71            HttpError::UnknownStreamId => "Attempted an operation with an unknown HTTP/2 stream ID",
72            HttpError::UnableToConnect => "An error attempting to establish an HTTP/2 connection",
73            HttpError::MalformedResponse => "The received response was malformed",
74            HttpError::Other(_) => "An unknown error",
75        }
76    }
77
78    fn cause(&self) -> Option<&Error> {
79        match *self {
80            HttpError::Other(ref e) => Some(&**e),
81            HttpError::IoError(ref e) => Some(e),
82            _ => None,
83        }
84    }
85}
86
87/// Implementation of the `PartialEq` trait as a convenience for tests.
88#[cfg(test)]
89impl PartialEq for HttpError {
90    fn eq(&self, other: &HttpError) -> bool {
91        match (self, other) {
92            (&HttpError::IoError(ref e1), &HttpError::IoError(ref e2)) => {
93                e1.kind() == e2.kind() && e1.description() == e2.description()
94            },
95            (&HttpError::InvalidFrame, &HttpError::InvalidFrame) => true,
96            (&HttpError::CompressionError(ref e1), &HttpError::CompressionError(ref e2)) => {
97                e1 == e2
98            },
99            (&HttpError::UnknownStreamId, &HttpError::UnknownStreamId) => true,
100            (&HttpError::UnableToConnect, &HttpError::UnableToConnect) => true,
101            (&HttpError::MalformedResponse, &HttpError::MalformedResponse) => true,
102            (&HttpError::Other(ref e1), &HttpError::Other(ref e2)) => {
103                e1.description() == e2.description()
104            },
105            _ => false,
106        }
107    }
108}
109
110/// A convenience `Result` type that has the `HttpError` type as the error
111/// type and a generic Ok result type.
112pub type HttpResult<T> = Result<T, HttpError>;
113
114/// An enum representing the two possible HTTP schemes.
115#[derive(Debug, Copy, Clone, PartialEq)]
116pub enum HttpScheme {
117    /// The variant corresponding to `http://`
118    Http,
119    /// The variant corresponding to `https://`
120    Https,
121}
122
123impl HttpScheme {
124    /// Returns a byte string representing the scheme.
125    #[inline]
126    pub fn as_bytes(&self) -> &'static [u8] {
127        match *self {
128            HttpScheme::Http => b"http",
129            HttpScheme::Https => b"https",
130        }
131    }
132}
133
134/// A struct representing the full raw response received on an HTTP/2 connection.
135///
136/// The full body of the response is included, regardless how large it may be.
137/// The headers contain both the meta-headers, as well as the actual headers.
138#[derive(Clone)]
139pub struct Response {
140    /// The ID of the stream to which the response is associated. HTTP/1.1 does
141    /// not really have an equivalent to this.
142    pub stream_id: StreamId,
143    /// Exposes *all* the raw response headers, including the meta-headers.
144    /// (For now the only meta header allowed in HTTP/2 responses is the
145    /// `:status`.)
146    pub headers: Vec<Header>,
147    /// The full body of the response as an uninterpreted sequence of bytes.
148    pub body: Vec<u8>,
149}
150
151impl Response {
152    /// Creates a new `Response` with all the components already provided.
153    pub fn new(stream_id: StreamId, headers: Vec<Header>, body: Vec<u8>)
154            -> Response {
155        Response {
156            stream_id: stream_id,
157            headers: headers,
158            body: body,
159        }
160    }
161
162    /// Gets the response status code from the pseudo-header. If the response
163    /// does not contain the response as the first pseuo-header, an error is
164    /// returned as such a response is malformed.
165    pub fn status_code(&self) -> HttpResult<u16> {
166        // Since pseudo-headers MUST be found before any regular header fields
167        // and the *only* pseudo-header defined for responses is the `:status`
168        // field, the `:status` MUST be the first header; otherwise, the
169        // response is malformed.
170        if self.headers.len() < 1 {
171            return Err(HttpError::MalformedResponse)
172        }
173        if &self.headers[0].0 != &b":status" {
174            Err(HttpError::MalformedResponse)
175        } else {
176            Ok(try!(Response::parse_status_code(&self.headers[0].1)))
177        }
178    }
179
180    /// A helper function that parses a given buffer as a status code and
181    /// returns it as a `u16`, if it is valid.
182    fn parse_status_code(buf: &[u8]) -> HttpResult<u16> {
183        // "The status-code element is a three-digit integer code [...]"
184        if buf.len() != 3 {
185            return Err(HttpError::MalformedResponse);
186        }
187
188        // "There are five values for the first digit"
189        if buf[0] < b'1' || buf[0] > b'5' {
190            return Err(HttpError::MalformedResponse);
191        }
192
193        // The rest of them just have to be digits
194        if buf[1] < b'0' || buf[1] > b'9' || buf[2] < b'0' || buf[2] > b'9' {
195            return Err(HttpError::MalformedResponse);
196        }
197
198        // Finally, we can merge them into an integer
199        Ok(100 * ((buf[0] - b'0') as u16) +
200           10 * ((buf[1] - b'0') as u16) +
201           1 * ((buf[2] - b'0') as u16))
202    }
203}
204
205/// A struct representing a full HTTP/2 request, along with the full body, as a
206/// sequence of bytes.
207#[derive(Clone)]
208pub struct Request {
209    pub stream_id: u32,
210    pub headers: Vec<Header>,
211    pub body: Vec<u8>,
212}
213
214
215#[cfg(test)]
216pub mod tests;