Describe the bug
When issuing a range request matching the entire content of an object_store, the object_store refuses an HTTP/200 response containing range headers.
Trace:
GET /data.bin HTTP/1.1
range: bytes=0-11
accept: */*
user-agent: object_store/0.14.1
host: 127.0.0.1:8000
HTTP/1.1 200 OK
content-length: 12
content-range: bytes 0-11/12
content-type: application/octet-stream
last-modified: Fri, 17 Jul 2026 08:44:44 GMT
etag: "2c6d9c:c:6a59eb7c:2f8f2e0c"
content-disposition: attachment; filename="data.bin"
accept-ranges: bytes
date: Fri, 17 Jul 2026 08:47:00 GMT
Relevant code section likely here:
|
// We expect a 206 Partial Content response if a range was requested |
|
// a 200 OK response would indicate the server did not fulfill the request |
|
if has_range && res.status() != StatusCode::PARTIAL_CONTENT { |
|
return Err(crate::Error::NotSupported { |
|
source: Box::new(Error::RangeNotSupported { |
|
href: path.to_string(), |
|
}), |
The request/response is legal as far as I can tell. Minor, the content-range in the response should be ignored per the RFC.
To Reproduce
This uses miniserve, which uses Actix under the hood.
//! HTTP store rejects a range request that covers the entire object when the
//! server (miniserve, or anything actix-files based) replies `200 OK` with the
//! full body instead of `206 Partial Content`.
//!
//! Setup:
//! mkdir -p /tmp/serve && printf 'hello world!' > /tmp/serve/data.bin
//! miniserve --interfaces 127.0.0.1 --port 8000 /tmp/serve &
use object_store::http::HttpBuilder;
use object_store::path::Path;
use object_store::{ClientOptions, GetOptions, GetRange, ObjectStore, ObjectStoreExt};
#[tokio::main]
async fn main() {
let store = HttpBuilder::new()
.with_url("http://127.0.0.1:8000")
.with_client_options(ClientOptions::new().with_allow_http(true))
.build()
.unwrap();
let path = Path::from("data.bin");
let size = store.head(&path).await.unwrap().size;
println!("object size: {size}");
for range in [0..size - 1, 0..size] {
let opts = GetOptions {
range: Some(GetRange::Bounded(range.clone())),
..Default::default()
};
match store.get_opts(&path, opts).await {
Ok(r) => println!("range {range:?} -> ok, {} bytes", r.bytes().await.unwrap().len()),
Err(e) => println!("range {range:?} -> ERROR: {e}"),
}
}
}
gives:
(a) on miniserve:
object size: 12
range 0..11 -> ok, 11 bytes
range 0..12 -> ERROR: Operation not supported: Range request not supported by data.bin
(b) on npx http-server:
object size: 12
range 0..11 -> ok, 11 bytes
range 0..12 -> ok, 12 bytes
Expected behavior
It should accept a 200 response on a full-object range request.
Additional context
Master issue ref:
pola-rs/polars#28400
Per AI analysis:
| server |
partial 0-10 |
full-file 0-11 |
| nginx 1.24 |
206 |
206, Content-Range: bytes 0-11/12 |
| node http-server (npm) |
206 |
206 |
| miniserve / actix-files |
206 |
200 (+ stray Content-Range) |
| python3 -m http.server |
200 (no range support at all) |
200 |
Describe the bug
When issuing a range request matching the entire content of an object_store, the object_store refuses an HTTP/200 response containing range headers.
Trace:
Relevant code section likely here:
arrow-rs-object-store/src/http/client.rs
Lines 404 to 410 in c7316d2
The request/response is legal as far as I can tell. Minor, the content-range in the response should be ignored per the RFC.
To Reproduce
This uses miniserve, which uses Actix under the hood.
gives:
(a) on miniserve:
(b) on npx http-server:
Expected behavior
It should accept a 200 response on a full-object range request.
Additional context
Master issue ref:
pola-rs/polars#28400
Per AI analysis: