Skip to content

Commit

Permalink
fix(core/http): retry http calls failing due to network issues (#4234)
Browse files Browse the repository at this point in the history
  • Loading branch information
anotherchrisberry authored Oct 9, 2017
1 parent 0041d9b commit 6821001
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 1 deletion.
66 changes: 66 additions & 0 deletions app/scripts/modules/core/src/api/network.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
IDeferred, IHttpInterceptor, IHttpPromiseCallbackArg, IHttpProvider, IPromise, IQService, IRequestConfig,
IWindowService, module
} from 'angular';

/**
* Handles two scenarios:
* 1. computer loses network connection (retries connections when network returns)
* 2. requests are aborted due to a network change (retries immediately)
*/

export class NetworkInterceptor implements IHttpInterceptor {

private networkAvailable: IDeferred<void>;
private retryQueue: IRequestConfig[] = [];

constructor(private $q: IQService,
private $window: IWindowService,
private $injector: any) {
'ngInject';
this.$window.addEventListener('offline', this.handleOffline);
this.$window.addEventListener('online', this.handleOnline);
this.resetNetworkAvailable();
}

private handleOffline(): void {
this.networkAvailable = this.$q.defer();
}

private handleOnline(): void {
this.networkAvailable.resolve();
this.resetNetworkAvailable();
}

private resetNetworkAvailable(): void {
this.networkAvailable = this.$q.defer();
this.networkAvailable.resolve();
}

private removeFromQueue(config: IRequestConfig): void {
this.retryQueue = this.retryQueue.filter(c => c !== config);
}

// see http://www.couchcoder.com/angular-1-interceptors-using-typescript for more details on why we need to do this
// in essence, we need to do this because "the ng1 implementation of interceptors only keeps references to the handler
// functions themselves and invokes them directly without any context (stateless) which means we lose `this` inside
// the handlers"
public responseError = <T>(response: IHttpPromiseCallbackArg<T>): IPromise<T> => {
const { config, status } = response;
// status of -1 indicates the request was aborted, retry if we haven't already
if (status === -1 && !this.retryQueue.includes(config)) {
return this.networkAvailable.promise.then(() => {
return this.$q.resolve(this.$injector.get('$http')(config))
.finally(() => this.removeFromQueue(config));
});
}
return this.$q.reject(response).finally(() => this.removeFromQueue(config));
}
}

export const NETWORK_INTERCEPTOR = 'spinnaker.core.network.interceptor';
module(NETWORK_INTERCEPTOR, [])
.service('networkInterceptor', NetworkInterceptor)
.config(($httpProvider: IHttpProvider) => {
$httpProvider.interceptors.push('networkInterceptor');
});
8 changes: 7 additions & 1 deletion app/scripts/modules/core/src/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ import { HELP_MODULE } from './help/help.module';
import { INSIGHT_NGMODULE } from './insight/insight.module';
import { INTERCEPTOR_MODULE } from './interceptor/interceptor.module';
import { LOAD_BALANCER_MODULE } from './loadBalancer/loadBalancer.module';

import { NETWORK_INTERCEPTOR } from './api/network.interceptor';

import { PAGE_TITLE_MODULE } from './pageTitle/pageTitle.module';
import { PIPELINE_TEMPLATE_MODULE } from './pipeline/config/templates/pipelineTemplate.module';
import { REACT_MODULE } from './reactShims';
Expand Down Expand Up @@ -100,8 +103,12 @@ module(CORE_MODULE, [

LOAD_BALANCER_MODULE,

MANIFEST_MODULE,

require('./modal/modal.module').name,

NETWORK_INTERCEPTOR,

require('./notification/notifications.module').name,

PAGE_TITLE_MODULE,
Expand All @@ -116,7 +123,6 @@ module(CORE_MODULE, [
require('./securityGroup/securityGroup.module').name,
SERVERGROUP_MODULE,
SUBNET_MODULE,
MANIFEST_MODULE,

require('./task/task.module').name,

Expand Down

0 comments on commit 6821001

Please sign in to comment.