mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Add Content Range Header and add one for blob end range (#34797)
Signed-off-by: rayguo17 <rayguo17@gmail.com>
This commit is contained in:
parent
faf30176f1
commit
55ac1887dc
6 changed files with 75 additions and 95 deletions
|
@ -11,7 +11,7 @@ use std::sync::atomic::{self, AtomicBool, AtomicUsize, Ordering};
|
|||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
|
||||
use embedder_traits::{EmbedderMsg, EmbedderProxy, FilterPattern};
|
||||
use headers::{ContentLength, ContentType, HeaderMap, HeaderMapExt, Range};
|
||||
use headers::{ContentLength, ContentRange, ContentType, HeaderMap, HeaderMapExt, Range};
|
||||
use http::header::{self, HeaderValue};
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use log::warn;
|
||||
|
@ -290,24 +290,39 @@ impl FileManager {
|
|||
response: &mut Response,
|
||||
) -> Result<(), BlobURLStoreError> {
|
||||
let file_impl = self.store.get_impl(id, file_token, origin_in)?;
|
||||
/*
|
||||
Only Fetch Blob Range Request would have unresolved range, and only in that case we care about range header.
|
||||
*/
|
||||
let mut is_range_requested = false;
|
||||
match file_impl {
|
||||
FileImpl::Memory(buf) => {
|
||||
let bounds = match bounds {
|
||||
BlobBounds::Unresolved(range) => get_range_request_bounds(range, buf.size),
|
||||
BlobBounds::Unresolved(range) => {
|
||||
if range.is_some() {
|
||||
is_range_requested = true;
|
||||
}
|
||||
get_range_request_bounds(range, buf.size)
|
||||
},
|
||||
BlobBounds::Resolved(bounds) => bounds,
|
||||
};
|
||||
let range = bounds
|
||||
.get_final(Some(buf.size))
|
||||
.map_err(|_| BlobURLStoreError::InvalidRange)?;
|
||||
|
||||
let range = range.to_abs_range(buf.size as usize);
|
||||
let range = range.to_abs_blob_range(buf.size as usize);
|
||||
let len = range.len() as u64;
|
||||
let content_range = if is_range_requested {
|
||||
ContentRange::bytes(range.start as u64..range.end as u64, buf.size).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
set_headers(
|
||||
&mut response.headers,
|
||||
len,
|
||||
buf.type_string.parse().unwrap_or(mime::TEXT_PLAIN),
|
||||
/* filename */ None,
|
||||
content_range,
|
||||
);
|
||||
|
||||
let mut bytes = vec![];
|
||||
|
@ -327,9 +342,14 @@ impl FileManager {
|
|||
|
||||
let file = File::open(&metadata.path)
|
||||
.map_err(|e| BlobURLStoreError::External(e.to_string()))?;
|
||||
|
||||
let mut is_range_requested = false;
|
||||
let bounds = match bounds {
|
||||
BlobBounds::Unresolved(range) => get_range_request_bounds(range, metadata.size),
|
||||
BlobBounds::Unresolved(range) => {
|
||||
if range.is_some() {
|
||||
is_range_requested = true;
|
||||
}
|
||||
get_range_request_bounds(range, metadata.size)
|
||||
},
|
||||
BlobBounds::Resolved(bounds) => bounds,
|
||||
};
|
||||
let range = bounds
|
||||
|
@ -349,6 +369,13 @@ impl FileManager {
|
|||
.and_then(|osstr| osstr.to_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let content_range = if is_range_requested {
|
||||
let abs_range = range.to_abs_blob_range(metadata.size as usize);
|
||||
ContentRange::bytes(abs_range.start as u64..abs_range.end as u64, metadata.size)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
set_headers(
|
||||
&mut response.headers,
|
||||
metadata.size,
|
||||
|
@ -356,6 +383,7 @@ impl FileManager {
|
|||
.first()
|
||||
.unwrap_or(mime::TEXT_PLAIN),
|
||||
filename,
|
||||
content_range,
|
||||
);
|
||||
|
||||
self.fetch_file_in_chunks(
|
||||
|
@ -939,8 +967,17 @@ fn read_file_in_chunks(
|
|||
}
|
||||
}
|
||||
|
||||
fn set_headers(headers: &mut HeaderMap, content_length: u64, mime: Mime, filename: Option<String>) {
|
||||
fn set_headers(
|
||||
headers: &mut HeaderMap,
|
||||
content_length: u64,
|
||||
mime: Mime,
|
||||
filename: Option<String>,
|
||||
content_range: Option<ContentRange>,
|
||||
) {
|
||||
headers.typed_insert(ContentLength(content_length));
|
||||
if let Some(content_range) = content_range {
|
||||
headers.typed_insert(content_range);
|
||||
}
|
||||
headers.typed_insert(ContentType::from(mime.clone()));
|
||||
let name = match filename {
|
||||
Some(name) => name,
|
||||
|
|
|
@ -109,6 +109,16 @@ impl RelativePos {
|
|||
end: (start + span).to_usize().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// Per <https://fetch.spec.whatwg.org/#concept-scheme-fetch> step 3.blob.14.8:
|
||||
// "A range header denotes an inclusive byte range, while the slice blob algorithm input range does not.
|
||||
// To use the slice blob algorithm, we have to increment rangeEnd."
|
||||
pub fn to_abs_blob_range(&self, size: usize) -> Range<usize> {
|
||||
let orig_range = self.to_abs_range(size);
|
||||
let start = orig_range.start;
|
||||
let end = usize::min(orig_range.end + 1, size);
|
||||
Range { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
/// Response to file selection request
|
||||
|
|
24
tests/wpt/meta/fetch/range/blob.any.js.ini
vendored
24
tests/wpt/meta/fetch/range/blob.any.js.ini
vendored
|
@ -1,18 +1,18 @@
|
|||
[blob.any.html]
|
||||
[A simple blob range request.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no end.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no start.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A simple blob range request with whitespace.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob content with short content and a large range end]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob range with whitespace before and after hyphen]
|
||||
expected: FAIL
|
||||
|
@ -60,7 +60,7 @@
|
|||
expected: FAIL
|
||||
|
||||
[Blob content with short content and a range end matching content length]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob range with no value]
|
||||
expected: FAIL
|
||||
|
@ -83,19 +83,19 @@
|
|||
|
||||
[blob.any.worker.html]
|
||||
[A simple blob range request.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no end.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no start.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A simple blob range request with whitespace.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob content with short content and a large range end]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob range with whitespace before and after hyphen]
|
||||
expected: FAIL
|
||||
|
@ -143,7 +143,7 @@
|
|||
expected: FAIL
|
||||
|
||||
[Blob content with short content and a range end matching content length]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob range with no value]
|
||||
expected: FAIL
|
||||
|
|
20
tests/wpt/meta/xhr/blob-range.any.js.ini
vendored
20
tests/wpt/meta/xhr/blob-range.any.js.ini
vendored
|
@ -1,24 +1,24 @@
|
|||
[blob-range.any.worker.html]
|
||||
[A simple blob range request.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no type.]
|
||||
expected: FAIL
|
||||
|
||||
[A blob range request with no end.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no start.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A simple blob range request with whitespace.]
|
||||
expected: FAIL
|
||||
|
||||
[Blob content with short content and a large range end]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob content with short content and a range end matching content length]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob range with whitespace before and after hyphen]
|
||||
expected: FAIL
|
||||
|
@ -83,25 +83,25 @@
|
|||
|
||||
[blob-range.any.html]
|
||||
[A simple blob range request.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no type.]
|
||||
expected: FAIL
|
||||
|
||||
[A blob range request with no end.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A blob range request with no start.]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[A simple blob range request with whitespace.]
|
||||
expected: FAIL
|
||||
|
||||
[Blob content with short content and a large range end]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob content with short content and a range end matching content length]
|
||||
expected: FAIL
|
||||
expected: PASS
|
||||
|
||||
[Blob range with whitespace before and after hyphen]
|
||||
expected: FAIL
|
||||
|
|
7
tests/wpt/mozilla/meta/MANIFEST.json
vendored
7
tests/wpt/mozilla/meta/MANIFEST.json
vendored
|
@ -13905,13 +13905,6 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"range_request_blob_url.html": [
|
||||
"075397620e989dafc814c0ed2bca46bd476bccf6",
|
||||
[
|
||||
null,
|
||||
{}
|
||||
]
|
||||
],
|
||||
"range_request_file_url.html": [
|
||||
"4fd4ddc8b1a9959e90b243795267c220d6a05f5e",
|
||||
[
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
[{
|
||||
range: "bytes=0-",
|
||||
status: 206,
|
||||
expected: "abcdefghijklmnopqrstuvwxyz"
|
||||
}, {
|
||||
range: "bytes=0-9",
|
||||
status: 206,
|
||||
expected: "abcdefghi"
|
||||
}, {
|
||||
range: "bytes=1-9",
|
||||
status: 206,
|
||||
expected: "bcdefghi"
|
||||
}, {
|
||||
range: "bytes=-10",
|
||||
status: 206,
|
||||
expected: "qrstuvwxyz"
|
||||
}, {
|
||||
range: "bytes=0-100",
|
||||
status: 206,
|
||||
expected: "abcdefghijklmnopqrstuvwxyz"
|
||||
}, {
|
||||
range: "bytes=100-",
|
||||
status: 416,
|
||||
expected: ""
|
||||
}, {
|
||||
range: "bytes=-100",
|
||||
status: 206,
|
||||
expected: "abcdefghijklmnopqrstuvwxyz"
|
||||
}].forEach(test => {
|
||||
promise_test(function() {
|
||||
const abc = "abcdefghijklmnopqrstuvwxyz";
|
||||
const blob = new Blob([abc], { "type": "text/plain" });
|
||||
return fetch(URL.createObjectURL(blob), {
|
||||
headers: {
|
||||
"Range": test.range
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
assert_equals(response.status, test.status);
|
||||
if (response.status != 206) {
|
||||
return "";
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(response => {
|
||||
assert_equals(response, test.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</head>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue