solicit/client/
simple.rs

1//! The module contains an implementation of a simple HTTP/2 client.
2
3use http::{StreamId, HttpResult, HttpError, Response, Header};
4use http::transport::TransportStream;
5use http::connection::{HttpConnection, SendStatus};
6use http::session::{SessionState, DefaultSessionState, DefaultStream, Stream};
7use http::client::{ClientConnection, HttpConnect, RequestStream};
8
9/// A struct implementing a simple HTTP/2 client.
10///
11/// This client works as an HTTP/1.1 client with a Keep-Alive connection and
12/// pipelining might work.
13///
14/// Multiple requests can be queued up (and sent to the server) by calling
15/// `request` multiple times, before any `get_response`.
16///
17/// Once a `get_response` is issued, the client blocks until it receives the
18/// response for the particular request that was referenced in the `get_response`
19/// call.
20///
21/// Therefore, by doing `request` -> `get_response` we can use the HTTP/2
22/// connection as a `Keep-Alive` HTTP/1.1 connection and a pipelined flow by
23/// queuing up a sequence of requests and then "joining" over them by calling
24/// `get_response` for each of them.
25///
26/// The responses that are returned by the client are very raw representations
27/// of the response.
28///
29/// # Examples
30///
31/// Issue a simple GET request using the helper `get` method. Premade connection
32/// passed to the client.
33///
34/// ```no_run
35/// use std::net::TcpStream;
36/// use solicit::http::HttpScheme;
37/// use solicit::http::connection::HttpConnection;
38/// use solicit::http::client::write_preface;
39/// use solicit::client::SimpleClient;
40/// use std::str;
41///
42/// // Prepare a stream manually... We must write the preface ourselves in this case.
43/// // This is a more advanced way to use the client and the `HttpConnect` implementations
44/// // should usually be preferred for their convenience.
45/// let mut stream = TcpStream::connect(&("http2bin.org", 80)).unwrap();
46/// write_preface(&mut stream);
47/// // Connect to an HTTP/2 aware server
48/// let conn = HttpConnection::<TcpStream, TcpStream>::with_stream(
49///                                stream,
50///                                HttpScheme::Http);
51/// let mut client = SimpleClient::with_connection(conn, "http2bin.org".into()).unwrap();
52/// let response = client.get(b"/", &[]).unwrap();
53/// assert_eq!(response.stream_id, 1);
54/// assert_eq!(response.status_code().unwrap(), 200);
55/// // Dump the headers and the response body to stdout.
56/// // They are returned as raw bytes for the user to do as they please.
57/// // (Note: in general directly decoding assuming a utf8 encoding might not
58/// // always work -- this is meant as a simple example that shows that the
59/// // response is well formed.)
60/// for header in response.headers.iter() {
61///     println!("{}: {}",
62///         str::from_utf8(&header.0).unwrap(),
63///         str::from_utf8(&header.1).unwrap());
64/// }
65/// println!("{}", str::from_utf8(&response.body).unwrap());
66/// ```
67///
68/// Issue a simple GET request using the helper `get` method. Pass a connector
69/// to establish a new connection.
70///
71/// ```no_run
72/// use solicit::http::client::CleartextConnector;
73/// use solicit::client::SimpleClient;
74/// use std::str;
75///
76/// // Connect to an HTTP/2 aware server
77/// let connector = CleartextConnector::new("http2bin.org");
78/// let mut client = SimpleClient::with_connector(connector).unwrap();
79/// let response = client.get(b"/", &[]).unwrap();
80/// assert_eq!(response.stream_id, 1);
81/// assert_eq!(response.status_code().unwrap(), 200);
82/// // Dump the headers and the response body to stdout.
83/// // They are returned as raw bytes for the user to do as they please.
84/// // (Note: in general directly decoding assuming a utf8 encoding might not
85/// // always work -- this is meant as a simple example that shows that the
86/// // response is well formed.)
87/// for header in response.headers.iter() {
88///     println!("{}: {}",
89///         str::from_utf8(&header.0).unwrap(),
90///         str::from_utf8(&header.1).unwrap());
91/// }
92/// println!("{}", str::from_utf8(&response.body).unwrap());
93/// ```
94pub struct SimpleClient<S> where S: TransportStream {
95    /// The underlying `ClientConnection` that the client uses
96    conn: ClientConnection<S, S>,
97    /// Holds the ID that can be assigned to the next stream to be opened by the
98    /// client.
99    next_stream_id: u32,
100    /// The name of the host to which the client is connected to.
101    host: Vec<u8>,
102}
103
104impl<S> SimpleClient<S> where S: TransportStream {
105    /// Create a new `SimpleClient` instance that will use the given `HttpConnection`
106    /// to communicate to the server.
107    ///
108    /// It assumes that the connection stream is initialized and will *not* automatically write the
109    /// client preface.
110    pub fn with_connection(conn: HttpConnection<S, S>, host: String)
111            -> HttpResult<SimpleClient<S>> {
112        let mut client = SimpleClient {
113            conn: ClientConnection::with_connection(conn, DefaultSessionState::new()),
114            next_stream_id: 1,
115            host: host.as_bytes().to_vec(),
116        };
117
118        try!(client.init());
119
120        Ok(client)
121    }
122
123    /// A convenience constructor that first tries to establish an HTTP/2
124    /// connection by using the given connector instance (an implementation of
125    /// the `HttpConnect` trait).
126    ///
127    /// # Panics
128    ///
129    /// Currently, it panics if the connector returns an error.
130    pub fn with_connector<C>(connector: C) -> HttpResult<SimpleClient<S>>
131            where C: HttpConnect<Stream=S> {
132        let stream = try!(connector.connect());
133        let conn = HttpConnection::<S, S>::with_stream(stream.0, stream.1);
134        SimpleClient::with_connection(conn, stream.2)
135    }
136
137    /// Internal helper method that performs the initialization of the client's
138    /// connection.
139    #[inline]
140    fn init(&mut self) -> HttpResult<()> {
141        self.conn.init()
142    }
143
144    /// Send a request to the server. Blocks until the entire request has been
145    /// sent.
146    ///
147    /// The request is described by the method, the path on which it should be
148    /// invoked and the "real" headers that should be included. Clients should
149    /// never put pseudo-headers in the `headers` parameter, as those are
150    /// automatically included based on metadata.
151    ///
152    /// # Returns
153    ///
154    /// If the full request is successfully sent, returns the ID of the stream
155    /// on which the request was sent. Clients can use this ID to refer to the
156    /// response.
157    ///
158    /// Any IO errors are propagated.
159    pub fn request(&mut self, method: &[u8], path: &[u8], extras: &[Header], body: Option<Vec<u8>>)
160            -> HttpResult<StreamId> {
161        // Prepares the request stream
162        let stream = self.new_stream(method, path, extras, body);
163        // Remember the stream's ID before passing on the ownership to the connection
164        let stream_id = stream.stream.id();
165        // Starts the request (i.e. sends out the headers)
166        try!(self.conn.start_request(stream));
167
168        // And now makes sure the data is sent out...
169        // Note: Since for now there is no flow control, sending data will always continue
170        //       progressing, but it might violate flow control windows, causing the peer to shut
171        //       down the connection.
172        debug!("Trying to send the body");
173        while let SendStatus::Sent = try!(self.conn.send_next_data()) {
174            // We iterate until the data is sent, as the contract of this call is that it blocks
175            // until such a time.
176        }
177
178        Ok(stream_id)
179    }
180
181    /// Gets the response for the stream with the given ID. If a valid stream ID
182    /// is given, it blocks until a response is received.
183    ///
184    /// # Returns
185    ///
186    /// A `Response` if the response can be successfully read.
187    ///
188    /// Any underlying IO errors are propagated. Errors in the HTTP/2 protocol
189    /// also stop processing and are returned to the client.
190    pub fn get_response(&mut self, stream_id: StreamId) -> HttpResult<Response> {
191        match self.conn.state.get_stream_ref(stream_id) {
192            None => return Err(HttpError::UnknownStreamId),
193            Some(_) => {},
194        };
195        loop {
196            if let Some(stream) = self.conn.state.get_stream_ref(stream_id) {
197                if stream.is_closed() {
198                    return Ok(Response {
199                        stream_id: stream.id(),
200                        headers: stream.headers.clone().unwrap(),
201                        body: stream.body.clone(),
202                    });
203                }
204            }
205            try!(self.handle_next_frame());
206        }
207    }
208
209    /// Performs a GET request on the given path. This is a shortcut method for
210    /// calling `request` followed by `get_response` for the returned stream ID.
211    pub fn get(&mut self, path: &[u8], extra_headers: &[Header])
212            -> HttpResult<Response> {
213        let stream_id = try!(self.request(b"GET", path, extra_headers, None));
214        self.get_response(stream_id)
215    }
216
217    /// Performs a POST request on the given path.
218    pub fn post(&mut self, path: &[u8], extra_headers: &[Header], body: Vec<u8>)
219            -> HttpResult<Response> {
220        let stream_id = try!(self.request(b"POST", path, extra_headers, Some(body)));
221        self.get_response(stream_id)
222    }
223
224    /// Internal helper method that prepares a new `RequestStream` instance based on the given
225    /// request parameters.
226    ///
227    /// The `RequestStream` is then ready to be passed on to the connection instance in order to
228    /// start the request.
229    fn new_stream(&mut self, method: &[u8], path: &[u8], extras: &[Header], body: Option<Vec<u8>>)
230            -> RequestStream<DefaultStream> {
231        let stream_id = self.get_next_stream_id();
232        let mut stream = DefaultStream::new(stream_id);
233        match body {
234            Some(body) => stream.set_full_data(body),
235            None => stream.close_local(),
236        };
237
238        let mut headers: Vec<Header> = vec![
239            (b":method".to_vec(), method.to_vec()),
240            (b":path".to_vec(), path.to_vec()),
241            (b":authority".to_vec(), self.host.clone()),
242            (b":scheme".to_vec(), self.conn.scheme().as_bytes().to_vec()),
243        ];
244        headers.extend(extras.to_vec().into_iter());
245
246        RequestStream {
247            headers: headers,
248            stream: stream,
249        }
250    }
251
252    /// Internal helper method that gets the next valid stream ID number.
253    fn get_next_stream_id(&mut self) -> StreamId {
254        let ret = self.next_stream_id;
255        self.next_stream_id += 2;
256
257        ret
258    }
259
260    /// Internal helper method that triggers the client to handle the next
261    /// frame off the HTTP/2 connection.
262    #[inline]
263    fn handle_next_frame(&mut self) -> HttpResult<()> {
264        self.conn.handle_next_frame()
265    }
266}