|
| 1 | +--- |
| 2 | +title: Getting Started with a Client |
| 3 | +layout: guide |
| 4 | +--- |
| 5 | + |
| 6 | +To start with, we'll just get a simple `GET` request to a webpage working, |
| 7 | +so we can see all the moving parts. First, we need our dependencies. |
| 8 | +Let's tell Cargo about our dependencies by having this in the Cargo.toml. |
| 9 | + |
| 10 | +## Dependencies |
| 11 | + |
| 12 | +```toml |
| 13 | +[dependencies] |
| 14 | +hyper = { version = "1.0.0-rc.1", features = ["full"] } |
| 15 | +tokio = { version = "1", features = ["full"] } |
| 16 | +http-body-util = "0.1.0-rc.1" |
| 17 | +``` |
| 18 | + |
| 19 | +Now, we need to import pieces to use from our dependencies: |
| 20 | + |
| 21 | +```rust |
| 22 | +# extern crate http_body_util; |
| 23 | +# extern crate hyper; |
| 24 | +# extern crate tokio; |
| 25 | +use http_body_util::Empty; |
| 26 | +use hyper::Request; |
| 27 | +use hyper::body::Bytes; |
| 28 | +use tokio::net::TcpStream; |
| 29 | +# fn main() {} |
| 30 | +``` |
| 31 | + |
| 32 | +## Runtime |
| 33 | + |
| 34 | +Now, we'll make a request in the `main` of our program. This may seem |
| 35 | +like a bit of work just to make a simple request, and you'd be correct, |
| 36 | +but the point here is just to show all the setup required. Once you have this, |
| 37 | +you are set to make thousands of client requests efficiently. |
| 38 | + |
| 39 | +We have to setup some sort of runtime. You can use whichever async runtime you'd |
| 40 | +like, but for this guide we're going to use tokio. If you've never used futures |
| 41 | +in Rust before, you may wish to read through [Tokio's guide on Futures][Tokio-Futures]. |
| 42 | + |
| 43 | +```rust |
| 44 | +# extern crate tokio; |
| 45 | +# mod no_run { |
| 46 | +#[tokio::main] |
| 47 | +async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 48 | + // This is where we will setup our HTTP client requests. |
| 49 | + |
| 50 | + Ok(()) |
| 51 | +} |
| 52 | +# } |
| 53 | +# fn main() {} |
| 54 | +``` |
| 55 | + |
| 56 | +## Setup |
| 57 | + |
| 58 | +To get started we'll need to get a few things setup. For this guide we're |
| 59 | +going to send a GET [`Request`][Request] to [http://httpbin.org/ip](http://httpbin.org/ip), |
| 60 | +which will return a `200 OK` and the Requester's IP address in the body. |
| 61 | + |
| 62 | +We need to open a TCP connection to the remote host using a hostname and port, |
| 63 | +which in this case is `httpbin.org` and the default port for HTTP: `80`. With our |
| 64 | +connection opened, we pass it in to the `client::conn::http1::handshake` function, |
| 65 | +performing a handshake to verify the remote is ready to receive our requests. |
| 66 | + |
| 67 | +A successful handshake will give us a [Connection][Connection] future that processes |
| 68 | +all HTTP state, and a [SendRequest][SendRequest] struct that we can use to send our |
| 69 | +`Request`s on the connection. |
| 70 | + |
| 71 | +To start driving the HTTP state we have to poll the `Connection`, so to finish our |
| 72 | +setup we'll spawn a `tokio::task` and `await` it. |
| 73 | + |
| 74 | +```rust |
| 75 | +# extern crate http_body_util; |
| 76 | +# extern crate hyper; |
| 77 | +# extern crate tokio; |
| 78 | +# use http_body_util::Empty; |
| 79 | +# use hyper::body::Bytes; |
| 80 | +# use hyper::Request; |
| 81 | +# use tokio::net::TcpStream; |
| 82 | +# async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 83 | +// Parse our URL... |
| 84 | +let url = "http://httpbin.org/ip".parse::<hyper::Uri>()?; |
| 85 | + |
| 86 | +// Get the host and the port |
| 87 | +let host = url.host().expect("uri has no host"); |
| 88 | +let port = url.port_u16().unwrap_or(80); |
| 89 | + |
| 90 | +let address = format!("{}:{}", host, port); |
| 91 | + |
| 92 | +// Open a TCP connection to the remote host |
| 93 | +let stream = TcpStream::connect(address).await?; |
| 94 | + |
| 95 | +// Perform a TCP handshake |
| 96 | +let (mut sender, conn) = hyper::client::conn::http1::handshake(stream).await?; |
| 97 | + |
| 98 | +// Spawn a task to poll the connection, driving the HTTP state |
| 99 | +tokio::task::spawn(async move { |
| 100 | + if let Err(err) = conn.await { |
| 101 | + println!("Connection failed: {:?}", err); |
| 102 | + } |
| 103 | +}); |
| 104 | +# let authority = url.authority().unwrap().clone(); |
| 105 | +# let req = Request::builder() |
| 106 | +# .uri(url) |
| 107 | +# .header(hyper::header::HOST, authority.as_str()) |
| 108 | +# .body(Empty::<Bytes>::new())?; |
| 109 | +# let mut res = sender.send_request(req).await?; |
| 110 | +# Ok(()) |
| 111 | +# } |
| 112 | +# fn main() {} |
| 113 | +``` |
| 114 | + |
| 115 | +## GET |
| 116 | + |
| 117 | +Now that we've set up our connection, we're ready to construct and send our first `Request`! |
| 118 | +Since `SendRequest` doesn't require absolute-form `URI`s we are required to include a `HOST` |
| 119 | +header in our requests. And while we can send our `Request` with an empty `Body`, we need to |
| 120 | +explicitly set it, which we'll do with the [`Empty`][Empty] utility struct. |
| 121 | + |
| 122 | +All we need to do now is pass the `Request` to `SendRequest::send_request`, this returns a |
| 123 | +future which will resolve to the [`Response`][Response] from `httpbin.org`. We'll print the |
| 124 | +status of the response to see that it returned the expected `200 OK` status. |
| 125 | + |
| 126 | +```rust |
| 127 | +# extern crate http_body_util; |
| 128 | +# extern crate hyper; |
| 129 | +# extern crate tokio; |
| 130 | +# use http_body_util::Empty; |
| 131 | +# use hyper::body::Bytes; |
| 132 | +# use hyper::Request; |
| 133 | +# use tokio::net::TcpStream; |
| 134 | +# async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 135 | +# let url = "http://httpbin.org/ip".parse::<hyper::Uri>()?; |
| 136 | +# let host = url.host().expect("uri has no host"); |
| 137 | +# let port = url.port_u16().unwrap_or(80); |
| 138 | +# let addr = format!("{}:{}", host, port); |
| 139 | +# let stream = TcpStream::connect(addr).await?; |
| 140 | +# let (mut sender, conn) = hyper::client::conn::http1::handshake(stream).await?; |
| 141 | +# tokio::task::spawn(async move { |
| 142 | +# if let Err(err) = conn.await { |
| 143 | +# println!("Connection failed: {:?}", err); |
| 144 | +# } |
| 145 | +# }); |
| 146 | +// The authority of our URL will be the hostname of the httpbin remote |
| 147 | +let authority = url.authority().unwrap().clone(); |
| 148 | + |
| 149 | +// Create an HTTP request with an empty body and a HOST header |
| 150 | +let req = Request::builder() |
| 151 | + .uri(url) |
| 152 | + .header(hyper::header::HOST, authority.as_str()) |
| 153 | + .body(Empty::<Bytes>::new())?; |
| 154 | + |
| 155 | +// Await the response... |
| 156 | +let mut res = sender.send_request(req).await?; |
| 157 | + |
| 158 | +println!("Response status: {}", res.status()); |
| 159 | +# Ok(()) |
| 160 | +# } |
| 161 | +# fn main() {} |
| 162 | +``` |
| 163 | + |
| 164 | +## Response bodies |
| 165 | + |
| 166 | +We know that sending a GET `Request` to `httpbin.org/ip` will return our IP address in |
| 167 | +the `Response` body. To see the returned body, we'll simply write it to `stdout`. |
| 168 | + |
| 169 | +Bodies in hyper are asynchronous streams of [`Frame`][Frame]s, so we don't have to wait for the |
| 170 | +whole body to arrive, buffering it into memory, and then writing it out. We can simply |
| 171 | +`await` each `Frame` and write them directly to `stdout` as they arrive! |
| 172 | + |
| 173 | +In addition to importing `stdout`, we'll need to make use of the `BodyExt` trait: |
| 174 | + |
| 175 | +```rust |
| 176 | +# extern crate http_body_util; |
| 177 | +# extern crate tokio; |
| 178 | +use http_body_util::BodyExt; |
| 179 | +use tokio::io::{stdout, AsyncWriteExt as _}; |
| 180 | +``` |
| 181 | + |
| 182 | +```rust |
| 183 | +# extern crate http_body_util; |
| 184 | +# extern crate hyper; |
| 185 | +# extern crate tokio; |
| 186 | +# use http_body_util::{BodyExt, Empty}; |
| 187 | +# use hyper::body::Bytes; |
| 188 | +# use hyper::Request; |
| 189 | +# use tokio::net::TcpStream; |
| 190 | +# use tokio::io::{self, AsyncWriteExt as _}; |
| 191 | +# async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 192 | +# let url = "http://httpbin.org/ip".parse::<hyper::Uri>()?; |
| 193 | +# let host = url.host().expect("uri has no host"); |
| 194 | +# let port = url.port_u16().unwrap_or(80); |
| 195 | +# let addr = format!("{}:{}", host, port); |
| 196 | +# let stream = TcpStream::connect(addr).await?; |
| 197 | +# let (mut sender, conn) = hyper::client::conn::http1::handshake(stream).await?; |
| 198 | +# tokio::task::spawn(async move { |
| 199 | +# if let Err(err) = conn.await { |
| 200 | +# println!("Connection failed: {:?}", err); |
| 201 | +# } |
| 202 | +# }); |
| 203 | +# let authority = url.authority().unwrap().clone(); |
| 204 | +# let req = Request::builder() |
| 205 | +# .uri(url) |
| 206 | +# .header(hyper::header::HOST, authority.as_str()) |
| 207 | +# .body(Empty::<Bytes>::new())?; |
| 208 | +# let mut res = sender.send_request(req).await?; |
| 209 | +// Stream the body, writing each frame to stdout as it arrives |
| 210 | +while let Some(next) = res.frame().await { |
| 211 | + let frame = next?; |
| 212 | + if let Some(chunk) = frame.data_ref() { |
| 213 | + io::stdout().write_all(&chunk).await?; |
| 214 | + } |
| 215 | +} |
| 216 | +# Ok(()) |
| 217 | +# } |
| 218 | +# fn main() {} |
| 219 | +``` |
| 220 | +And that's it! You can see the [full example here][example]. |
| 221 | + |
| 222 | +[Tokio]: https://tokio.rs |
| 223 | +[Tokio-Futures]: https://tokio.rs/tokio/tutorial/async |
| 224 | +[StatusCode]: {{ site.docs_url }}/hyper/struct.StatusCode.html |
| 225 | +[Response]: {{ site.docs_url }}/hyper/struct.Response.html |
| 226 | +[Request]: {{ site.docs_url }}/hyper/struct.Request.html |
| 227 | +[Connection]: {{ site.docs_url }}/hyper/client/conn/http1/struct.Connection.html |
| 228 | +[SendRequest]: {{ site.docs_url }}/hyper/client/conn/http1/struct.SendRequest.html |
| 229 | +[Frame]: {{ site.docs_url }}/hyper/body/struct.Frame.html |
| 230 | +[Empty]: {{ site.http_body_util_url }}/http_body_util/struct.Empty.html |
| 231 | + |
| 232 | +[example]: {{ site.examples_url }}/client.rs |
0 commit comments