Skip to content

Commit

Permalink
Use Response::build_from for Range Responses
Browse files Browse the repository at this point in the history
  • Loading branch information
Drakulix committed May 15, 2019
1 parent eced004 commit 6847bdb
Showing 1 changed file with 56 additions and 97 deletions.
153 changes: 56 additions & 97 deletions core/lib/src/response/responder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,62 +219,55 @@ impl<'r> Responder<'r> for String {
}
}

fn try_range_response<'r, B: io::Read + io::Seek + 'r>(req: Request<'r>, mut body: B) -> Result<response::Response<'r>, B> {
fn range_response<'r, B: io::Seek + io::Read + 'r>(mut body: B, req: &Request) -> Response<'r> {
use std::cmp;
use http::hyper::header::{ContentRange, ByteRangeSpec, ContentRangeSpec};

// A server MUST ignore a Range header field received with a request method other than GET.
if req.method() == Method::Get {
return Err(body);
}

let range = req.headers().get_one("Range").and_then(Range::from_str).map_err(|_| body)?;

match range {
Range::Bytes(ranges) => {
if ranges.len() == 1 {
let size = body.seek(io::SeekFrom::End(0))
.expect("Attempted to retrieve size by seeking, but failed.");

let (start, end) = match ranges[0] {
ByteRangeSpec::FromTo(mut start, mut end) => {
if end < start {
return Ok(
Response::build()
if req.method() != Method::Get {
let range = req.headers().get_one("Range").and_then(|x| Range::from_str(x).ok());
match range {
Some(Range::Bytes(ranges)) => {
if ranges.len() == 1 {
let size = body.seek(io::SeekFrom::End(0))
.expect("Attempted to retrieve size by seeking, but failed.");

let (start, end) = match ranges[0] {
ByteRangeSpec::FromTo(mut start, mut end) => {
if end < start {
return Response::build()
.status(Status::RangeNotSatisfiable)
.header(AcceptRanges(vec![RangeUnit::Bytes]))
.finalize()
);
}
if start > size {
start = size;
}
if start > size {
start = size;
}
if end > size {
end = size;
}
(start, end)
},
ByteRangeSpec::AllFrom(mut start) => {
if start > size {
start = size;
}
(start, size - start)
},
ByteRangeSpec::Last(len) => {
// we could seek to SeekFrom::End(-len), but if we reach a value < 0, that is an error.
// but the RFC reads:
// If the selected representation is shorter than the specified
// suffix-length, the entire representation is used.
let start = cmp::max(size - len, 0);
(start, size - start)
}
if end > size {
end = size;
}
(start, end)
},
ByteRangeSpec::AllFrom(mut start) => {
if start > size {
start = size;
}
(start, size - start)
},
ByteRangeSpec::Last(len) => {
// we could seek to SeekFrom::End(-len), but if we reach a value < 0, that is an error.
// but the RFC reads:
// If the selected representation is shorter than the specified
// suffix-length, the entire representation is used.
let start = cmp::max(size - len, 0);
(start, size - start)
}
};
};

body.seek(io::SeekFrom::Start(start))
.expect("Attempted to seek to the start of the requested range, but failed.");
body.seek(io::SeekFrom::Start(start))
.expect("Attempted to seek to the start of the requested range, but failed.");

return Ok(
Response::build()
return Response::build()
.status(Status::PartialContent)
.header(AcceptRanges(vec![RangeUnit::Bytes]))
.header(ContentRange(ContentRangeSpec::Bytes {
Expand All @@ -283,36 +276,28 @@ fn try_range_response<'r, B: io::Read + io::Seek + 'r>(req: Request<'r>, mut bod
}))
.raw_body(Body::Sized(body, end - start))
.finalize()
)
}
// A server MAY ignore the Range header field.
},
// An origin server MUST ignore a Range header field that contains a
// range unit it does not understand.
Range::Unregistered(_, _) => {},
};
}
// A server MAY ignore the Range header field.
},
// An origin server MUST ignore a Range header field that contains a
// range unit it does not understand.
Some(Range::Unregistered(_, _)) => {},
None => {},
};
}

Err(body)
Response::build()
.header(AcceptRanges(vec![RangeUnit::Bytes]))
.sized_body(body)
.finalize()
}

/// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`.
impl<'r> Responder<'r> for &'r [u8] {
fn respond_to(self, req: &Request) -> response::Result<'r> {
let mut body = Cursor::new(self);

match try_range_response(req, body) {
Ok(mut resp) => {
resp.set_header(ContentType::Binary);
return Ok(resp)
},
Err(old_body) => body = old_body,
};

Response::build()
Response::build_from(range_response(Cursor::new(self), req))
.header(ContentType::Binary)
.header(AcceptRanges(vec![RangeUnit::Bytes]))
.sized_body(body)
.ok()
}
}
Expand All @@ -321,42 +306,16 @@ impl<'r> Responder<'r> for &'r [u8] {
/// fixed-size body containing the data in `self`. Always returns `Ok`.
impl<'r> Responder<'r> for Vec<u8> {
fn respond_to(self, req: &Request) -> response::Result<'r> {
let mut body = Cursor::new(self);

match try_range_response(req, body) {
Ok(mut resp) => {
resp.set_header(ContentType::Binary);
return Ok(resp)
},
Err(old_body) => body = old_body,
};

Response::build()
Response::build_from(range_response(Cursor::new(self), req))
.header(ContentType::Binary)
.header(AcceptRanges(vec![RangeUnit::Bytes]))
.sized_body(body)
.ok()
}
}

/// Returns a response with a sized body for the file. Always returns `Ok`.
impl<'r> Responder<'r> for File {
fn respond_to(self, req: &Request) -> response::Result<'r> {
let (metadata, mut file) = (self.metadata(), BufReader::new(self));
match metadata {
Ok(md) => {
match try_range_response(req, body) {
Ok(mut resp) => return Ok(resp),
Err(old_body) => body = old_body,
};

Response::build()
.header(AcceptRanges(vec![RangeUnit::Bytes]))
.raw_body(Body::Sized(file, md.len()))
.ok()
},
Err(_) => Response::build().streamed_body(file).ok()
}
Response::build_from(range_response(BufReader::new(self), req)).ok()
}
}

Expand Down

0 comments on commit 6847bdb

Please sign in to comment.