Added support for range header when serving files
This commit is contained in:
parent
89e72d00ae
commit
777cca245e
2 changed files with 105 additions and 29 deletions
|
@ -1,8 +1,8 @@
|
|||
use rocket::http::{Cookie, Cookies, Status};
|
||||
use rocket::request::{self, FromRequest, Request};
|
||||
use rocket::response::NamedFile;
|
||||
use rocket::{Outcome, State};
|
||||
use rocket_contrib::json::Json;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
@ -11,6 +11,7 @@ use config::{self, Config};
|
|||
use db::DB;
|
||||
use errors;
|
||||
use index;
|
||||
use serve;
|
||||
use thumbnails;
|
||||
use user;
|
||||
use utils;
|
||||
|
@ -227,7 +228,7 @@ fn search(db: State<DB>, _auth: Auth, query: String) -> Result<Json<Vec<index::C
|
|||
}
|
||||
|
||||
#[get("/serve/<path..>")]
|
||||
fn serve(db: State<DB>, _auth: Auth, path: PathBuf) -> Result<NamedFile, errors::Error> {
|
||||
fn serve(db: State<DB>, _auth: Auth, path: PathBuf) -> Result<serve::RangeResponder<File>, errors::Error> {
|
||||
let db: &DB = db.deref();
|
||||
let vfs = db.get_vfs()?;
|
||||
let real_path = vfs.virtual_to_real(&path)?;
|
||||
|
@ -238,6 +239,6 @@ fn serve(db: State<DB>, _auth: Auth, path: PathBuf) -> Result<NamedFile, errors:
|
|||
real_path
|
||||
};
|
||||
|
||||
let serving = NamedFile::open(&serve_path)?;
|
||||
Ok(serving)
|
||||
let file = File::open(serve_path)?;
|
||||
Ok(serve::RangeResponder::new(file))
|
||||
}
|
||||
|
|
125
src/serve.rs
125
src/serve.rs
|
@ -6,10 +6,13 @@ use iron::modifiers::Header;
|
|||
use iron::prelude::*;
|
||||
use iron::response::WriteBody;
|
||||
use iron::status::{self, Status};
|
||||
use rocket;
|
||||
use rocket::response::{self, Responder};
|
||||
use std::cmp;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use errors::{Error, ErrorKind};
|
||||
|
||||
|
@ -98,33 +101,10 @@ impl PartialFile {
|
|||
|
||||
impl Modifier<Response> for PartialFile {
|
||||
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,
|
||||
};
|
||||
let range: Option<(u64, u64)> = truncate_range(&self.range, &file_length);
|
||||
|
||||
if let Some(range) = range {
|
||||
let content_range = ContentRange(ContentRangeSpec::Bytes {
|
||||
range: Some(range),
|
||||
|
@ -165,3 +145,98 @@ impl WriteBody for PartialContentBody {
|
|||
io::copy(&mut limiter, res).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RangeResponder<R> {
|
||||
original: R,
|
||||
}
|
||||
|
||||
impl<'r, R: Responder<'r>> RangeResponder<R> {
|
||||
pub fn new(original: R) -> RangeResponder<R> {
|
||||
RangeResponder{ original }
|
||||
}
|
||||
|
||||
fn ignore_range(self, request: &rocket::request::Request) -> response::Result<'r> {
|
||||
let mut response = self.original.respond_to(request)?;
|
||||
response.set_status(rocket::http::Status::RangeNotSatisfiable);
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
fn truncate_range(range: &PartialFileRange, file_length: &Option<u64>) -> Option<(u64, u64)> {
|
||||
use self::PartialFileRange::*;
|
||||
|
||||
match (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,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Responder<'r> for RangeResponder<File> {
|
||||
|
||||
fn respond_to(mut self, request: &rocket::request::Request) -> response::Result<'r> {
|
||||
|
||||
use rocket::http::hyper::header::*;
|
||||
|
||||
let range_header = request.headers().get_one("Range");
|
||||
let range_header = match range_header {
|
||||
None => return Ok(self.original.respond_to(request)?),
|
||||
Some(h) => h,
|
||||
};
|
||||
|
||||
let vec_range = match Range::from_str(range_header) {
|
||||
Ok(Range::Bytes(v)) => v,
|
||||
_ => return self.ignore_range(request),
|
||||
};
|
||||
|
||||
let partial_file_range = match vec_range.into_iter().next() {
|
||||
None => PartialFileRange::AllFrom(0),
|
||||
Some(byte_range) => PartialFileRange::from(byte_range),
|
||||
};
|
||||
|
||||
let metadata: Option<_> = self.original.metadata().ok();
|
||||
let file_length: Option<u64> = metadata.map(|m| m.len());
|
||||
let range: Option<(u64, u64)> = truncate_range(&partial_file_range, &file_length);
|
||||
|
||||
if let Some((from, to)) = range {
|
||||
let content_range = ContentRange(ContentRangeSpec::Bytes {
|
||||
range: range,
|
||||
instance_length: file_length,
|
||||
});
|
||||
let content_len = to - from + 1;
|
||||
|
||||
match self.original.seek(SeekFrom::Start(from)) {
|
||||
Ok(_) => (),
|
||||
Err(_) => return Err(rocket::http::Status::InternalServerError),
|
||||
}
|
||||
let partial_original = self.original.take(content_len).into_inner();
|
||||
let mut response = partial_original.respond_to(request)?;
|
||||
response.set_header(ContentLength(content_len));
|
||||
response.set_header(content_range);
|
||||
response.set_status(rocket::http::Status::PartialContent);
|
||||
|
||||
Ok(response)
|
||||
} else {
|
||||
self.ignore_range(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue