Skip to content

Errors on 200 Response for entire-object range request #806

Description

@kdn36

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions