forked from bytecodealliance/wstd
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.rs
More file actions
122 lines (108 loc) · 3.98 KB
/
client.rs
File metadata and controls
122 lines (108 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use super::{Body, Error, Request, Response};
use crate::http::request::try_into_outgoing;
use crate::http::response::try_from_incoming;
use crate::io::AsyncPollable;
use crate::time::Duration;
use wasip2::http::types::RequestOptions as WasiRequestOptions;
/// An HTTP client.
#[derive(Debug, Clone)]
pub struct Client {
options: Option<RequestOptions>,
}
impl Default for Client {
fn default() -> Self {
Self::new()
}
}
impl Client {
/// Create a new instance of `Client`
pub fn new() -> Self {
Self { options: None }
}
/// Send an HTTP request.
pub async fn send<B: Into<Body>>(&self, req: Request<B>) -> Result<Response<Body>, Error> {
let (wasi_req, body) = try_into_outgoing(req)?;
let body = body.into();
let wasi_body = wasi_req.body().unwrap();
// 1. Start sending the request head
let res = wasip2::http::outgoing_handler::handle(wasi_req, self.wasi_options()?)?;
let ((), body) = futures_lite::future::try_zip(
async move {
// 3. send the body:
body.send(wasi_body).await
},
async move {
// 4. Receive the response
AsyncPollable::new(res.subscribe()).wait_for().await;
// NOTE: the first `unwrap` is to ensure readiness, the second `unwrap`
// is to trap if we try and get the response more than once. The final
// `?` is to raise the actual error if there is one.
let res = res.get().unwrap().unwrap()?;
try_from_incoming(res)
},
)
.await?;
Ok(body)
}
/// Set timeout on connecting to HTTP server
pub fn set_connect_timeout(&mut self, d: impl Into<Duration>) {
self.options_mut().connect_timeout = Some(d.into());
}
/// Set timeout on recieving first byte of the Response body
pub fn set_first_byte_timeout(&mut self, d: impl Into<Duration>) {
self.options_mut().first_byte_timeout = Some(d.into());
}
/// Set timeout on recieving subsequent chunks of bytes in the Response body stream
pub fn set_between_bytes_timeout(&mut self, d: impl Into<Duration>) {
self.options_mut().between_bytes_timeout = Some(d.into());
}
fn options_mut(&mut self) -> &mut RequestOptions {
match &mut self.options {
Some(o) => o,
uninit => {
*uninit = Some(RequestOptions::default());
uninit.as_mut().unwrap()
}
}
}
fn wasi_options(&self) -> Result<Option<WasiRequestOptions>, crate::http::Error> {
self.options
.as_ref()
.map(RequestOptions::to_wasi)
.transpose()
}
}
#[derive(Default, Debug, Clone)]
struct RequestOptions {
connect_timeout: Option<Duration>,
first_byte_timeout: Option<Duration>,
between_bytes_timeout: Option<Duration>,
}
impl RequestOptions {
fn to_wasi(&self) -> Result<WasiRequestOptions, crate::http::Error> {
let wasi = WasiRequestOptions::new();
if let Some(timeout) = self.connect_timeout {
wasi.set_connect_timeout(Some(timeout.0)).map_err(|()| {
anyhow::Error::msg(
"wasi-http implementation does not support connect timeout option",
)
})?;
}
if let Some(timeout) = self.first_byte_timeout {
wasi.set_first_byte_timeout(Some(timeout.0)).map_err(|()| {
anyhow::Error::msg(
"wasi-http implementation does not support first byte timeout option",
)
})?;
}
if let Some(timeout) = self.between_bytes_timeout {
wasi.set_between_bytes_timeout(Some(timeout.0))
.map_err(|()| {
anyhow::Error::msg(
"wasi-http implementation does not support between byte timeout option",
)
})?;
}
Ok(wasi)
}
}