$http interceptor is causing an infinite loop - angularjs

I'm building an AngularJs app working with a custom API, the latter needs a valid authorization token, if it isn't, my API returns a 4O1 HTTP status code.
Thus, I use a http interceptor which is meant to ask for a new token before retrying the previous request.
app.factory('httpResponseErrorInterceptor', ['$q', '$injector', function($q, $injector, $http) {
return {
'responseError': function(response) {
if (response.status === 401) {
// should retry
let deferred = $q.defer();
let $http = $injector.get('$http');
$.ajax({
url : PUBLIC_API_URI,
type : 'HEAD',
beforeSend : function(request) {
request.setRequestHeader('Authorization', api_access_key);
},
success : function(result, status, xhr) {
console.log("error -> ok : should retry");
deferred.resolve(xhr.getResponseHeader('Authorization'));
},
error : function(xhr, status, error) {
console.log("error -> bad : should give up");
deferred.resolve(null);
}
});
console.log(deferred.promise);
if(deferred.promise == null)
return $q.reject(response);
response.config['Authorization'] = api_access_key = deferred.promise;
return $http(response.config);
}
return $q.reject(response);
}
};
}]);
I used JQuery.ajax in my interceptor because I guessed that using $http was causing the infinite loop when the token-renewal request resulted in an error.
But it still causes an infinite loop on error :
original-request -> renewal-request ...

i don't see the complete log of the error but i guess the problem could be related to the injection of $http service (that is still there even if you're using $.ajax) inside an http interceptor. this will cause a circular dependency because angular will face an infinite loop trying to resolve both the dependency of $http and its interceptor.
see my previous answer here
if you need to make an ajax call try injecting the service "by need"
angular.module('myApp').factory('interceptor', function($injector){
return {
'responseError': function(response) {
var http = $injector.get('$http');
http.get..
}
}
});

Related

Get headers when ng-view changes

All of our content pages have a particular header, X-Foo. When the content of the ng-view changes, we want to display the new page's X-Foo header in a different element. How can we get this value whenever the content changes?
EDIT: Since this was apparently unclear, the header is expected in the response, not the request.
You can use a httpInterceptor for this. HTTP interceptors are a great way to define behavior in a single place for how a request or response is handled for all HTTP calls using the $http service
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}).factory('httpInterceptor', function (liveInterviewConfiguration) {
return {
request : function (request) {
console.info("Your request headers are");
console.info(request.headers);
return request;
},
requestError : function (error) {
return error;
},
response : function (response) {
return response;
},
responseError : function (error) {
return error;
}
};
});
Can you access the headers in the controller with $http? I've nothing that has changing headers readily available to test this with.
controller('SampleCtrl', function($http, $scope) {
// Add headers from $http
$scope.http = $http.defaults.headers.common;
});
Alternatively, if that does not work, you may want to look at using http interceptors.
.config(function($routeProvider, $locationProvider, $httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'response': function(response) {
// do something on success
console.log(response);
return response;
}
};
});
}

Error status codes are not being caught

I am trying to catch angular resource's HTTP error status code (!=200).
My Service, where I have resources defined:
(apiService.js)
.factory('ApiService', function($resource, $http, localStorageService, CONFIG) {
var base_api_url = api_url = CONFIG.api_url, api_version_prefix = CONFIG.api_version_prefix;
return {
userDevices: $resource(api_url+'/requestRegistration/userDevices/:action', {}, {
registerDevice: {
method: 'POST',
params: {
action: ''
}
},
verify: {
method: 'POST',
params: {
action: 'verify'
}
},
}
}
});
My controller's code:
.controller('LoginCtrl', function(CONFIG, $scope, $state, $ionicPlatform, $ionicPopup, ApiService) {
$scope.data = {
username: null
};
$scope.registerDevice = function() {
if($scope.data.username) {
var authenticationResponse = ApiService.userDevices.registerDevice({
username: $scope.data.username
});
authenticationResponse.$promise.then(function(result) {
// this is always fired, even then response code is 400 or 503 :( I am not able to check response status code.
console.log(result);
console.log('success!');
}, function(error){
// this code is not being exectued even when response status code is different then 200
// its never executed at all :(
console.log('error!');
});
}
};
});
When I send the request and I receive response code 400/503, I believe that function(error) code should be executed but it's not.
Instead, my code in $promise.then(function(result)(...) is executed and I am not able to detect a response HTTP status code.
So, my questions:
Why isn't my error handling function being executed?
How can I detect HTTP response status codes?
The first .catch is converting rejections to fulfilled. To prevent conversion, the .catch method needs to throw the error.
authenticationResponse.$promise.catch(function(error){
alert('catched error!!!');
//throw to chain error
throw error;
}).then(function(result) {
// this is always fired, even then response code is 400 or 503 :(
console.log(result);
console.log('success!');
//return to chain data
return result
}, function(error){
// This should be executed when status code is different then 200?
// its never executed at all :(
console.log('error!');
//throw to chain rejection
throw error;
});
When a function omits a return or throw statement, it returns undefined. The $q service creates a derived promise that resolves to undefined.
Diagnosing ngResource Problems
To diagnose problems with $resource methods, add a response interceptor:
userDevices: $resource(api_url+'/requestRegistration/userDevices/:action', {}, {
registerDevice: {
method: 'POST',
params: {
action: ''
},
interceptor: {
response: function (response) {
console.log("registerDevice success");
console.log(response.status);
return response;
},
errorResponse: function (errorResponse) {
console.log("registerDevice error");
console.log(errorResponse.status);
throw errorResponse;
}
}
},
verify: {
method: 'POST',
The other thing to look for is other $http interceptors in the App converting responses by omitting a throw statement.
There is a function to capture the response of the HTTP status in Angular. You can look to see how it is done here stack overflow http response
You can use an interceptor.
Intercept requests before they are handed to the server and responses
before they are handed over to the application code that initiated
these requests
So,
This will catch all response errors originating from $http which $resource.
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(response) {
if (response.status == 400) {
// Handle 400 error code
}
if (response.status == 503) {
// Handle 503 error code
}
// Rejects the derived promise.
return $q.reject(response);
}
};
});

AngularJS $HTTP error status of -1

I currently have an error intercept set up in AngularJS to catch any failed HTTP requests so that I can notify myself when it occurs. Occasionally (but often enough) I get an error with a status of -1. I have no idea what this means or why its occurring... Anyone have any explanations?
appService.factory("$errorInterceptor", ['$q', '$injector', function ($q,$injector) {
var errorInterceptor = {
responseError: function (response) {
var $http = $injector.get('$http');
if (response.status !== 401 && response.status !== 409) {
var error = {
status: response.status,
message: JSON.stringify(response),
url: window.location.href,
};
$http.post("/api/errors/angular", error);
}
return $q.reject(response);
}
}
return errorInterceptor;
}])
and the repsponse I'm getting is..
{"data":null,"status":-1,"config":{"method":"PUT","transformRequest":[null],"transformResponse":[null],"url":"/api/baskets/changedeliverymethod/10","headers":{"Accept":"application/json, text/plain, */*"}},"statusText":""}
EDIT:
I have never actually been able to produce this myself. Just get it randomly from all types of devices/browsers.

Circular dependency found: $http <- $templateFactory <- $view <- $state

I have a current 401 check that I'm running with $location which is working fine. However I'd like to swap it over to $state and use ui-router instead. When I do so i get an error code as:
Circular dependency found: $http <- $templateFactory <- $view <- $state <- authHttpResponseInterceptor <- $http <- $compile
My currently code looks fine as I check certain paths and allow no logged in users to view them:
/* Look for 401 auth errors and then redirect */
.factory('authHttpResponseInterceptor',['$q','$location', function($q,$location) {
return {
response: function(response){
if (response.status === 401) {
}
return response || $q.when(response);
},
responseError: function(rejection) {
var reservedPaths = ['/','/login','/connect','/event'];
if (rejection.status === 401 && _.contains(reservedPaths,$location.path().trim())) {
$location.path('/welcome');
}
return $q.reject(rejection);
}
};
}])
.config(['$httpProvider',function($httpProvider) {
//Http Intercpetor to check auth failures for xhr requests
$httpProvider.interceptors.push('authHttpResponseInterceptor');
}]);
The code I added is as follows.:
/* Look for 401 auth errors and then redirect */
.factory('authHttpResponseInterceptor',['$q','$location', **'$state',** function($q,$location, **$state**) {
return {
response: function(response){
if (response.status === 401) {
}
return response || $q.when(response);
},
responseError: function(rejection) {
var reservedPaths = ['/','/mycube','/connect','/event'];
if (rejection.status === 401 && _.contains(reservedPaths,$location.path().trim())) {
**$state.go('home');**
}
return $q.reject(rejection);
}
};
}])
.config(['$httpProvider',function($httpProvider) {
//Http Intercpetor to check auth failures for xhr requests
$httpProvider.interceptors.push('authHttpResponseInterceptor');
}]);
Why would adding state cause this issue when it works fine with location?
It appears that $state service is resulting in a circular dependency with the $http service. This may be caused by the fact that the templateFactory (see https://github.com/angular-ui/ui-router/blob/master/src/templateFactory.js) is being injected with the $http service in addition to the interceptor itself being composed with the $http service.
To get around this circular dependency issue, you can use the $injector service to wire up the $state service to your interceptor. See the revised code:
/* Look for 401 auth errors and then redirect */
module.factory('authHttpResponseInterceptor', ['$q','$location', '$injector', function($q, $location, $injector) {
return {
response: function(response){
if (response.status === 401) {
}
return response || $q.when(response);
},
responseError: function(rejection) {
var reservedPaths = ['/', '/mycube', '/connect', '/event'];
if (rejection.status === 401 && _.contains(reservedPaths, $location.path().trim())) {
var stateService = $injector.get('$state');
stateService.go('home');
}
return $q.reject(rejection);
}
};
}]);
You can learn more about the $injector service here: https://docs.angularjs.org/api/auto/service/$injector
IMPORTANT
I would recommend using the state change events (see https://github.com/angular-ui/ui-router/wiki#state-change-events) to watch for errors using $stateChangeError and inspecting the error returned from the 401.
Here is the simplest solution I did and it worked. Inside the factory write:
var $http = $injector.get("$http");
and then use $http as you normally would.
NOTE: If you don't have $injector available in your factory simply inject it as follow
.factory('authHttpResponseInterceptor',['$q','$location','$injector', function($q,$location,$injector) {
}])

AngularJS global $http state ajax loader

I have an AngularJS app, and need an ajax loader for every request done by the $http - is there a simple way to do this.
My solution now is to set $rootScope.loading = 1 everytime i call a $http, and on the success set $rootScope.loading = 0..
What is the "best practice" for this?
My code now looks like:
$rootScope.loading = 1;
$http({method : "POST", url:url, data: utils.params(data), headers: {'Content-Type': 'application/x-www-form-urlencoded'}}).success(function() {
$rootScope.loading = 0;
});
In this case will be better to use interceptor
Anytime that we want to provide global functionality on all of our requests, such as authentication,
error handling, etc., it’s useful to be able to provide the ability to intercept all requests before they
pass to the server and back from the server.
angular.module('myApp')
.factory('myInterceptor',
function ($q,$rootScope) {
var interceptor = {
'request': function (config) {
$rootScope.loading = 1;
// Successful request method
return config; // or $q.when(config);
},
'response': function (response) {
$rootScope.loading = 0;
// successful response
return response; // or $q.when(config);
},
'requestError': function (rejection) {
// an error happened on the request
// if we can recover from the error
// we can return a new request
// or promise
return response; // or new promise
// Otherwise, we can reject the next
// by returning a rejection
// return $q.reject(rejection);
},
'responseError': function (rejection) {
// an error happened on the request
// if we can recover from the error
// we can return a new response
// or promise
return rejection; // or new promise
// Otherwise, we can reject the next
// by returning a rejection
// return $q.reject(rejection);
}
};
return interceptor;
});
and register it to the config
angular.module('myApp')
.config(function($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
});
example from ng-book
Use an http interceptor in order to intercept all your $http requests\responses and perform logic in them.
Here's an example of creating a custom one.
Here's an example of a ready module.

Resources