Skip to content

Commit 2d83f79

Browse files
author
qrvaelet
committed
NamedFile: added ranges support, content-length support
1 parent 44c36e9 commit 2d83f79

File tree

1 file changed

+85
-8
lines changed

1 file changed

+85
-8
lines changed

src/fs.rs

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type};
2020
use error::Error;
2121
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
2222
use header;
23-
use http::{Method, StatusCode};
23+
use http::{HttpRange, Method, StatusCode};
2424
use httpmessage::HttpMessage;
2525
use httprequest::HttpRequest;
2626
use httpresponse::HttpResponse;
@@ -209,7 +209,7 @@ impl Responder for NamedFile {
209209
}).if_some(self.path().file_name(), |file_name, resp| {
210210
let mime_type = guess_mime_type(self.path());
211211
let inline_or_attachment = match mime_type.type_() {
212-
mime::IMAGE | mime::TEXT => "inline",
212+
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
213213
_ => "attachment",
214214
};
215215
resp.header(
@@ -228,6 +228,7 @@ impl Responder for NamedFile {
228228
.unwrap_or_else(|| req.cpu_pool().clone()),
229229
file: Some(self.file),
230230
fut: None,
231+
counter: 0,
231232
};
232233
return Ok(resp.streaming(reader));
233234
}
@@ -274,7 +275,7 @@ impl Responder for NamedFile {
274275
}).if_some(self.path().file_name(), |file_name, resp| {
275276
let mime_type = guess_mime_type(self.path());
276277
let inline_or_attachment = match mime_type.type_() {
277-
mime::IMAGE | mime::TEXT => "inline",
278+
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
278279
_ => "attachment",
279280
};
280281
resp.header(
@@ -292,6 +293,31 @@ impl Responder for NamedFile {
292293
.if_some(etag, |etag, resp| {
293294
resp.set(header::ETag(etag));
294295
});
296+
297+
// TODO: Debug, enabling "accept-ranges: bytes" causes problems with
298+
// certain clients when not using the ranges header.
299+
//resp.header(header::ACCEPT_RANGES, format!("bytes"));
300+
301+
let mut length = self.md.len();
302+
let mut offset = 0;
303+
304+
// check for ranges header
305+
if let Some(ranges) = req.headers().get(header::RANGE) {
306+
if let Ok(rangesheader) = ranges.to_str() {
307+
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
308+
length = rangesvec[0].length - 1;
309+
offset = rangesvec[0].start;
310+
resp.header(header::RANGE, format!("bytes={}-{}/{}", offset, offset+length, self.md.len()));
311+
} else {
312+
resp.header(header::RANGE, format!("*/{}", length));
313+
return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish());
314+
};
315+
} else {
316+
return Ok(resp.status(StatusCode::BAD_REQUEST).finish());
317+
};
318+
};
319+
320+
resp.header(header::CONTENT_LENGTH, format!("{}", length));
295321

296322
if precondition_failed {
297323
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish());
@@ -303,12 +329,16 @@ impl Responder for NamedFile {
303329
Ok(resp.finish())
304330
} else {
305331
let reader = ChunkedReadFile {
306-
size: self.md.len(),
307-
offset: 0,
332+
size: length,
333+
offset: offset,
308334
cpu_pool: self.cpu_pool
309335
.unwrap_or_else(|| req.cpu_pool().clone()),
310336
file: Some(self.file),
311337
fut: None,
338+
counter: 0,
339+
};
340+
if offset != 0 || length != self.md.len() {
341+
return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader));
312342
};
313343
Ok(resp.streaming(reader))
314344
}
@@ -323,6 +353,7 @@ pub struct ChunkedReadFile {
323353
cpu_pool: CpuPool,
324354
file: Option<File>,
325355
fut: Option<CpuFuture<(File, Bytes), io::Error>>,
356+
counter: u64,
326357
}
327358

328359
impl Stream for ChunkedReadFile {
@@ -336,6 +367,7 @@ impl Stream for ChunkedReadFile {
336367
self.fut.take();
337368
self.file = Some(file);
338369
self.offset += bytes.len() as u64;
370+
self.counter += bytes.len() as u64;
339371
Ok(Async::Ready(Some(bytes)))
340372
}
341373
Async::NotReady => Ok(Async::NotReady),
@@ -344,14 +376,16 @@ impl Stream for ChunkedReadFile {
344376

345377
let size = self.size;
346378
let offset = self.offset;
379+
let counter = self.counter;
347380

348-
if size == offset {
381+
if size == counter {
349382
Ok(Async::Ready(None))
350383
} else {
351384
let mut file = self.file.take().expect("Use after completion");
352385
self.fut = Some(self.cpu_pool.spawn_fn(move || {
353-
let max_bytes = cmp::min(size.saturating_sub(offset), 65_536) as usize;
354-
let mut buf = BytesMut::with_capacity(max_bytes);
386+
let max_bytes: usize;
387+
max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
388+
let mut buf = BytesMut::from(Vec::with_capacity(max_bytes));
355389
file.seek(io::SeekFrom::Start(offset))?;
356390
let nbytes = file.read(unsafe { buf.bytes_mut() })?;
357391
if nbytes == 0 {
@@ -742,6 +776,49 @@ mod tests {
742776
);
743777
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
744778
}
779+
780+
#[test]
781+
fn test_named_file_ranges_status_code() {
782+
let mut srv = test::TestServer::with_factory(|| {
783+
App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml"))
784+
});
785+
786+
let request = srv.get()
787+
.uri(srv.url("/t%65st/Cargo.toml"))
788+
.header(header::RANGE, "bytes=10-20")
789+
.finish()
790+
.unwrap();
791+
let response = srv.execute(request.send()).unwrap();
792+
793+
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
794+
}
795+
796+
#[test]
797+
fn test_named_file_ranges_headers() {
798+
let mut srv = test::TestServer::with_factory(|| {
799+
App::new().handler("test", StaticFiles::new(".").index_file("tests/test.binary"))
800+
});
801+
802+
let request = srv.get()
803+
.uri(srv.url("/t%65st/tests/test.binary"))
804+
.header(header::RANGE, "bytes=10-20")
805+
.finish()
806+
.unwrap();
807+
let response = srv.execute(request.send()).unwrap();
808+
let contentlength = response.headers().get(header::CONTENT_LENGTH).unwrap().to_str().unwrap();
809+
810+
assert_eq!(contentlength, "10");
811+
812+
let request = srv.get()
813+
.uri(srv.url("/t%65st/tests/test.binary"))
814+
.header(header::RANGE, "bytes=10-20")
815+
.finish()
816+
.unwrap();
817+
let response = srv.execute(request.send()).unwrap();
818+
let range = response.headers().get(header::RANGE).unwrap().to_str().unwrap();
819+
820+
assert_eq!(range, "bytes=10-20/100");
821+
}
745822

746823
#[test]
747824
fn test_named_file_not_allowed() {

0 commit comments

Comments
 (0)