Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upstream merge again for updated codebase #13

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"url": "git+https://github.com/google/neuroglancer.git"
},
"engines": {
"node": ">=20.10 <21 || >=21.2"
"node": ">=20.11 <21 || >=21.2"
},
"browserslist": [
"last 2 Chrome versions",
Expand Down
170 changes: 84 additions & 86 deletions src/kvstore/special/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {
} from "#src/kvstore/index.js";
import { composeByteRangeRequest } from "#src/kvstore/index.js";
import { uncancelableToken } from "#src/util/cancellation.js";
import { HttpError, isNotFoundError } from "#src/util/http_request.js";
import { isNotFoundError } from "#src/util/http_request.js";
import type { SpecialProtocolCredentialsProvider } from "#src/util/special_protocol_request.js";
import { cancellableFetchSpecialOk } from "#src/util/special_protocol_request.js";

Expand Down Expand Up @@ -85,97 +85,95 @@ class SpecialProtocolKvStore implements ReadableKvStore {
const { cancellationToken = uncancelableToken } = options;
let { byteRange: byteRangeRequest } = options;
const url = this.baseUrl + key;
for (let attempt = 0; ; ++attempt) {
try {
const requestInit: RequestInit = {};
const rangeHeader = getRangeHeader(byteRangeRequest);
if (rangeHeader !== undefined) {
requestInit.headers = { range: rangeHeader };
requestInit.cache = byteRangeCacheMode;

try {
// The HTTP spec supports suffixLength requests directly via "Range:
// bytes=-N" requests, which avoids the need for a separate HEAD request.
// However, per
// https://fetch.spec.whatwg.org/#cors-safelisted-request-header a suffix
// length byte range request header will always trigger an OPTIONS preflight
// request, which would otherwise be avoided. This negates the benefit of
// using a suffixLength request directly. Additionally, some servers such as
// the npm http-server package and https://uk1s3.embassy.ebi.ac.uk/ do not
// correctly handle suffixLength requests or do not correctly handle CORS
// preflight requests. To avoid those issues, always just issue a separate
// HEAD request to determine the length.
let totalSize: number | undefined;
if (
byteRangeRequest !== undefined &&
"suffixLength" in byteRangeRequest
) {
const totalSize = await this.getObjectLength(url, options);
byteRangeRequest = composeByteRangeRequest(
{ offset: 0, length: totalSize },
byteRangeRequest,
).outer;
}
const requestInit: RequestInit = {};
const rangeHeader = getRangeHeader(byteRangeRequest);
if (rangeHeader !== undefined) {
requestInit.headers = { range: rangeHeader };
requestInit.cache = byteRangeCacheMode;
}
const { response, data } = await cancellableFetchSpecialOk(
this.credentialsProvider,
url,
requestInit,
async (response) => ({
response,
data: await response.arrayBuffer(),
}),
cancellationToken,
);
let byteRange: ByteRange | undefined;
if (response.status === 206) {
const contentRange = response.headers.get("content-range");
if (contentRange === null) {
// Content-range should always be sent, but some buggy servers don't
// send it.
if (byteRangeRequest !== undefined) {
byteRange = {
offset: byteRangeRequest.offset,
length: data.byteLength,
};
} else {
throw new Error(
"Unexpected HTTP 206 response when no byte range specified.",
);
}
}
const { response, data } = await cancellableFetchSpecialOk(
this.credentialsProvider,
url,
requestInit,
async (response) => ({
response,
data: await response.arrayBuffer(),
}),
cancellationToken,
);
let byteRange: ByteRange | undefined;
let totalSize: number | undefined;
if (response.status === 206) {
const contentRange = response.headers.get("content-range");
if (contentRange === null) {
if (byteRangeRequest !== undefined) {
if ("suffixLength" in byteRangeRequest) {
const objectSize = await this.getObjectLength(url, options);
byteRange = {
offset: objectSize - byteRangeRequest.suffixLength,
length: Number(response.headers.get("content-length")),
};
} else {
byteRange = {
offset: byteRangeRequest.offset,
length: data.byteLength,
};
}
} else {
throw new Error(
"Unexpected HTTP 206 response when no byte range specified.",
);
}
if (contentRange !== null) {
const m = contentRange.match(/bytes ([0-9]+)-([0-9]+)\/([0-9]+|\*)/);
if (m === null) {
throw new Error(
`Invalid content-range header: ${JSON.stringify(contentRange)}`,
);
}
if (contentRange !== null) {
const m = contentRange.match(
/bytes ([0-9]+)-([0-9]+)\/([0-9]+|\*)/,
const beginPos = parseInt(m[1], 10);
const endPos = parseInt(m[2], 10);
if (endPos !== beginPos + data.byteLength - 1) {
throw new Error(
`Length in content-range header ${JSON.stringify(
contentRange,
)} does not match content length ${data.byteLength}`,
);
if (m === null) {
throw new Error(
`Invalid content-range header: ${JSON.stringify(contentRange)}`,
);
}
const beginPos = parseInt(m[1], 10);
const endPos = parseInt(m[2], 10);
if (endPos !== beginPos + data.byteLength - 1) {
throw new Error(
`Length in content-range header ${JSON.stringify(
contentRange,
)} does not match content length ${data.byteLength}`,
);
}
totalSize = m[3] === "*" ? undefined : parseInt(m[3], 10);
byteRange = { offset: beginPos, length: data.byteLength };
}
if (m[3] !== "*") {
totalSize = parseInt(m[3], 10);
}
byteRange = { offset: beginPos, length: data.byteLength };
}
if (byteRange === undefined) {
byteRange = { offset: 0, length: data.byteLength };
totalSize = data.byteLength;
}
return { data: new Uint8Array(data), dataRange: byteRange, totalSize };
} catch (e) {
if (
attempt === 0 &&
e instanceof HttpError &&
e.status === 416 &&
options.byteRange !== undefined &&
"suffixLength" in options.byteRange
) {
// Some servers, such as the npm http-server package, do not support suffixLength
// byte-range requests.
const contentLengthNumber = await this.getObjectLength(url, options);
byteRangeRequest = composeByteRangeRequest(
{ offset: 0, length: contentLengthNumber },
byteRangeRequest,
).outer;
continue;
}
if (isNotFoundError(e)) {
return undefined;
}
throw e;
}
if (byteRange === undefined) {
byteRange = { offset: 0, length: data.byteLength };
totalSize = data.byteLength;
}
return { data: new Uint8Array(data), dataRange: byteRange, totalSize };
} catch (e) {
if (isNotFoundError(e)) {
return undefined;
}
throw e;
}
}
}
Expand Down
Loading