Skip to content

Commit

Permalink
fix(net): Fix HEAD requests in new Chromium
Browse files Browse the repository at this point in the history
This makes it so that we no longer try to download the body of a
head response, in the http fetch plugin. This is necessary due to
an upcoming change to Chromium, where the body object of such
responses is null.

Fixes shaka-project#5164
  • Loading branch information
theodab committed Apr 25, 2023
1 parent 577b0e7 commit b7739bf
Showing 1 changed file with 69 additions and 57 deletions.
126 changes: 69 additions & 57 deletions lib/net/http_fetch_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,65 +120,77 @@ shaka.net.HttpFetchPlugin = class {
// not the body yet.
headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
response.headers));
// Getting the reader in this way allows us to observe the process of
// downloading the body, instead of just waiting for an opaque promise to
// resolve.
// We first clone the response because calling getReader locks the body
// stream; if we didn't clone it here, we would be unable to get the
// response's arrayBuffer later.
const reader = response.clone().body.getReader();

const contentLengthRaw = response.headers.get('Content-Length');
const contentLength =
contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;

const start = (controller) => {
const push = async () => {
let readObj;
try {
readObj = await reader.read();
} catch (e) {
// If we abort the request, we'll get an error here. Just ignore it
// since real errors will be reported when we read the buffer below.
shaka.log.v1('error reading from stream', e.message);
return;
}

if (!readObj.done) {
loaded += readObj.value.byteLength;
if (streamDataCallback) {
await streamDataCallback(readObj.value);

// In new versions of Chromium, HEAD requests now have a response body
// that is null.
// So just don't try to download the body at all, if it's a HEAD request,
// to avoid null reference errors.
// See: https://crbug.com/1297060
if (init.method != 'HEAD') {
goog.asserts.assert(response.body,
'non-HEAD responses should have a body');

// Getting the reader in this way allows us to observe the process of
// downloading the body, instead of just waiting for an opaque promise
// to resolve.
// We first clone the response because calling getReader locks the body
// stream; if we didn't clone it here, we would be unable to get the
// response's arrayBuffer later.
const reader = response.clone().body.getReader();

const contentLengthRaw = response.headers.get('Content-Length');
const contentLength =
contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;

const start = (controller) => {
const push = async () => {
let readObj;
try {
readObj = await reader.read();
} catch (e) {
// If we abort the request, we'll get an error here. Just ignore
// it since real errors will be reported when we read the buffer
// below.
shaka.log.v1('error reading from stream', e.message);
return;
}

if (!readObj.done) {
loaded += readObj.value.byteLength;
if (streamDataCallback) {
await streamDataCallback(readObj.value);
}
}

const currentTime = Date.now();
// If the time between last time and this time we got progress event
// is long enough, or if a whole segment is downloaded, call
// progressUpdated().
if (currentTime - lastTime > 100 || readObj.done) {
progressUpdated(currentTime - lastTime, loaded - lastLoaded,
contentLength - loaded);
lastLoaded = loaded;
lastTime = currentTime;
}

if (readObj.done) {
goog.asserts.assert(!readObj.value,
'readObj should be unset when "done" is true.');
controller.close();
} else {
controller.enqueue(readObj.value);
push();
}
}

const currentTime = Date.now();
// If the time between last time and this time we got progress event
// is long enough, or if a whole segment is downloaded, call
// progressUpdated().
if (currentTime - lastTime > 100 || readObj.done) {
progressUpdated(currentTime - lastTime, loaded - lastLoaded,
contentLength - loaded);
lastLoaded = loaded;
lastTime = currentTime;
}

if (readObj.done) {
goog.asserts.assert(!readObj.value,
'readObj should be unset when "done" is true.');
controller.close();
} else {
controller.enqueue(readObj.value);
push();
}
};
push();
};
push();
};
// Create a ReadableStream to use the reader. We don't need to use the
// actual stream for anything, though, as we are using the response's
// arrayBuffer method to get the body, so we don't store the
// ReadableStream.
new ReadableStream({start}); // eslint-disable-line no-new
arrayBuffer = await response.arrayBuffer();
// Create a ReadableStream to use the reader. We don't need to use the
// actual stream for anything, though, as we are using the response's
// arrayBuffer method to get the body, so we don't store the
// ReadableStream.
new ReadableStream({start}); // eslint-disable-line no-new
arrayBuffer = await response.arrayBuffer();
}
} catch (error) {
if (abortStatus.canceled) {
throw new shaka.util.Error(
Expand Down

0 comments on commit b7739bf

Please sign in to comment.