Skip to content
This repository was archived by the owner on Jun 7, 2024. It is now read-only.

Commit 0a76f18

Browse files
authored
Merge pull request #8 from wacker-dev/response
Refactor body-related method of the Response
2 parents ecaaeee + fbce21d commit 0a76f18

File tree

2 files changed

+53
-32
lines changed

2 files changed

+53
-32
lines changed

src/response.rs

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ use anyhow::{anyhow, Result};
22
#[cfg(feature = "json")]
33
use serde::de::DeserializeOwned;
44
use std::collections::HashMap;
5-
use wasi::http::types::{IncomingResponse, StatusCode};
6-
use wasi::io::streams::StreamError;
5+
use wasi::http::types::{IncomingBody, IncomingResponse, StatusCode};
6+
use wasi::io::streams::{InputStream, StreamError};
77

88
pub struct Response {
99
status: StatusCode,
1010
headers: HashMap<String, String>,
11-
body: Vec<u8>,
11+
// input-stream resource is a child: it must be dropped before the parent incoming-body is dropped
12+
input_stream: InputStream,
13+
_incoming_body: IncomingBody,
1214
}
1315

1416
impl Response {
@@ -22,42 +24,48 @@ impl Response {
2224
}
2325
drop(headers_handle);
2426

25-
let incoming_body = incoming_response
26-
.consume()
27-
.map_err(|()| anyhow!("incoming response has no body stream"))?;
27+
// The consume() method can only be called once
28+
let incoming_body = incoming_response.consume().unwrap();
2829
drop(incoming_response);
2930

31+
// The stream() method can only be called once
3032
let input_stream = incoming_body.stream().unwrap();
31-
let mut body = vec![];
32-
loop {
33-
let mut body_chunk = match input_stream.read(1024 * 1024) {
34-
Ok(c) => c,
35-
Err(StreamError::Closed) => break,
36-
Err(e) => Err(anyhow!("input_stream read failed: {e:?}"))?,
37-
};
38-
39-
if !body_chunk.is_empty() {
40-
body.append(&mut body_chunk);
41-
}
42-
}
43-
4433
Ok(Self {
4534
status,
4635
headers,
47-
body,
36+
input_stream,
37+
_incoming_body: incoming_body,
4838
})
4939
}
5040

51-
pub fn status(&self) -> &StatusCode {
52-
&self.status
41+
pub fn status(&self) -> StatusCode {
42+
self.status
5343
}
5444

5545
pub fn headers(&self) -> &HashMap<String, String> {
5646
&self.headers
5747
}
5848

59-
pub fn body(&self) -> &Vec<u8> {
60-
&self.body
49+
/// Get a chunk of the response body.
50+
///
51+
/// It will block until at least one byte can be read or the stream is closed.
52+
pub fn chunk(&self, len: u64) -> Result<Option<Vec<u8>>> {
53+
match self.input_stream.blocking_read(len) {
54+
Ok(c) => Ok(Some(c)),
55+
Err(StreamError::Closed) => Ok(None),
56+
Err(e) => Err(anyhow!("input_stream read failed: {e:?}"))?,
57+
}
58+
}
59+
60+
/// Get the full response body.
61+
///
62+
/// It will block until the stream is closed.
63+
pub fn body(self) -> Result<Vec<u8>> {
64+
let mut body = Vec::new();
65+
while let Some(mut chunk) = self.chunk(1024 * 1024)? {
66+
body.append(&mut chunk);
67+
}
68+
Ok(body)
6169
}
6270

6371
/// Deserialize the response body as JSON.
@@ -67,7 +75,7 @@ impl Response {
6775
/// This requires the `json` feature enabled.
6876
#[cfg(feature = "json")]
6977
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
70-
pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
71-
Ok(serde_json::from_slice(&self.body)?)
78+
pub fn json<T: DeserializeOwned>(self) -> Result<T> {
79+
Ok(serde_json::from_slice(self.body()?.as_ref())?)
7280
}
7381
}

tests/program/src/lib.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,30 @@ impl Guest for Component {
2727
println!(
2828
"GET https://httpbin.org/get, status code: {}, body:\n{}",
2929
resp.status(),
30-
String::from_utf8_lossy(resp.body())
30+
String::from_utf8(resp.body().unwrap()).unwrap()
3131
);
3232

3333
// get with json response
3434
let resp = Client::new().get("https://httpbin.org/get").send().unwrap();
35+
let status = resp.status();
3536
let json_data = resp.json::<Data>().unwrap();
3637
println!(
3738
"GET https://httpbin.org/get, status code: {}, body:\n{:?}\n",
38-
resp.status(),
39-
json_data,
39+
status, json_data,
40+
);
41+
42+
let resp = Client::new()
43+
.get("https://httpbin.org/range/20?duration=5&chunk_size=10")
44+
.send()
45+
.unwrap();
46+
println!(
47+
"GET https://httpbin.org/range, status code: {}, body:",
48+
resp.status()
4049
);
50+
while let Some(chunk) = resp.chunk(1024).unwrap() {
51+
println!("{}", String::from_utf8(chunk).unwrap());
52+
}
53+
println!();
4154

4255
// post with json data
4356
let resp = Client::new()
@@ -51,7 +64,7 @@ impl Guest for Component {
5164
println!(
5265
"POST https://httpbin.org/post, status code: {}, body:\n{}",
5366
resp.status(),
54-
String::from_utf8_lossy(resp.body())
67+
String::from_utf8(resp.body().unwrap()).unwrap()
5568
);
5669

5770
// post with form data
@@ -66,7 +79,7 @@ impl Guest for Component {
6679
println!(
6780
"POST https://httpbin.org/post, status code: {}, body:\n{}",
6881
resp.status(),
69-
String::from_utf8_lossy(resp.body())
82+
String::from_utf8(resp.body().unwrap()).unwrap()
7083
);
7184

7285
// post with file form data
@@ -93,7 +106,7 @@ hello
93106
println!(
94107
"POST https://httpbin.org/post, status code: {}, body:\n{}",
95108
resp.status(),
96-
String::from_utf8_lossy(resp.body())
109+
String::from_utf8(resp.body().unwrap()).unwrap()
97110
);
98111
Ok(())
99112
}

0 commit comments

Comments
 (0)