Skip to content

Commit 96a701e

Browse files
authored
guides: add 1.0 version of basic client guide (#76)
1 parent 41592d3 commit 96a701e

File tree

3 files changed

+242
-1
lines changed

3 files changed

+242
-1
lines changed

_config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ plugins:
4747

4848
docs_url: https://docs.rs/hyper/1.0.0-rc.1
4949
examples_url: https://github.com/hyperium/hyper/tree/master/examples
50-
futures_url: https://docs.rs/futures/0.3.*
50+
http_body_util_url: https://docs.rs/http-body-util/0.1.0-rc.1
5151
hyper_tls_url: https://docs.rs/hyper-tls/*
5252

53+
futures_url: https://docs.rs/futures/0.3.*
5354
legacy_docs_url: https://docs.rs/hyper/0.14.23
5455
legacy_examples_url: https://github.com/hyperium/hyper/tree/0.14.x/examples

_stable/client/basic.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
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

_stable/client/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: Client Guides
3+
permalink: /1/client/
4+
---
5+
6+
This is just placeholder page. It should probably become a table of
7+
contents for the client guides.
8+

0 commit comments

Comments
 (0)