Use BufRead in more places (#261)

This moves Stream's enum into an `Inner` enum, and wraps a single
BufReader around the whole thing. This makes it easier to consistently
treat the contents of Stream as being wrapped in a BufReader.

Also, implement BufRead for DeadlineStream. This means when a timeout
is set, we don't have to set that timeout on the socket with every
small read, just when we fill up the buffer. This reduces the number
of syscalls.

Remove the `Cursor` variant from Stream. It was strictly less powerful
than that `Test` variant, so I've replaced the handful of Cursor uses
with `Test`. Because some of those cases weren't test, I removed the
`#[cfg(test)]` param on the `Test` variant.

Now that all inputs to `do_from_read` are `impl BufRead`, add that
as a type constraint. Change `read_next_line` to take advantage of
`BufRead::read_line`, which may be somewhat faster (though I haven't
benchmarked).
This commit is contained in:
Jacob Hoffman-Andrews
2020-11-30 09:23:19 -08:00
committed by GitHub
4 changed files with 124 additions and 124 deletions

View File

@@ -1,6 +1,6 @@
use std::fmt;
use std::io::{self, Cursor, Read};
use std::io::{self, Read};
use std::str::FromStr;
use std::{fmt, io::BufRead};
use chunked_transfer::Decoder as ChunkDecoder;
@@ -424,7 +424,7 @@ impl Response {
/// let resp = ureq::Response::do_from_read(read);
///
/// assert_eq!(resp.status(), 401);
pub(crate) fn do_from_read(mut reader: impl Read) -> Result<Response, Error> {
pub(crate) fn do_from_read(mut reader: impl BufRead) -> Result<Response, Error> {
//
// HTTP/1.1 200 OK\r\n
let status_line = read_next_line(&mut reader)?;
@@ -454,8 +454,8 @@ impl Response {
}
#[cfg(test)]
pub fn to_write_vec(&self) -> Vec<u8> {
self.stream.as_ref().unwrap().to_write_vec()
pub fn to_write_vec(self) -> Vec<u8> {
self.stream.unwrap().to_write_vec()
}
}
@@ -507,10 +507,9 @@ impl FromStr for Response {
/// assert_eq!(body, "Hello World!!!");
/// ```
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.as_bytes().to_owned();
let mut cursor = Cursor::new(bytes);
let mut resp = Self::do_from_read(&mut cursor)?;
set_stream(&mut resp, "".into(), None, Stream::Cursor(cursor));
let mut stream = Stream::from_vec(s.as_bytes().to_owned());
let mut resp = Self::do_from_read(&mut stream)?;
set_stream(&mut resp, "".into(), None, stream);
Ok(resp)
}
}
@@ -524,34 +523,24 @@ pub(crate) fn set_stream(resp: &mut Response, url: String, unit: Option<Unit>, s
resp.stream = Some(stream);
}
fn read_next_line<R: Read>(reader: &mut R) -> io::Result<String> {
let mut buf = Vec::new();
let mut prev_byte_was_cr = false;
let mut one = [0_u8];
loop {
let amt = reader.read(&mut one[..])?;
if amt == 0 {
return Err(io::Error::new(
io::ErrorKind::ConnectionAborted,
"Unexpected EOF",
));
}
let byte = one[0];
if byte == b'\n' && prev_byte_was_cr {
buf.pop(); // removing the '\r'
return String::from_utf8(buf).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "Header is not in ASCII")
});
}
prev_byte_was_cr = byte == b'\r';
buf.push(byte);
fn read_next_line(reader: &mut impl BufRead) -> io::Result<String> {
let mut s = String::new();
if reader.read_line(&mut s)? == 0 {
return Err(io::Error::new(
io::ErrorKind::ConnectionAborted,
"Unexpected EOF",
));
}
if !s.ends_with("\r\n") {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Header field didn't end with \\r: {}", s),
));
}
s.pop();
s.pop();
Ok(s)
}
/// Limits a `Read` to a content size (as set by a "Content-Length" header).