Skip to content

Commit

Permalink
Detect HTTP 302 redirects
Browse files Browse the repository at this point in the history
When possible, use xhr.responseURL to detect 302 redirects and make
the information available in the Response object.

Use redirect URI as BaseURL for manifest parsing.

See also #225, #266

Change-Id: Ie24abeb3b8418b3e89fed6666eb525aecd74f03b
  • Loading branch information
joeyparrish committed Jan 14, 2016
1 parent 54fa88f commit 075af4e
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 18 deletions.
4 changes: 4 additions & 0 deletions externs/shaka/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ shakaExtern.Request;

/**
* @typedef {{
* uri: string,
* data: ArrayBuffer,
* headers: !Object.<string, string>
* }}
Expand All @@ -94,6 +95,9 @@ shakaExtern.Request;
* This is given back from the scheme plugin. This is passed to a response
* filter before being returned from the request call.
*
* @property {string} uri
* The URI which was loaded. Request filters and server redirects can cause
* this to be different from the original request URIs.
* @property {ArrayBuffer} data
* The body of the response.
* @property {!Object.<string, string>} headers
Expand Down
9 changes: 6 additions & 3 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ shaka.dash.DashParser.prototype.requestManifest_ = function() {
return this.networkingEngine_.request(requestType, request)
.then(function(response) {
// This may throw; but it will result in a failed promise.
return this.parseManifest_(response.data);
return this.parseManifest_(response.data, response.uri);
}.bind(this));
};

Expand All @@ -325,11 +325,14 @@ shaka.dash.DashParser.prototype.requestManifest_ = function() {
* stored manifest.
*
* @param {!ArrayBuffer} data
* @param {string} finalManifestUri The final manifest URI, which may
* differ from this.manifestUri_ is there has been a redirect.
* @return {shakaExtern.Manifest}
* @throws shaka.util.Error When there is a parsing error.
* @private
*/
shaka.dash.DashParser.prototype.parseManifest_ = function(data) {
shaka.dash.DashParser.prototype.parseManifest_ =
function(data, finalManifestUri) {
var XmlUtils = shaka.util.XmlUtils;

var string = shaka.util.Uint8ArrayUtils.toString(new Uint8Array(data));
Expand All @@ -346,7 +349,7 @@ shaka.dash.DashParser.prototype.parseManifest_ = function(data) {
}

var uris = XmlUtils.findChildren(mpd, 'BaseURL').map(XmlUtils.getContents);
var baseUris = shaka.dash.MpdUtils.resolveUris([this.manifestUri_], uris);
var baseUris = shaka.dash.MpdUtils.resolveUris([finalManifestUri], uris);
/** @type {shaka.dash.DashParser.Context} */
var context = {
period: null,
Expand Down
3 changes: 2 additions & 1 deletion lib/net/data_uri_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ shaka.net.DataUriPlugin = function(uri, request) {
}
data = shaka.util.Uint8ArrayUtils.fromString(data).buffer;

var response = {data: data, headers: {}};
/** @type {shakaExtern.Response} */
var response = {uri: uri, data: data, headers: {}};
resolve(response);
});
};
Expand Down
6 changes: 5 additions & 1 deletion lib/net/http_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ shaka.net.HttpPlugin = function(uri, request) {
return all;
},
{});
var response = {data: target.response, headers: headers};
if (target.responseURL) {
uri = target.responseURL;
}
/** @type {shakaExtern.Response} */
var response = {uri: uri, data: target.response, headers: headers};
resolve(response);
} else {
reject(new shaka.util.Error(
Expand Down
63 changes: 63 additions & 0 deletions spec/dash_parser_live_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,69 @@ describe('DashParser.Live', function() {
.then(done);
});

it('uses redirect URL for manifest BaseURL', function(done) {
var template = [
'<MPD type="dynamic" minimumUpdatePeriod="PT%(updatePeriod)dS">',
' <Period id="1" duration="PT10S">',
' <AdaptationSet id="2" mimeType="video/mp4">',
' <Representation id="3" bandwidth="500">',
' <SegmentTemplate startNumber="1" media="s$Number$.mp4">',
' <SegmentTimeline>',
' <S d="10" t="0" />',
' <S d="5" />',
' <S d="15" />',
' </SegmentTimeline>',
' </SegmentTemplate>',
' </Representation>',
' </AdaptationSet>',
' </Period>',
'</MPD>'
].join('\n');
var manifestText = sprintf(template, {updatePeriod: updateTime});
var manifestData = Uint8ArrayUtils.fromString(manifestText).buffer;
var originalUri = 'http://example.com/';
var redirectedUri = 'http://redirected.com/';

// The initial manifest request will be redirected.
fakeNetEngine.request.and.returnValue(
Promise.resolve({uri: redirectedUri, data: manifestData}));

parser.start(originalUri)
.then(function(manifest) {
// The manifest request was made to the original URL.
expect(fakeNetEngine.request.calls.count()).toBe(1);
var netRequest = fakeNetEngine.request.calls.argsFor(0)[1];
expect(netRequest.uris).toEqual([originalUri]);

// Since the manifest request was redirected, the segment refers to
// the redirected base.
var stream = manifest.periods[0].streamSets[0].streams[0];
var segmentUri = stream.getSegmentReference(1).uris[0];
expect(segmentUri).toBe(redirectedUri + 's1.mp4');

// The update request will not redirect.
fakeNetEngine.request.and.returnValue(
Promise.resolve({uri: originalUri, data: manifestData}));
fakeNetEngine.request.calls.reset();
return waitForManifestUpdate().then(function() {
// The update request was made to the original URL.
expect(fakeNetEngine.request.calls.count()).toBe(1);
var netRequest = fakeNetEngine.request.calls.argsFor(0)[1];
expect(netRequest.uris).toEqual([originalUri]);

// Since the update was not redirected, the segment refers to
// the original base again.
var stream = manifest.periods[0].streamSets[0].streams[0];
var segmentUri = stream.getSegmentReference(1).uris[0];
expect(segmentUri).toBe(originalUri + 's1.mp4');
// NOTE: the bases of segment references are never updated for
// SegmentTemplate+duration.
});
})
.catch(fail)
.then(done);
});

it('failures in update call error callback', function(done) {
var lines = [
'<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />'
Expand Down
9 changes: 1 addition & 8 deletions spec/dash_parser_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,7 @@ function verifySegmentIndex(manifest, references) {
*/
function dashTestSegmentIndex(done, manifestText, references) {
var dummyUri = 'dummy://foo';
var buffer = shaka.util.Uint8ArrayUtils.fromString(manifestText).buffer;
var fakeNetEngine = {
request: function(type, request) {
expect(request.uris.length).toBe(1);
expect(request.uris[0]).toBe(dummyUri);
return Promise.resolve({data: buffer});
}
};
var fakeNetEngine = new dashFakeNetEngine(manifestText);
var dashParser = new shaka.dash.DashParser(fakeNetEngine, {}, function() {});
dashParser.start(dummyUri)
.then(function(manifest) { verifySegmentIndex(manifest, references); })
Expand Down
1 change: 1 addition & 0 deletions spec/data_uri_plugin_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe('DataUriPlugin', function() {
shaka.net.DataUriPlugin(uri, {})
.then(function(response) {
expect(response).toBeTruthy();
expect(response.uri).toBe(uri);
expect(response.data).toBeTruthy();
var array = new Uint8Array(response.data);
var data = shaka.util.Uint8ArrayUtils.toString(array);
Expand Down
17 changes: 16 additions & 1 deletion spec/http_plugin_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ describe('HttpPlugin', function() {
'status': 204,
'responseHeaders': { 'FOO': 'BAR' }
});
jasmine.Ajax.stubRequest('https://foo.bar/302').andReturn({
'response': new ArrayBuffer(10),
'status': 200,
'responseHeaders': { 'FOO': 'BAR' },
'responseURL': 'https://foo.bar/after/302'
});
jasmine.Ajax.stubRequest('https://foo.bar/404').andReturn({
'response': new ArrayBuffer(0),
'status': 404
Expand Down Expand Up @@ -70,6 +76,14 @@ describe('HttpPlugin', function() {
testSucceeds('https://foo.bar/204', done);
});

// Disabled until responseURL patch is accepted in jasmine-ajax.
// Until then, we can't mock responseURL.
// See jasmine/jasmine-ajax#145
xit('gets redirect URLs with 302 status', function(done) {
testSucceeds('https://foo.bar/302', done,
'https://foo.bar/after/302');
});

it('fails if non-2xx status', function(done) {
testFails('https://foo.bar/404', done);
});
Expand All @@ -82,13 +96,14 @@ describe('HttpPlugin', function() {
testFails('https://foo.bar/error', done);
});

function testSucceeds(uri, done) {
function testSucceeds(uri, done, opt_overrideUri) {
var request = {uris: [uri]};
shaka.net.HttpPlugin(uri, request)
.catch(fail)
.then(function(response) {
expect(jasmine.Ajax.requests.mostRecent().url).toBe(uri);
expect(response).toBeTruthy();
expect(response.uri).toBe(opt_overrideUri || uri);
expect(response.data).toBeTruthy();
expect(response.data.byteLength).toBe(10);
expect(response.headers).toBeTruthy();
Expand Down
16 changes: 12 additions & 4 deletions spec/networking_engine_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ describe('NetworkingEngine', function() {
networkingEngine = new shaka.net.NetworkingEngine();
resolveScheme = jasmine.createSpy('resolve scheme').and.callFake(
function() {
return Promise.resolve({data: new ArrayBuffer(5), headers: {}});
return Promise.resolve({
uri: '', data: new ArrayBuffer(5), headers: {}
});
});
rejectScheme = jasmine.createSpy('reject scheme')
.and.callFake(function() { return Promise.reject(); });
Expand All @@ -55,7 +57,9 @@ describe('NetworkingEngine', function() {
if (rejectScheme.calls.count() == 1)
return Promise.reject();
else
return Promise.resolve({data: new ArrayBuffer(0), headers: {}});
return Promise.resolve({
uri: '', data: new ArrayBuffer(0), headers: {}
});
});
networkingEngine.request(requestType, request)
.catch(fail)
Expand All @@ -74,7 +78,9 @@ describe('NetworkingEngine', function() {
if (rejectScheme.calls.count() < 3)
return Promise.reject();
else
return Promise.resolve({data: new ArrayBuffer(0), headers: {}});
return Promise.resolve({
uri: '', data: new ArrayBuffer(0), headers: {}
});
});
networkingEngine.request(requestType, request)
.catch(fail)
Expand Down Expand Up @@ -336,7 +342,9 @@ describe('NetworkingEngine', function() {
filter = jasmine.createSpy('response filter');
networkingEngine.registerResponseFilter(filter);
resolveScheme.and.callFake(function(request) {
var response = {data: new ArrayBuffer(100), headers: {}};
var response = {
uri: '', data: new ArrayBuffer(100), headers: {}
};
return Promise.resolve(response);
});
});
Expand Down

0 comments on commit 075af4e

Please sign in to comment.