Skip to content

Commit 0b2476d

Browse files
committed
Add a somewhat more complex HTTP client.
1 parent 4da851d commit 0b2476d

File tree

1 file changed

+139
-0
lines changed

1 file changed

+139
-0
lines changed

examples/complex_http_client.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use anyhow::{anyhow, Result};
2+
use clap::{ArgAction, Parser};
3+
use std::str::FromStr;
4+
use wstd::http::{
5+
body::BodyForthcoming, Client, HeaderMap, HeaderName, HeaderValue, Method, Request, Uri,
6+
};
7+
8+
/// Complex HTTP client
9+
///
10+
/// A somewhat more complex command-line HTTP client, implemented using
11+
/// `wstd`, using WASI.
12+
#[derive(Parser, Debug)]
13+
#[command(version, about)]
14+
struct Args {
15+
/// The URL to request
16+
url: Uri,
17+
18+
/// Forward stdin to the request body
19+
#[arg(long)]
20+
body: bool,
21+
22+
/// Add a header to the request
23+
#[arg(long = "header", action = ArgAction::Append, value_name = "HEADER")]
24+
headers: Vec<String>,
25+
26+
/// Add a trailer to the request
27+
#[arg(long = "trailer", action = ArgAction::Append, value_name = "TRAILER")]
28+
trailers: Vec<String>,
29+
30+
/// Method of the request
31+
#[arg(long, default_value = "GET")]
32+
method: Method,
33+
34+
/// Set the connect timeout
35+
#[arg(long, value_name = "DURATION")]
36+
connect_timeout: Option<humantime::Duration>,
37+
38+
/// Set the first-byte timeout
39+
#[arg(long, value_name = "DURATION")]
40+
first_byte_timeout: Option<humantime::Duration>,
41+
42+
/// Set the between-bytes timeout
43+
#[arg(long, value_name = "DURATION")]
44+
between_bytes_timeout: Option<humantime::Duration>,
45+
}
46+
47+
#[wstd::main]
48+
async fn main() -> Result<()> {
49+
let args = Args::parse();
50+
51+
// Create and configure the `Client`
52+
53+
let mut client = Client::new();
54+
55+
if let Some(connect_timeout) = args.connect_timeout {
56+
client.set_connect_timeout(*connect_timeout);
57+
}
58+
if let Some(first_byte_timeout) = args.first_byte_timeout {
59+
client.set_first_byte_timeout(*first_byte_timeout);
60+
}
61+
if let Some(between_bytes_timeout) = args.between_bytes_timeout {
62+
client.set_between_bytes_timeout(*between_bytes_timeout);
63+
}
64+
65+
// Create and configure the request.
66+
67+
let mut request = Request::builder();
68+
69+
request = request.uri(args.url).method(args.method);
70+
71+
for header in args.headers {
72+
let mut parts = header.splitn(2, ": ");
73+
let key = parts.next().unwrap();
74+
let value = parts
75+
.next()
76+
.ok_or_else(|| anyhow!("headers must be formatted like \"key: value\""))?;
77+
request = request.header(key, value);
78+
}
79+
let mut trailers = HeaderMap::new();
80+
for trailer in args.trailers {
81+
let mut parts = trailer.splitn(2, ": ");
82+
let key = parts.next().unwrap();
83+
let value = parts
84+
.next()
85+
.ok_or_else(|| anyhow!("trailers must be formatted like \"key: value\""))?;
86+
trailers.insert(HeaderName::from_str(key)?, HeaderValue::from_str(value)?);
87+
}
88+
89+
// Send the request.
90+
91+
let request = request.body(BodyForthcoming)?;
92+
93+
eprintln!("> {} / {:?}", request.method(), request.version());
94+
for (key, value) in request.headers().iter() {
95+
let value = String::from_utf8_lossy(value.as_bytes());
96+
eprintln!("> {key}: {value}");
97+
}
98+
99+
let (mut outgoing_body, response) = client.start_request(request).await?;
100+
101+
if args.body {
102+
wstd::io::copy(wstd::io::stdin(), &mut outgoing_body).await?;
103+
} else {
104+
wstd::io::copy(wstd::io::empty(), &mut outgoing_body).await?;
105+
}
106+
107+
if !trailers.is_empty() {
108+
eprintln!("...");
109+
}
110+
for (key, value) in trailers.iter() {
111+
let value = String::from_utf8_lossy(value.as_bytes());
112+
eprintln!("> {key}: {value}");
113+
}
114+
115+
Client::finish(outgoing_body, Some(trailers))?;
116+
117+
let response = response.get().await?;
118+
119+
// Print the response.
120+
121+
eprintln!("< {:?} {}", response.version(), response.status());
122+
for (key, value) in response.headers().iter() {
123+
let value = String::from_utf8_lossy(value.as_bytes());
124+
eprintln!("< {key}: {value}");
125+
}
126+
127+
let mut body = response.into_body();
128+
wstd::io::copy(&mut body, wstd::io::stdout()).await?;
129+
130+
let trailers = body.finish().await?;
131+
if let Some(trailers) = trailers {
132+
for (key, value) in trailers.iter() {
133+
let value = String::from_utf8_lossy(value.as_bytes());
134+
eprintln!("< {key}: {value}");
135+
}
136+
}
137+
138+
Ok(())
139+
}

0 commit comments

Comments
 (0)