Skip to content

Commit

Permalink
Fix incorrect 206-partial handling
Browse files Browse the repository at this point in the history
Per [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests),
`Accept-Ranges` response is returned ONLY when using HEAD request without the `Range` header. When asking for a specific range, the server must reply with 206 status.
  • Loading branch information
nyurik committed Nov 7, 2023
1 parent c4f2c25 commit 2f5f932
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub enum HttpError {
ResponseBodyTooLong(usize, usize),
#[error("HTTP error {0}")]
Http(#[from] reqwest::Error),
#[error("{0}")]
InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
}

// This is required because thiserror #[from] does not support two-level conversion.
Expand Down
22 changes: 8 additions & 14 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use async_trait::async_trait;
use bytes::Bytes;
use reqwest::header::{HeaderValue, ACCEPT_RANGES, RANGE};
use reqwest::{Client, IntoUrl, Method, Request, Url};
use reqwest::header::{HeaderValue, RANGE};
use reqwest::{Client, IntoUrl, Method, Request, StatusCode, Url};

use crate::async_reader::AsyncBackend;
use crate::error::{Error, HttpError};
Expand All @@ -20,8 +20,6 @@ impl HttpBackend {
}
}

static VALID_ACCEPT_RANGES: HeaderValue = HeaderValue::from_static("bytes");

#[async_trait]
impl AsyncBackend for HttpBackend {
async fn read_exact(&self, offset: usize, length: usize) -> Result<Bytes, Error> {
Expand All @@ -35,23 +33,19 @@ impl AsyncBackend for HttpBackend {
}

async fn read(&self, offset: usize, length: usize) -> Result<Bytes, Error> {
let mut req = Request::new(Method::GET, self.pmtiles_url.clone());
let range_header = req
.headers_mut()
.entry(RANGE)
.or_insert(HeaderValue::from_static(""));
let end = offset + length - 1;
// This .unwrap() should be safe, since `offset` and `end` will always be valid.
*range_header = HeaderValue::from_str(format!("bytes={offset}-{end}").as_str()).unwrap();
let range = format!("bytes={offset}-{end}");
let range = HeaderValue::try_from(range).map_err(HttpError::from)?;

let response = self.client.execute(req).await?.error_for_status()?;
let mut req = Request::new(Method::GET, self.pmtiles_url.clone());
req.headers_mut().insert(RANGE, range);

if response.headers().get(ACCEPT_RANGES) != Some(&VALID_ACCEPT_RANGES) {
let response = self.client.execute(req).await?.error_for_status()?;
if response.status() != StatusCode::PARTIAL_CONTENT {
return Err(HttpError::RangeRequestsUnsupported.into());
}

let response_bytes = response.bytes().await?;

if response_bytes.len() > length {
Err(HttpError::ResponseBodyTooLong(response_bytes.len(), length).into())
} else {
Expand Down

0 comments on commit 2f5f932

Please sign in to comment.