Skip to content

Commit

Permalink
Enabled asynchronous response filters.
Browse files Browse the repository at this point in the history
Also updated externs.

Issue #610

Change-Id: I4c9539442d6fb77781296ea1acf123b3653e3b40
  • Loading branch information
theodab committed Jan 9, 2017
1 parent 9ce802d commit 8b2212c
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 12 deletions.
9 changes: 7 additions & 2 deletions externs/shaka/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,12 @@ shakaExtern.SchemePlugin;
/**
* Defines a filter for requests. This filter takes the request and modifies
* it before it is sent to the scheme plugin.
* A request filter can run asynchronously by returning a promise; in this case,
* the request will not be sent until the promise is resolved.
*
* @typedef {!function(shaka.net.NetworkingEngine.RequestType,
* shakaExtern.Request)}
* shakaExtern.Request):
(Promise|undefined)}
* @exportDoc
*/
shakaExtern.RequestFilter;
Expand All @@ -142,9 +145,11 @@ shakaExtern.RequestFilter;
/**
* Defines a filter for responses. This filter takes the response and modifies
* it before it is returned.
* A response filter can run asynchronously by returning a promise.
*
* @typedef {!function(shaka.net.NetworkingEngine.RequestType,
* shakaExtern.Response)}
* shakaExtern.Response):
(Promise|undefined)}
* @exportDoc
*/
shakaExtern.ResponseFilter;
1 change: 1 addition & 0 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,7 @@ shaka.dash.DashParser.prototype.requestInitSegment_ = function(
*
* @param {shaka.net.NetworkingEngine.RequestType} type
* @param {!shakaExtern.Response} response
* @return {(Promise|undefined)}
* @private
*/
shaka.dash.DashParser.prototype.emsgResponseFilter_ = function(type, response) {
Expand Down
32 changes: 22 additions & 10 deletions lib/net/networking_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,18 +357,30 @@ shaka.net.NetworkingEngine.prototype.send_ = function(
return plugin(request.uris[index], request, type).then(function(response) {
if (response.timeMs === undefined)
response.timeMs = Date.now() - startTimeMs;

// Since we are inside a promise, no need to catch errors; they will result
// in a failed promise.
var filterStartMs = Date.now();
var responseFilters = this.responseFilters_;
for (var i = 0; i < responseFilters.length; i++) {
responseFilters[i](type, response);
}
response.timeMs += Date.now() - filterStartMs;
response.timeMs += requestFilterTime;

return response;
var responseFilters = this.responseFilters_;
var p = Promise.resolve();
responseFilters.forEach(function(responseFilter) {
// Response filters are resolved sequentially.
p = p.then(function() {
return Promise.resolve(responseFilter(type, response));
}.bind(this));
});

// Catch any errors thrown by response filters, and substitute
// them with a Shaka-native error.
p = p.catch(function(e) {
throw new shaka.util.Error(shaka.util.Error.Category.NETWORK,
shaka.util.Error.Code.RESPONSE_FILTER_ERROR, e);
});

return p.then(function() {
response.timeMs += Date.now() - filterStartMs;
response.timeMs += requestFilterTime;

return response;
});
}.bind(this));
};

Expand Down
6 changes: 6 additions & 0 deletions lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ shaka.util.Error.Code = {
*/
'REQUEST_FILTER_ERROR': 1006,

/**
* A response filter threw an error.
* <br> error.data[0] is the original error.
*/
'RESPONSE_FILTER_ERROR': 1007,


/** The text parser failed to parse a text stream due to an invalid header. */
'INVALID_TEXT_HEADER': 2000,
Expand Down
74 changes: 74 additions & 0 deletions test/net/networking_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,21 +328,33 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() {
});

it('waits for asynchronous filters', function(done) {
var responseFilter = jasmine.createSpy('response filter');
networkingEngine.registerResponseFilter(responseFilter);

var p = new shaka.util.PublicPromise();
var p2 = new shaka.util.PublicPromise();
filter.and.returnValue(p);
responseFilter.and.returnValue(p2);
var request = createRequest('resolve://foo');
var r = networkingEngine.request(requestType, request);
Util.capturePromiseStatus(r);

Util.delay(0.1).then(function() {
expect(filter).toHaveBeenCalled();
expect(resolveScheme).not.toHaveBeenCalled();
expect(responseFilter).not.toHaveBeenCalled();
expect(r.status).toBe('pending');
p.resolve();

return Util.delay(0.1);
}).then(function() {
expect(resolveScheme).toHaveBeenCalled();
expect(responseFilter).toHaveBeenCalled();
expect(r.status).toBe('pending');
p2.resolve();

return Util.delay(0.1);
}).then(function() {
expect(r.status).toBe('resolved');
done();
}).catch(fail);
Expand Down Expand Up @@ -546,6 +558,68 @@ describe('NetworkingEngine', /** @suppress {accessControls} */ function() {
});
});

it('applies response filters sequentially', function(done) {
var secondFilter = jasmine.createSpy('second response filter');
networkingEngine.registerResponseFilter(secondFilter);

var order = 0;
filter.and.callFake(function() {
expect(order).toBe(0);
order += 1;
});
secondFilter.and.callFake(function() {
expect(order).toBe(1);
order += 1;
});

networkingEngine.request(requestType, createRequest('resolve://foo'))
.catch(fail)
.then(done);
});

it('turns errors into shaka errors', function(done) {
var fakeError = 'fake error';
filter.and.callFake(function() {
throw fakeError;
});
networkingEngine.request(requestType, createRequest('resolve://foo'))
.then(fail)
.catch(function(e) {
expect(e.code).toBe(shaka.util.Error.Code.RESPONSE_FILTER_ERROR);
expect(e.data).toEqual([fakeError]);
done();
});
});

it('can modify responses asynchronously', function(done) {
var p = new shaka.util.PublicPromise();
filter.and.callFake(function(type, response) {
return p.then(function() {
expect(response.headers).toBeTruthy();
response.headers['DATE'] = 'CAT';
response.data = new ArrayBuffer(5);
});
});

var request = createRequest('resolve://foo');
var r = networkingEngine.request(requestType, request)
.catch(fail)
.then(function(response) {
expect(response).toBeTruthy();
expect(response.headers['DATE']).toBe('CAT');
expect(response.data.byteLength).toBe(5);
done();
});
Util.capturePromiseStatus(r);

Util.delay(0.1).then(function() {
expect(filter).toHaveBeenCalled();
expect(r.status).toBe('pending');

p.resolve();
});
});

it('if throws will stop requests', function(done) {
filter.and.throwError(new Error());
networkingEngine.request(requestType, createRequest('resolve://foo'))
Expand Down

0 comments on commit 8b2212c

Please sign in to comment.