forked from integer32llc/playground-middleware
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
this commit adds partial content delivery support borrowing @Cobrand's PR iron/staticfile#98 commit motivated by static-web-server/static-web-server#15
- Loading branch information
Showing
3 changed files
with
170 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// NOTE: | ||
// This file implements Partial Content Delivery which is used as part as this middleware. | ||
// Code below was borrowed from one @Cobrand's PR and adapted to this project. | ||
// More details at https://github.com/iron/staticfile/pull/98 | ||
|
||
use iron::headers::{ByteRangeSpec, ContentLength, ContentRange, ContentRangeSpec}; | ||
use iron::modifier::Modifier; | ||
use iron::response::{Response, WriteBody}; | ||
use iron::status::Status; | ||
use std::cmp; | ||
use std::fs::File; | ||
use std::io::{self, Read, Seek, SeekFrom, Write}; | ||
use std::path::Path; | ||
|
||
pub enum PartialFileRange { | ||
AllFrom(u64), | ||
FromTo(u64, u64), | ||
Last(u64), | ||
} | ||
|
||
pub struct PartialFile { | ||
file: File, | ||
range: PartialFileRange, | ||
} | ||
|
||
struct PartialContentBody { | ||
pub file: File, | ||
pub offset: u64, | ||
pub len: u64, | ||
} | ||
|
||
impl PartialFile { | ||
pub fn new<Range>(file: File, range: Range) -> PartialFile | ||
where | ||
Range: Into<PartialFileRange>, | ||
{ | ||
let range = range.into(); | ||
PartialFile { file, range } | ||
} | ||
|
||
pub fn from_path<P: AsRef<Path>, Range>(path: P, range: Range) -> Result<PartialFile, io::Error> | ||
where | ||
Range: Into<PartialFileRange>, | ||
{ | ||
let file = File::open(path.as_ref())?; | ||
Ok(Self::new(file, range)) | ||
} | ||
} | ||
|
||
impl From<ByteRangeSpec> for PartialFileRange { | ||
fn from(b: ByteRangeSpec) -> PartialFileRange { | ||
match b { | ||
ByteRangeSpec::AllFrom(from) => PartialFileRange::AllFrom(from), | ||
ByteRangeSpec::FromTo(from, to) => PartialFileRange::FromTo(from, to), | ||
ByteRangeSpec::Last(last) => PartialFileRange::Last(last), | ||
} | ||
} | ||
} | ||
|
||
impl From<Vec<ByteRangeSpec>> for PartialFileRange { | ||
fn from(v: Vec<ByteRangeSpec>) -> PartialFileRange { | ||
match v.into_iter().next() { | ||
// in the case no value is in "Range", return | ||
// the whole file instead of panicking | ||
// Note that an empty vec should never happen, | ||
// but we can never be too sure | ||
None => PartialFileRange::AllFrom(0), | ||
Some(byte_range) => PartialFileRange::from(byte_range), | ||
} | ||
} | ||
} | ||
|
||
impl Modifier<Response> for PartialFile { | ||
#[inline] | ||
fn modify(self, res: &mut Response) { | ||
use self::PartialFileRange::*; | ||
|
||
let metadata: Option<_> = self.file.metadata().ok(); | ||
let file_length: Option<u64> = metadata.map(|m| m.len()); | ||
let range: Option<(u64, u64)> = match (self.range, file_length) { | ||
(FromTo(from, to), Some(file_length)) => { | ||
if from <= to && from < file_length { | ||
Some((from, cmp::min(to, file_length - 1))) | ||
} else { | ||
None | ||
} | ||
} | ||
(AllFrom(from), Some(file_length)) => { | ||
if from < file_length { | ||
Some((from, file_length - 1)) | ||
} else { | ||
None | ||
} | ||
} | ||
(Last(last), Some(file_length)) => { | ||
if last < file_length { | ||
Some((file_length - last, file_length - 1)) | ||
} else { | ||
Some((0, file_length - 1)) | ||
} | ||
} | ||
(_, None) => None, | ||
}; | ||
|
||
if let Some(range) = range { | ||
let content_range = ContentRange(ContentRangeSpec::Bytes { | ||
range: Some(range), | ||
instance_length: file_length, | ||
}); | ||
let content_len = range.1 - range.0 + 1; | ||
res.headers.set(ContentLength(content_len)); | ||
res.headers.set(content_range); | ||
let partial_content = PartialContentBody { | ||
file: self.file, | ||
offset: range.0, | ||
len: content_len, | ||
}; | ||
res.status = Some(Status::PartialContent); | ||
res.body = Some(Box::new(partial_content)); | ||
} else { | ||
if let Some(file_length) = file_length { | ||
res.headers.set(ContentRange(ContentRangeSpec::Bytes { | ||
range: None, | ||
instance_length: Some(file_length), | ||
})); | ||
}; | ||
res.status = Some(Status::RangeNotSatisfiable); | ||
} | ||
} | ||
} | ||
|
||
impl WriteBody for PartialContentBody { | ||
fn write_body(&mut self, res: &mut dyn Write) -> io::Result<()> { | ||
self.file.seek(SeekFrom::Start(self.offset))?; | ||
let mut limiter = <File as Read>::by_ref(&mut self.file).take(self.len); | ||
io::copy(&mut limiter, res).map(|_| ()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters