From a83fa408eb5819758cd48b821dad1706fd80bed0 Mon Sep 17 00:00:00 2001 From: oddgrd <29732646+oddgrd@users.noreply.github.com> Date: Tue, 27 Dec 2022 00:43:22 +0100 Subject: [PATCH 1/4] feat: add 1.0 version of advanced client guide --- _stable/client/advanced.md | 239 +++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 _stable/client/advanced.md diff --git a/_stable/client/advanced.md b/_stable/client/advanced.md new file mode 100644 index 0000000..1cd73f7 --- /dev/null +++ b/_stable/client/advanced.md @@ -0,0 +1,239 @@ +--- +title: Advanced Client Usage +layout: guide +--- + +Once you've done all the setup in the [simple guide][], you probably +have more advanced requests you need to make. In this guide, we'll +make a `POST` request to [http://httpbin.org/post](http://httpbin.org/post), +and make multiple requests at the same time. + +## Making a POST + +Like we did in the getting started guide, we can prepare a request +before giving it to the client by using the `Request::builder` method. +Since we want to post some JSON, and not just simply get a resource, +that's what we'll do. + +We'll reuse the setup code we did in the getting started guide. + +```rust +# extern crate http_body_util; +# extern crate hyper; +use http_body_util::Full; +use hyper::Method; +``` + +After a quick addition to imports, let’s prepare our POST `Request`: + +```rust +# extern crate http_body_util; +# extern crate hyper; +# extern crate tokio; +# use http_body_util::Full; +# use hyper::body::Bytes; +# use hyper::{Method, Request}; +# use tokio::net::TcpStream; +# async fn run() -> Result<(), Box> { +# let url = "http://httpbin.org/ip".parse::()?; +# let host = url.host().expect("uri has no host"); +# let port = url.port_u16().unwrap_or(80); +# let addr = format!("{}:{}", host, port); +# let stream = TcpStream::connect(addr).await?; +# let (mut sender, conn) = hyper::client::conn::http1::handshake(stream).await?; +# tokio::task::spawn(async move { +# if let Err(err) = conn.await { +# println!("Connection failed: {:?}", err); +# } +# }); +# let authority = url.authority().unwrap().clone(); +let req = Request::builder() + .method(Method::POST) + .uri(url) + .header(hyper::header::HOST, authority.as_str()) + .header(hyper::header::CONTENT_TYPE, "application/json") + .body(Full::new(Bytes::from(r#"{"library":"hyper"}"#)))?; +# let mut res = sender.send_request(req).await?; +# Ok(()) +# } +# fn main() {} +``` + +Using the convenient request builder, we set the [`Method`][Method] to `POST`, +added our URL and HOST header like before, and set the `content-type` header to +describe our payload. Lastly, we used the [`Full`][Full] utility to add a +single-chunk body containing our JSON bytes. + +Now, we can give that to the `client` with the `request` method: + + +```rust +# extern crate http_body_util; +# extern crate hyper; +# extern crate tokio; +# use http_body_util::Full; +# use hyper::body::Bytes; +# use hyper::{Method, Request}; +# use tokio::net::TcpStream; +# async fn run() -> Result<(), Box> { +# let url = "http://httpbin.org/ip".parse::()?; +# let host = url.host().expect("uri has no host"); +# let port = url.port_u16().unwrap_or(80); +# let addr = format!("{}:{}", host, port); +# let stream = TcpStream::connect(addr).await?; +# let (mut sender, conn) = hyper::client::conn::http1::handshake(stream).await?; +# tokio::task::spawn(async move { +# if let Err(err) = conn.await { +# println!("Connection failed: {:?}", err); +# } +# }); +# let authority = url.authority().unwrap().clone(); +# let req = Request::builder() +# .method(Method::POST) +# .uri(url) +# .header(hyper::header::HOST, authority.as_str()) +# .header(hyper::header::CONTENT_TYPE, "application/json") +# .body(Full::new(Bytes::from(r#"{"library":"hyper"}"#)))?; +// let req = ... + +// POST it using the SendRequest we set up earlier +let mut res = sender.send_request(req).await?; + +// Print the status +println!("Response status: {}", res.status()); +# Ok(()) +# } +# fn main() {} +``` + +## Multiple Requests + +While `await` allows us to write "asynchronous" code in a way that looks +"synchronous", to take full advantage of it, we can make multiple requests +in parallel instead of serially. + +We're going to take advantage of "joining" futures. + +Now, we'll create some `async` blocks to describe each future, but since they +are lazy, we can start them in parallel. Since we'll need to create a `SendRequest` +for each, lets extract some of our setup code from the getting started guide into a +helper function. + +First lets add a couple of new imports: +```rust +# extern crate http_body_util; +# extern crate hyper; +# extern crate tokio; +use std::convert::Infallible; +use http_body_util::combinators::BoxBody; +use hyper::client::conn::http1::SendRequest; +``` + +```rust +# extern crate http_body_util; +# extern crate hyper; +# extern crate tokio; +# use std::convert::Infallible; +# use hyper::body::Bytes; +# use http_body_util::combinators::BoxBody; +# use tokio::net::TcpStream; +# use hyper::client::conn::http1::SendRequest; +# type Result = std::result::Result>; +async fn prepare_sender(addr: &str) -> Result>> { + let stream = TcpStream::connect(addr).await?; + + let (sender, conn) = hyper::client::conn::http1::handshake(stream).await?; + + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + Ok(sender) +} +# fn main() {} +``` + +We'll simply pass in the address to connect to, the host and port from our URL, and return +a `SendRequest` with a boxed trait object as it's body type, allowing us some freedom in +which type of body we return. We only care that it implements the `HttpBody` trait, that its +data is `Bytes` and since we're only using `Full` and `Empty` we can use `Infallible` for the +error type. + +Now that we have that out of the way, we can create our async blocks and execute them concurrently. + +```rust +# extern crate http_body_util; +# extern crate hyper; +# extern crate tokio; +# use std::convert::Infallible; +# use http_body_util::{BodyExt, Empty, Full}; +# use http_body_util::combinators::BoxBody; +# use hyper::body::Bytes; +# use hyper::{Method, Request}; +# use tokio::net::TcpStream; +# use hyper::client::conn::http1::SendRequest; +# type Result = std::result::Result>; +# async fn prepare_sender(addr: &str) -> Result>> { +# let stream = TcpStream::connect(addr).await?; +# let (sender, conn) = hyper::client::conn::http1::handshake::<_, BoxBody>(stream).await?; +# tokio::task::spawn(async move { +# if let Err(err) = conn.await { +# println!("Connection failed: {:?}", err); +# } +# }); +# Ok(sender) +# } +# async fn run() -> Result<()> { +# let url = "http://httpbin.org/ip".parse::()?; +# let host = url.host().expect("uri has no host"); +# let port = url.port_u16().unwrap_or(80); +# let addr = format!("{}:{}", host, port); +# let mut sender = prepare_sender(&addr).await?; +# let authority = url.authority().unwrap().clone(); +# let req = Request::builder() +# .method(Method::POST) +# .uri(url) +# .header(hyper::header::HOST, authority.as_str()) +# .header(hyper::header::CONTENT_TYPE, "application/json") +# .body(Full::new(Bytes::from(r#"{"library":"hyper"}"#)).boxed())?; +# let mut res = sender.send_request(req).await?; +// We'll use a closure to create a request for each endpoint +let make_request = |url: &str| { + Request::builder() + .uri(url) + .header(hyper::header::HOST, authority.as_str()) + .body(Empty::::new().boxed()) + .unwrap() +}; + +// And another closure for creating our `send_request` futures +let send_request = |req: Request>| async { + let mut sender = prepare_sender(&addr).await?; + let res = sender.send_request(req).await?; + + // Collect the body of the response and return it as Bytes + Ok::<_, Box>(res.collect().await?.to_bytes()) +}; + +// Wait on both of our futures concurrently: +let (ip, headers) = tokio::try_join!( + send_request(make_request("http://httpbin.org/ip")), + send_request(make_request("http://httpbin.org/headers")) +)?; + +// Convert the response bytes to a string slice and print it +println!("Ip: {}", std::str::from_utf8(ip.as_ref()).unwrap()); +println!( + "Headers: {}", + std::str::from_utf8(headers.as_ref()).unwrap() +); +# Ok(()) +# } +# fn main() {} +``` + +[simple guide]: ./basic.md +[Request]: {{ site.legacy_docs_url }}/hyper/struct.Request.html +[Method]: {{ site.legacy_docs_url }}/hyper/struct.Method.html From 1115b7d9a27ea36a9ad9552a52a609cf49b9db0a Mon Sep 17 00:00:00 2001 From: oddgrd <29732646+oddgrd@users.noreply.github.com> Date: Tue, 27 Dec 2022 17:32:49 +0100 Subject: [PATCH 2/4] feat: rephrasings, links and spawn tasks for futs spawn a task for each fut in the multiple requests section, to run them in parallel --- _config.yml | 3 +- _stable/client/advanced.md | 101 ++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/_config.yml b/_config.yml index b87a627..b1fb4a2 100644 --- a/_config.yml +++ b/_config.yml @@ -47,8 +47,9 @@ plugins: docs_url: https://docs.rs/hyper/1.0.0-rc.1 examples_url: https://github.com/hyperium/hyper/tree/master/examples -futures_url: https://docs.rs/futures/0.3.* +http_body_util_url: https://docs.rs/http-body-util/0.1.0-rc.1 hyper_tls_url: https://docs.rs/hyper-tls/* +futures_url: https://docs.rs/futures/0.3.* legacy_docs_url: https://docs.rs/hyper/0.14.23 legacy_examples_url: https://github.com/hyperium/hyper/tree/0.14.x/examples diff --git a/_stable/client/advanced.md b/_stable/client/advanced.md index 1cd73f7..28e80bf 100644 --- a/_stable/client/advanced.md +++ b/_stable/client/advanced.md @@ -6,16 +6,15 @@ layout: guide Once you've done all the setup in the [simple guide][], you probably have more advanced requests you need to make. In this guide, we'll make a `POST` request to [http://httpbin.org/post](http://httpbin.org/post), -and make multiple requests at the same time. +and we'll make multiple requests at the same time. ## Making a POST -Like we did in the getting started guide, we can prepare a request -before giving it to the client by using the `Request::builder` method. -Since we want to post some JSON, and not just simply get a resource, -that's what we'll do. +Like we did in the getting started guide, we can prepare a [`Request`][Request] +before giving it to the client by utilizing the request builder. -We'll reuse the setup code we did in the getting started guide. +We'll reuse the setup code we used in the getting started guide, but we +need to add some imports: ```rust # extern crate http_body_util; @@ -24,8 +23,6 @@ use http_body_util::Full; use hyper::Method; ``` -After a quick addition to imports, let’s prepare our POST `Request`: - ```rust # extern crate http_body_util; # extern crate hyper; @@ -46,7 +43,9 @@ After a quick addition to imports, let’s prepare our POST `Request`: # println!("Connection failed: {:?}", err); # } # }); -# let authority = url.authority().unwrap().clone(); +// We'll get the hostname from the URL like before... +let authority = url.authority().unwrap().clone(); + let req = Request::builder() .method(Method::POST) .uri(url) @@ -59,13 +58,13 @@ let req = Request::builder() # fn main() {} ``` -Using the convenient request builder, we set the [`Method`][Method] to `POST`, -added our URL and HOST header like before, and set the `content-type` header to -describe our payload. Lastly, we used the [`Full`][Full] utility to add a -single-chunk body containing our JSON bytes. - -Now, we can give that to the `client` with the `request` method: +You'll noticed that we now explicitly set the [`Method`][Method], we didn't have +to do that before since the builder defaults to `GET`. In addition to setting the method, +we added our URL and `HOST` header like before, and we set the `content-type` header +to describe our payload. Lastly, we used the [`Full`][Full] utility to construct our +request with a single-chunk body containing our JSON bytes. +Now, we can pass it to the `SendRequest` we set up earlier: ```rust # extern crate http_body_util; @@ -96,10 +95,9 @@ Now, we can give that to the `client` with the `request` method: # .body(Full::new(Bytes::from(r#"{"library":"hyper"}"#)))?; // let req = ... -// POST it using the SendRequest we set up earlier +// POST it using the `SendRequest::send_request` method let mut res = sender.send_request(req).await?; -// Print the status println!("Response status: {}", res.status()); # Ok(()) # } @@ -138,12 +136,16 @@ use hyper::client::conn::http1::SendRequest; # use http_body_util::combinators::BoxBody; # use tokio::net::TcpStream; # use hyper::client::conn::http1::SendRequest; -# type Result = std::result::Result>; -async fn prepare_sender(addr: &str) -> Result>> { +// A simple type alias for errors. +type BoxError = Box; + +async fn prepare_sender(addr: &str) -> Result>, BoxError> { let stream = TcpStream::connect(addr).await?; let (sender, conn) = hyper::client::conn::http1::handshake(stream).await?; + // We have to remember to spawn a task to poll the connection, + // if we don't `SendRequest` will do nothing. tokio::task::spawn(async move { if let Err(err) = conn.await { println!("Connection failed: {:?}", err); @@ -155,27 +157,29 @@ async fn prepare_sender(addr: &str) -> Result = std::result::Result>; -# async fn prepare_sender(addr: &str) -> Result>> { +# type BoxError = Box; +# async fn prepare_sender(addr: &str) -> Result>, BoxError> { # let stream = TcpStream::connect(addr).await?; # let (sender, conn) = hyper::client::conn::http1::handshake::<_, BoxBody>(stream).await?; # tokio::task::spawn(async move { @@ -185,20 +189,12 @@ Now that we have that out of the way, we can create our async blocks and execute # }); # Ok(sender) # } -# async fn run() -> Result<()> { +# async fn run() -> Result<(), BoxError> { # let url = "http://httpbin.org/ip".parse::()?; # let host = url.host().expect("uri has no host"); # let port = url.port_u16().unwrap_or(80); # let addr = format!("{}:{}", host, port); -# let mut sender = prepare_sender(&addr).await?; # let authority = url.authority().unwrap().clone(); -# let req = Request::builder() -# .method(Method::POST) -# .uri(url) -# .header(hyper::header::HOST, authority.as_str()) -# .header(hyper::header::CONTENT_TYPE, "application/json") -# .body(Full::new(Bytes::from(r#"{"library":"hyper"}"#)).boxed())?; -# let mut res = sender.send_request(req).await?; // We'll use a closure to create a request for each endpoint let make_request = |url: &str| { Request::builder() @@ -209,31 +205,32 @@ let make_request = |url: &str| { }; // And another closure for creating our `send_request` futures -let send_request = |req: Request>| async { - let mut sender = prepare_sender(&addr).await?; - let res = sender.send_request(req).await?; +let send_request = |req: Request>| { + let addr = addr.clone(); + + // Spawn a task for our futures to run them in parallel + tokio::spawn(async move { + let mut sender = prepare_sender(&addr.clone()).await?; + let res = sender.send_request(req).await?; - // Collect the body of the response and return it as Bytes - Ok::<_, Box>(res.collect().await?.to_bytes()) + // Collect the body of the response and return it as Bytes + Ok::<_, BoxError>(res.collect().await?.to_bytes()) + }) }; -// Wait on both of our futures concurrently: +// Wait on both of our futures at the same time: let (ip, headers) = tokio::try_join!( send_request(make_request("http://httpbin.org/ip")), send_request(make_request("http://httpbin.org/headers")) )?; - -// Convert the response bytes to a string slice and print it -println!("Ip: {}", std::str::from_utf8(ip.as_ref()).unwrap()); -println!( - "Headers: {}", - std::str::from_utf8(headers.as_ref()).unwrap() -); # Ok(()) # } # fn main() {} ``` [simple guide]: ./basic.md -[Request]: {{ site.legacy_docs_url }}/hyper/struct.Request.html -[Method]: {{ site.legacy_docs_url }}/hyper/struct.Method.html +[SendRequest]: {{ site.docs_url }}/hyper/client/conn/http1/struct.SendRequest.html +[Full]: {{ site.http_body_util_url }}/http_body_util/struct.Full.html +[Empty]: {{ site.http_body_util_url }}/http_body_util/struct.Empty.html +[Request]: {{ site.docs_url }}/hyper/struct.Request.html +[Method]: {{ site.docs_url }}/hyper/struct.Method.html From d0492e9711e1717c20dde78f86dc36305f4d2f1a Mon Sep 17 00:00:00 2001 From: oddgrd <29732646+oddgrd@users.noreply.github.com> Date: Tue, 27 Dec 2022 18:00:55 +0100 Subject: [PATCH 3/4] refactor: rephrasing, Body link --- _stable/client/advanced.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/_stable/client/advanced.md b/_stable/client/advanced.md index 28e80bf..67c87e3 100644 --- a/_stable/client/advanced.md +++ b/_stable/client/advanced.md @@ -159,9 +159,9 @@ async fn prepare_sender(addr: &str) -> Result Date: Tue, 27 Dec 2022 18:02:26 +0100 Subject: [PATCH 4/4] refactor: typo --- _stable/client/advanced.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_stable/client/advanced.md b/_stable/client/advanced.md index 67c87e3..45cac4f 100644 --- a/_stable/client/advanced.md +++ b/_stable/client/advanced.md @@ -158,7 +158,7 @@ async fn prepare_sender(addr: &str) -> Result