746201ed49
Adds xhr connection managment to http/1.1 installs This includes various things: 1. An object pool to 'acquire', 'release' and 'dispose' of objects, also a 'purge' to completely empty it 2. A `Request` data object, mainly for reasoning about the object better 3. A pseudo http 'client' which doens't actually control the request itself but does help to manage the connections An initializer is used to detect the script element of the consul-ui sourcecode which we use later to sniff the protocol that we are most likely using for API access
88 lines
3 KiB
JavaScript
88 lines
3 KiB
JavaScript
import Service, { inject as service } from '@ember/service';
|
|
import { get, set } from '@ember/object';
|
|
import { Promise } from 'rsvp';
|
|
|
|
import getObjectPool from 'consul-ui/utils/get-object-pool';
|
|
import Request from 'consul-ui/utils/http/request';
|
|
|
|
const dispose = function(request) {
|
|
if (request.headers()['content-type'] === 'text/event-stream') {
|
|
const xhr = request.connection();
|
|
// unsent and opened get aborted
|
|
// headers and loading means wait for it
|
|
// to finish for the moment
|
|
if (xhr.readyState) {
|
|
switch (xhr.readyState) {
|
|
case 0:
|
|
case 1:
|
|
xhr.abort();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return request;
|
|
};
|
|
export default Service.extend({
|
|
dom: service('dom'),
|
|
init: function() {
|
|
this._super(...arguments);
|
|
let protocol = 'http/1.1';
|
|
try {
|
|
protocol = performance.getEntriesByType('resource').find(item => {
|
|
// isCurrent is added in initializers/client and is used
|
|
// to ensure we use the consul-ui.js src to sniff what the protocol
|
|
// is. Based on the assumption that whereever this script is it's
|
|
// likely to be the same as the xmlhttprequests
|
|
return item.initiatorType === 'script' && this.isCurrent(item.name);
|
|
}).nextHopProtocol;
|
|
} catch (e) {
|
|
// pass through
|
|
}
|
|
let maxConnections;
|
|
// http/2, http2+QUIC/39 and SPDY don't have connection limits
|
|
switch (true) {
|
|
case protocol.indexOf('h2') === 0:
|
|
case protocol.indexOf('hq') === 0:
|
|
case protocol.indexOf('spdy') === 0:
|
|
break;
|
|
default:
|
|
// generally 6 are available
|
|
// reserve 1 for traffic that we can't manage
|
|
maxConnections = 5;
|
|
break;
|
|
}
|
|
set(this, 'connections', getObjectPool(dispose, maxConnections));
|
|
if (typeof maxConnections !== 'undefined') {
|
|
set(this, 'maxConnections', maxConnections);
|
|
const doc = get(this, 'dom').document();
|
|
// when the user hides the tab, abort all connections
|
|
doc.addEventListener('visibilitychange', e => {
|
|
if (e.target.hidden) {
|
|
get(this, 'connections').purge();
|
|
}
|
|
});
|
|
}
|
|
},
|
|
whenAvailable: function(e) {
|
|
const doc = get(this, 'dom').document();
|
|
// if we are using a connection limited protocol and the user has hidden the tab (hidden browser/tab switch)
|
|
// any aborted errors should restart
|
|
if (typeof get(this, 'maxConnections') !== 'undefined' && doc.hidden) {
|
|
return new Promise(function(resolve) {
|
|
doc.addEventListener('visibilitychange', function listen(event) {
|
|
doc.removeEventListener('visibilitychange', listen);
|
|
resolve(e);
|
|
});
|
|
});
|
|
}
|
|
return Promise.resolve(e);
|
|
},
|
|
request: function(options, xhr) {
|
|
const request = new Request(options.type, options.url, { body: options.data || {} }, xhr);
|
|
return get(this, 'connections').acquire(request, request.getId());
|
|
},
|
|
complete: function() {
|
|
return get(this, 'connections').release(...arguments);
|
|
},
|
|
});
|