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}