diff --git a/lib/net/http_plugin.js b/lib/net/http_plugin.js index 6f1db6e2f7..ec647a3fb7 100644 --- a/lib/net/http_plugin.js +++ b/lib/net/http_plugin.js @@ -111,6 +111,8 @@ shaka.net.HttpPlugin = function(uri, request) { }; -shaka.net.NetworkingEngine.registerScheme('http', shaka.net.HttpPlugin); -shaka.net.NetworkingEngine.registerScheme('https', shaka.net.HttpPlugin); +shaka.net.NetworkingEngine.registerScheme('http', shaka.net.HttpPlugin, + shaka.net.NetworkingEngine.PluginPriority.FALLBACK); +shaka.net.NetworkingEngine.registerScheme('https', shaka.net.HttpPlugin, + shaka.net.NetworkingEngine.PluginPriority.FALLBACK); diff --git a/lib/net/networking_engine.js b/lib/net/networking_engine.js index a1b327884d..1742b8de65 100644 --- a/lib/net/networking_engine.js +++ b/lib/net/networking_engine.js @@ -73,10 +73,38 @@ shaka.net.NetworkingEngine.RequestType = { }; +/** + * Priority level for network scheme plugins. + * If multiple plugins are provided for the same scheme, only the + * highest-priority one is used. + * + * @enum {number} + * @export + */ +shaka.net.NetworkingEngine.PluginPriority = { + FALLBACK: 1, + PREFERRED: 2, + APPLICATION: 3 +}; + + +/** + * @typedef {{ + * plugin: shakaExtern.SchemePlugin, + * priority: number + * }} + * @property {shakaExtern.SchemePlugin} plugin + * The associated plugin. + * @property {number} priority + * The plugin's priority. + */ +shaka.net.NetworkingEngine.SchemeObject; + + /** * Contains the scheme plugins. * - * @private {!Object.} + * @private {!Object.} */ shaka.net.NetworkingEngine.schemes_ = {}; @@ -84,14 +112,27 @@ shaka.net.NetworkingEngine.schemes_ = {}; /** * Registers a scheme plugin. This plugin will handle all requests with the * given scheme. If a plugin with the same scheme already exists, it is - * replaced. + * replaced, unless the existing plugin is of higher priority. + * If no priority is provided, this defaults to the highest priority of + * APPLICATION. * * @param {string} scheme * @param {shakaExtern.SchemePlugin} plugin + * @param {number=} opt_priority * @export */ -shaka.net.NetworkingEngine.registerScheme = function(scheme, plugin) { - shaka.net.NetworkingEngine.schemes_[scheme] = plugin; +shaka.net.NetworkingEngine.registerScheme = + function(scheme, plugin, opt_priority) { + goog.asserts.assert(opt_priority == undefined || opt_priority > 0, + 'explicit priority must be > 0'); + var priority = + opt_priority || shaka.net.NetworkingEngine.PluginPriority.APPLICATION; + var existing = shaka.net.NetworkingEngine.schemes_[scheme]; + if (!existing || priority >= existing.priority) + shaka.net.NetworkingEngine.schemes_[scheme] = { + priority: priority, + plugin: plugin + }; }; @@ -354,7 +395,8 @@ shaka.net.NetworkingEngine.prototype.send_ = function( request.uris[index] = uri.toString(); } - var plugin = shaka.net.NetworkingEngine.schemes_[scheme]; + var object = shaka.net.NetworkingEngine.schemes_[scheme]; + var plugin = object ? object.plugin : null; if (!plugin) { return Promise.reject(new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, diff --git a/test/net/networking_engine_unit.js b/test/net/networking_engine_unit.js index 81d9b68d04..5f85642ce1 100644 --- a/test/net/networking_engine_unit.js +++ b/test/net/networking_engine_unit.js @@ -43,6 +43,15 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() { }; }); + function makeResolveScheme(spyName) { + return jasmine.createSpy(spyName).and.callFake( + function() { + return Promise.resolve({ + uri: '', data: new ArrayBuffer(5), headers: {} + }); + }); + } + beforeEach(function() { fakeProtocol = 'http:'; error = new shaka.util.Error( @@ -51,18 +60,15 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() { shaka.util.Error.Code.HTTP_ERROR); networkingEngine = new shaka.net.NetworkingEngine(); - resolveScheme = jasmine.createSpy('resolve scheme').and.callFake( - function() { - return Promise.resolve({ - uri: '', data: new ArrayBuffer(5), headers: {} - }); - }); + resolveScheme = makeResolveScheme('resolve scheme'); rejectScheme = jasmine.createSpy('reject scheme') .and.callFake(function() { return Promise.reject(error); }); shaka.net.NetworkingEngine.registerScheme( - 'resolve', Util.spyFunc(resolveScheme)); + 'resolve', Util.spyFunc(resolveScheme), + shaka.net.NetworkingEngine.PluginPriority.FALLBACK); shaka.net.NetworkingEngine.registerScheme( - 'reject', Util.spyFunc(rejectScheme)); + 'reject', Util.spyFunc(rejectScheme), + shaka.net.NetworkingEngine.PluginPriority.FALLBACK); }); afterEach(function() { @@ -263,13 +269,54 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() { }); describe('request', function() { - it('uses registered schemes', function(done) { - networkingEngine.request(requestType, createRequest('resolve://foo')) + function testResolve(schemeSpy) { + return networkingEngine.request( + requestType, createRequest('resolve://foo')) .catch(fail) .then(function() { - expect(resolveScheme).toHaveBeenCalled(); - done(); + expect(schemeSpy).toHaveBeenCalled(); }); + } + + it('uses registered schemes', function(done) { + testResolve(resolveScheme).then(done); + }); + + it('uses registered scheme plugins in order of priority', function(done) { + var applicationResolveScheme = + makeResolveScheme('application resolve scheme'); + shaka.net.NetworkingEngine.registerScheme( + 'resolve', Util.spyFunc(applicationResolveScheme), + shaka.net.NetworkingEngine.PluginPriority.APPLICATION); + var preferredResolveScheme = + makeResolveScheme('preferred resolve scheme'); + shaka.net.NetworkingEngine.registerScheme( + 'resolve', Util.spyFunc(preferredResolveScheme), + shaka.net.NetworkingEngine.PluginPriority.PREFERRED); + + testResolve(applicationResolveScheme).then(done); + }); + + it('uses newest scheme plugin in case of tie in priority', function(done) { + var secondResolveScheme = makeResolveScheme('second resolve scheme'); + shaka.net.NetworkingEngine.registerScheme( + 'resolve', Util.spyFunc(secondResolveScheme), + shaka.net.NetworkingEngine.PluginPriority.FALLBACK); + + testResolve(secondResolveScheme).then(done); + }); + + it('defaults new scheme plugins to application priority', function(done) { + var secondResolveScheme = makeResolveScheme('second resolve scheme'); + shaka.net.NetworkingEngine.registerScheme( + 'resolve', Util.spyFunc(secondResolveScheme)); + var preferredResolveScheme = + makeResolveScheme('preferred resolve scheme'); + shaka.net.NetworkingEngine.registerScheme( + 'resolve', Util.spyFunc(preferredResolveScheme), + shaka.net.NetworkingEngine.PluginPriority.PREFERRED); + + testResolve(secondResolveScheme).then(done); }); it('can unregister scheme', function(done) { @@ -280,6 +327,22 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() { .then(done); }); + it('unregister removes all plugins for scheme at once', function(done) { + var preferredResolveScheme = + makeResolveScheme('preferred resolve scheme'); + shaka.net.NetworkingEngine.registerScheme( + 'resolve', Util.spyFunc(preferredResolveScheme), + shaka.net.NetworkingEngine.PluginPriority.PREFERRED); + + shaka.net.NetworkingEngine.unregisterScheme('resolve'); + networkingEngine.request(requestType, createRequest('resolve://foo')) + .then(fail) + .catch(function() { + expect(resolveScheme).not.toHaveBeenCalled(); + expect(preferredResolveScheme).not.toHaveBeenCalled(); + }).then(done); + }); + it('rejects if scheme does not exist', function(done) { networkingEngine.request(requestType, createRequest('foo://foo')) .then(fail)