sigoden
3 years ago
committed by
GitHub
6 changed files with 226 additions and 64 deletions
@ -0,0 +1,68 @@ |
|||||||
|
use async_stream::stream; |
||||||
|
use futures::{Stream, StreamExt}; |
||||||
|
use std::io::Error; |
||||||
|
use std::pin::Pin; |
||||||
|
use tokio::io::{AsyncRead, AsyncReadExt}; |
||||||
|
|
||||||
|
pub struct Streamer<R> |
||||||
|
where |
||||||
|
R: AsyncRead + Unpin + Send + 'static, |
||||||
|
{ |
||||||
|
reader: R, |
||||||
|
buf_size: usize, |
||||||
|
} |
||||||
|
|
||||||
|
impl<R> Streamer<R> |
||||||
|
where |
||||||
|
R: AsyncRead + Unpin + Send + 'static, |
||||||
|
{ |
||||||
|
#[inline] |
||||||
|
pub fn new(reader: R, buf_size: usize) -> Self { |
||||||
|
Self { reader, buf_size } |
||||||
|
} |
||||||
|
pub fn into_stream( |
||||||
|
mut self, |
||||||
|
) -> Pin<Box<impl ?Sized + Stream<Item = Result<Vec<u8>, Error>> + 'static>> { |
||||||
|
let stream = stream! { |
||||||
|
loop { |
||||||
|
let mut buf = vec![0; self.buf_size]; |
||||||
|
let r = self.reader.read(&mut buf).await?; |
||||||
|
if r == 0 { |
||||||
|
break
|
||||||
|
} |
||||||
|
buf.truncate(r); |
||||||
|
yield Ok(buf); |
||||||
|
} |
||||||
|
}; |
||||||
|
stream.boxed() |
||||||
|
} |
||||||
|
// allow truncation as truncated remaining is always less than buf_size: usize
|
||||||
|
pub fn into_stream_sized( |
||||||
|
mut self, |
||||||
|
max_length: u64, |
||||||
|
) -> Pin<Box<impl ?Sized + Stream<Item = Result<Vec<u8>, Error>> + 'static>> { |
||||||
|
let stream = stream! { |
||||||
|
let mut remaining = max_length; |
||||||
|
loop { |
||||||
|
if remaining == 0 { |
||||||
|
break; |
||||||
|
} |
||||||
|
let bs = if remaining >= self.buf_size as u64 { |
||||||
|
self.buf_size |
||||||
|
} else { |
||||||
|
remaining as usize |
||||||
|
}; |
||||||
|
let mut buf = vec![0; bs]; |
||||||
|
let r = self.reader.read(&mut buf).await?; |
||||||
|
if r == 0 { |
||||||
|
break; |
||||||
|
} else { |
||||||
|
buf.truncate(r); |
||||||
|
yield Ok(buf); |
||||||
|
} |
||||||
|
remaining -= r as u64; |
||||||
|
} |
||||||
|
}; |
||||||
|
stream.boxed() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
mod fixtures; |
||||||
|
mod utils; |
||||||
|
|
||||||
|
use fixtures::{server, Error, TestServer}; |
||||||
|
use headers::HeaderValue; |
||||||
|
use rstest::rstest; |
||||||
|
|
||||||
|
#[rstest] |
||||||
|
fn get_file_range(server: TestServer) -> Result<(), Error> { |
||||||
|
let resp = fetch!(b"GET", format!("{}index.html", server.url())) |
||||||
|
.header("range", HeaderValue::from_static("bytes=0-6")) |
||||||
|
.send()?; |
||||||
|
assert_eq!(resp.status(), 206); |
||||||
|
assert_eq!(resp.headers().get("content-range").unwrap(), "bytes 0-6/18"); |
||||||
|
assert_eq!(resp.headers().get("accept-ranges").unwrap(), "bytes"); |
||||||
|
assert_eq!(resp.headers().get("content-length").unwrap(), "7"); |
||||||
|
assert_eq!(resp.text()?, "This is"); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
#[rstest] |
||||||
|
fn get_file_range_beyond(server: TestServer) -> Result<(), Error> { |
||||||
|
let resp = fetch!(b"GET", format!("{}index.html", server.url())) |
||||||
|
.header("range", HeaderValue::from_static("bytes=12-20")) |
||||||
|
.send()?; |
||||||
|
assert_eq!(resp.status(), 206); |
||||||
|
assert_eq!( |
||||||
|
resp.headers().get("content-range").unwrap(), |
||||||
|
"bytes 12-17/18" |
||||||
|
); |
||||||
|
assert_eq!(resp.headers().get("accept-ranges").unwrap(), "bytes"); |
||||||
|
assert_eq!(resp.headers().get("content-length").unwrap(), "6"); |
||||||
|
assert_eq!(resp.text()?, "x.html"); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
#[rstest] |
||||||
|
fn get_file_range_invalid(server: TestServer) -> Result<(), Error> { |
||||||
|
let resp = fetch!(b"GET", format!("{}index.html", server.url())) |
||||||
|
.header("range", HeaderValue::from_static("bytes=20-")) |
||||||
|
.send()?; |
||||||
|
assert_eq!(resp.status(), 416); |
||||||
|
assert_eq!(resp.headers().get("content-range").unwrap(), "bytes */18"); |
||||||
|
Ok(()) |
||||||
|
} |
Loading…
Reference in new issue