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:
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user