Skip to content

Commit

Permalink
fix(net): Fix HEAD requests in new Chromium (#5180)
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 #5164
  • Loading branch information
theodab authored and joeyparrish committed Apr 26, 2023
1 parent 64e5373 commit ec301e6
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 ec301e6

Please sign in to comment.