/// An HTTP response. /// /// Returned by [`Request::send`](struct.Request.html#method.send). /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), minreq::Error> { /// let response = minreq::get("http://example.com").send()?; /// println!("{}", response.as_str()?); /// # Ok(()) } /// ``` #[derive(Clone, PartialEq, Eq, Debug)] pubstruct Response { /// The status code of the response, eg. 404. pub status_code: i32, /// The reason phrase of the response, eg. "Not Found". pub reason_phrase: String, /// The headers of the response. The header field names (the /// keys) are all lowercase. pub headers: HashMap<String, String>, /// The URL of the resource returned in this response. May differ from the /// request URL if it was redirected or typo corrections were applied (e.g. /// <http://example.com?foo=bar> would be corrected to /// <http://example.com/?foo=bar>). pub url: String,
body: Vec<u8>,
}
impl Response { pub(crate) fn create(mut parent: ResponseLazy, is_head: bool) -> Result<Response, Error> { letmut body = Vec::new(); if !is_head && parent.status_code != 204 && parent.status_code != 304 { for byte in &mut parent { let (byte, length) = byte?;
body.reserve(length);
body.push(byte);
}
}
/// Returns the body as an `&str`. /// /// # Errors /// /// Returns /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody) /// if the body is not UTF-8, with a description as to why the /// provided slice is not UTF-8. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # let url = "http://example.org/"; /// let response = minreq::get(url).send()?; /// println!("{}", response.as_str()?); /// # Ok(()) /// # } /// ``` pubfn as_str(&self) -> Result<&str, Error> { match str::from_utf8(&self.body) {
Ok(s) => Ok(s),
Err(err) => Err(Error::InvalidUtf8InBody(err)),
}
}
/// Returns a reference to the contained bytes of the body. If you /// want the `Vec<u8>` itself, use /// [`into_bytes()`](#method.into_bytes) instead. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # let url = "http://example.org/"; /// let response = minreq::get(url).send()?; /// println!("{:?}", response.as_bytes()); /// # Ok(()) /// # } /// ``` pubfn as_bytes(&self) -> &[u8] {
&self.body
}
/// Turns the `Response` into the inner `Vec<u8>`, the bytes that /// make up the response's body. If you just need a `&[u8]`, use /// [`as_bytes()`](#method.as_bytes) instead. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # let url = "http://example.org/"; /// let response = minreq::get(url).send()?; /// println!("{:?}", response.into_bytes()); /// // This would error, as into_bytes consumes the Response: /// // let x = response.status_code; /// # Ok(()) /// # } /// ``` pubfn into_bytes(self) -> Vec<u8> { self.body
}
/// Converts JSON body to a `struct` using Serde. /// /// # Errors /// /// Returns /// [`SerdeJsonError`](enum.Error.html#variant.SerdeJsonError) if /// Serde runs into a problem, or /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody) /// if the body is not UTF-8. /// /// # Example /// In case compiler cannot figure out return type you might need to declare it explicitly: /// /// ```no_run /// use serde_json::Value; /// /// # fn main() -> Result<(), minreq::Error> { /// # let url_to_json_resource = "http://example.org/resource.json"; /// // Value could be any type that implements Deserialize! /// let user = minreq::get(url_to_json_resource).send()?.json::<Value>()?; /// println!("User name is '{}'", user["name"]); /// # Ok(()) /// # } /// ``` #[cfg(feature = "json-using-serde")] pubfn json<'a, T>(&'a self) -> Result<T, Error> where
T: serde::de::Deserialize<'a>,
{ let str = matchself.as_str() {
Ok(str) => str,
Err(_) => return Err(Error::InvalidUtf8InResponse),
}; match serde_json::from_str(str) {
Ok(json) => Ok(json),
Err(err) => Err(Error::SerdeJsonError(err)),
}
}
}
/// An HTTP response, which is loaded lazily. /// /// In comparison to [`Response`](struct.Response.html), this is /// returned from /// [`send_lazy()`](struct.Request.html#method.send_lazy), where as /// [`Response`](struct.Response.html) is returned from /// [`send()`](struct.Request.html#method.send). /// /// In practice, "lazy loading" means that the bytes are only loaded /// as you iterate through them. The bytes are provided in the form of /// a `Result<(u8, usize), minreq::Error>`, as the reading operation /// can fail in various ways. The `u8` is the actual byte that was /// read, and `usize` is how many bytes we are expecting to read in /// the future (including this byte). Note, however, that the `usize` /// can change, particularly when the `Transfer-Encoding` is /// `chunked`: then it will reflect how many bytes are left of the /// current chunk. The expected size is capped at 16 KiB to avoid /// server-side DoS attacks targeted at clients accidentally reserving /// too much memory. /// /// # Example /// ```no_run /// // This is how the normal Response works behind the scenes, and /// // how you might use ResponseLazy. /// # fn main() -> Result<(), minreq::Error> { /// let response = minreq::get("http://example.com").send_lazy()?; /// let mut vec = Vec::new(); /// for result in response { /// let (byte, length) = result?; /// vec.reserve(length); /// vec.push(byte); /// } /// # Ok(()) /// # } /// /// ``` pubstruct ResponseLazy { /// The status code of the response, eg. 404. pub status_code: i32, /// The reason phrase of the response, eg. "Not Found". pub reason_phrase: String, /// The headers of the response. The header field names (the /// keys) are all lowercase. pub headers: HashMap<String, String>, /// The URL of the resource returned in this response. May differ from the /// request URL if it was redirected or typo corrections were applied (e.g. /// <http://example.com?foo=bar> would be corrected to /// <http://example.com/?foo=bar>). pub url: String,
impl Read for ResponseLazy { fn read(&mutself, buf: &mut [u8]) -> io::Result<usize> { letmut index = 0; for res inself { // there is no use for the estimated length in the read implementation // so it is ignored. let (byte, _) = res.map_err(|e| match e {
Error::IoError(e) => e,
_ => io::Error::new(io::ErrorKind::Other, e),
})?;
buf[index] = byte;
index += 1;
// if the buffer is full, it should stop reading if index >= buf.len() { break;
}
}
// index of the next byte is the number of bytes thats have been read
Ok(index)
}
}
if *chunk_length == 0 { // Max length of the chunk length line is 1KB: not too long to // take up much memory, long enough to tolerate some chunk // extensions (which are ignored).
// Get the size of the next chunk let length_line = match read_line(bytes, Some(1024), Error::MalformedChunkLength) {
Ok(line) => line,
Err(err) => return Some(Err(err)),
};
// Note: the trim() and check for empty lines shouldn't be // needed according to the RFC, but we might as well, it's a // small change and it fixes a few servers. let incoming_length = if length_line.is_empty() { 0
} else { let length = iflet Some(i) = length_line.find(';') {
length_line[..i].trim()
} else {
length_line.trim()
}; match usize::from_str_radix(length, 16) {
Ok(length) => length,
Err(_) => return Some(Err(Error::MalformedChunkLength)),
}
};
if *chunk_length > 0 {
*chunk_length -= 1; iflet Some(byte) = bytes.next() { match byte {
Ok(byte) => { // If we're at the end of the chunk... if *chunk_length == 0 { //...read the trailing \r\n of the chunk, and // possibly return an error instead.
// TODO: Maybe this could be written in a way // that doesn't discard the last ok byte if // the \r\n reading fails? iflet Err(err) = read_line(bytes, Some(2), Error::MalformedChunkEnd) { return Some(Err(err));
}
}
enum HttpStreamState { // No Content-Length, and Transfer-Encoding != chunked, so we just // read unti lthe server closes the connection (this should be the // fallback, if I read the rfc right).
EndOnClose, // Content-Length was specified, read that amount of bytes
ContentLength(usize), // Transfer-Encoding == chunked, so we need to save two pieces of // information: are we expecting more chunks, how much is there // left of the current chunk, and how much have we read? The last // number is needed in order to provide an accurate Content-Length // header after loading all the bytes.
Chunked(bool, usize, usize),
}
// This struct is just used in the Response and ResponseLazy // constructors, but not in their structs, for api-cleanliness // reasons. (Eg. response.status_code is much cleaner than // response.meta.status_code or similar.) struct ResponseMetadata {
status_code: i32,
reason_phrase: String,
headers: HashMap<String, String>,
state: HttpStreamState,
max_trailing_headers_size: Option<usize>,
}
fn read_metadata(
stream: &mut HttpStreamBytes, mut max_headers_size: Option<usize>,
max_status_line_len: Option<usize>,
) -> Result<ResponseMetadata, Error> { let line = read_line(stream, max_status_line_len, Error::StatusLineOverflow)?; let (status_code, reason_phrase) = parse_status_line(&line);
letmut headers = HashMap::new(); loop { let line = read_line(stream, max_headers_size, Error::HeadersOverflow)?; if line.is_empty() { // Body starts here break;
} iflet Some(refmut max_headers_size) = max_headers_size {
*max_headers_size -= line.len() + 2;
} iflet Some(header) = parse_header(line) {
headers.insert(header.0, header.1);
}
}
letmut chunked = false; letmut content_length = None; for (header, value) in &headers { // Handle the Transfer-Encoding header if header.to_lowercase().trim() == "transfer-encoding"
&& value.to_lowercase().trim() == "chunked"
{
chunked = true;
}
// Handle the Content-Length header if header.to_lowercase().trim() == "content-length" { match str::parse::<usize>(value.trim()) {
Ok(length) => content_length = Some(length),
Err(_) => return Err(Error::MalformedContentLength),
}
}
}
let state = if chunked {
HttpStreamState::Chunked(true, 0, 0)
} elseiflet Some(length) = content_length {
HttpStreamState::ContentLength(length)
} else {
HttpStreamState::EndOnClose
};
(503, "Server did not provide a status line".to_string())
}
fn parse_header(mut line: String) -> Option<(String, String)> { iflet Some(location) = line.find(':') { // Trim the first character of the header if it is a space, // otherwise return everything after the ':'. This should // preserve the behavior in versions <=2.0.1 in most cases // (namely, ones where it was valid), where the first // character after ':' was always cut off. let value = iflet Some(sp) = line.get(location + 1..location + 2) { if sp == " " {
line[location + 2..].to_string()
} else {
line[location + 1..].to_string()
}
} else {
line[location + 1..].to_string()
};
line.truncate(location); // Headers should be ascii, I'm pretty sure. If not, please open an issue.
line.make_ascii_lowercase(); return Some((line, value));
}
None
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.