open-consul/ui-v2/app/services/client/http.js
John Cowen 746201ed49 ui: Adds XHR connection management to HTTP/1.1 installs (#5083)
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
2019-05-01 18:22:02 +00:00

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);
},
});