sigoden
3 years ago
committed by
GitHub
6 changed files with 226 additions and 64 deletions
@ -0,0 +1,68 @@
@@ -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 @@
@@ -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