I want to return cached response before timeout happened and then renew cache.
I tried this:
.factory('TestInterceptor', TestInterceptor);
function TestInterceptor($q) {
return {
request: request,
response: response
};
function request(config) {
...
return $q.resolve(fakeResponse);
...
}
function response() {
....
}
}
but it doesn't work.
It is not possible. As the manual says,
request: interceptors get called with a http config object. The function is free to modify the config object or create a new one. The function needs to return the config object directly, or a promise containing the config or a new config object.
Related
I have been looking at an application I made a while back and there is a particular page where the details are being loaded last. Because of this, it seems to be queuing the request (there are more than 6 others before it) and that is causing the page to be slow.
I figured I could find a solution to prioritize these requests and I found this:
How to prioritize requests in angular $http service?
So I created my version of it and added it to my interceptors:
// Add our auth interceptor to handle authenticated requests
$httpProvider.interceptors.push('authInterceptor');
$httpProvider.interceptors.push('httpPriorityInterceptor');
The interceptor looks like this:
function factory($injector, $q) {
var requestStack = [], // request stack
$http = null; // http service to be lazy loaded
return {
request: request,
responseError: responseError
};
//////////////////////////////////////////////////
function request(config) {
// Lazy load $http service
if (!$http) {
$http = $injector.get('$http');
}
if (!config.hasBeenRequested) {
config.hasBeenRequested = true;
config.priority = config.priority || 3;
console.log(config);
// add a copy of the configuration
// to prevent it from copying the timeout property
requestStack.push(angular.copy(config));
// sort each configuration by priority
requestStack = requestStack.sort(sort);
// cancel request by adding a resolved promise
config.timeout = $q.when();
}
// return config
return config;
}
function responseError(rejection) {
// check if there are requests to be processed
if (requestStack.length > 0) {
requestStack.reduceRight(function(promise, config) {
return promise.finally(function() {
return $http(config);
});
}, $q.when());
requestStack.length = 0;
}
// return rejected request
return $q.reject(rejection);
}
//////////////////////////////////////////////////
function sort(config1, config2) {
return config1.priority < config2.priority;
}
}
The problem is, it seems to be intercepting template requests too. I have no issue with that, but they are not resolving. Instead I get a lot of errors:
Error: [$templateRequest:tpload] Failed to load template: app/accounts/accounts.html (HTTP status: -1 )
Has anyone encountered this before? Is there something I can do to fix this?
you should know that every request such as html files , css file and ... comes into interceptor.
in your case you dont need to prioritize this files. so you can filter your request like:
if (config.url.toString().toLowerCase().includes("api")) {
//place your functionality
}
I want to use request interceptor in my app to add verify code into requests (for CSRF protection). My code looks like this now:
$httpProvider.interceptors.push(function($q, $injector, AppConst) {
return {
request: function(request) {
var VerifyCodeService = $injector.get('VerifyCodeService');
var verifyCodeUrl = AppConst.apiUrl + '/app/verifyCode'
if(request.url!=verifyCodeUrl && request.data!=undefined){
VerifyCodeService.getCode()
.then(function(data) {
if (AppConst.serviceResponseOk==data.result) {
request.data.verifyCode = data.verifyCode;
return request;
} else {
console.log('error');
return request;
}
}, function(error) {
console.log('error:' + error);
return request;
});
} else {
return request;
}
}
};
});
But for some reason I keep getting this error:
TypeError: Cannot read property 'headers' of undefined
at serverRequest (angular.js:10028)
at processQueue (angular.js:14567)
at angular.js:14583
at Scope.$eval (angular.js:15846)
at Scope.$digest (angular.js:15657)
at Scope.$apply (angular.js:15951)
at done (angular.js:10364)
at completeRequest (angular.js:10536)
at XMLHttpRequest.requestLoaded (angular.js:10477)
Anyone knows whats happening?
If I read this right, you are making a XHR which triggers another XHR to get the CSRF token and add it to the original request's data object. I think the issue is the second XHR. This second request is async just like the first, so you can't just return from the then method.
Try returning the promise generated by your second XHR in your if statement (which is resolved in the inner then invocation after the second XHR by return request;).
if(request.url!=verifyCodeUrl && request.data!=undefined){
var secondXhr = VerifyCodeService.getCode()
.then(function(data) {
if (AppConst.serviceResponseOk==data.result) {
request.data.verifyCode = data.verifyCode;
return request;
} else {
console.log('error');
return request;
}
}, function(error) {
console.log('error:' + error);
return request;
});
return secondXhr;
} else {
return request;
}
From the Interceptors section of the Angular docs (bolding added):
request: interceptors get called with a http config object. The
function is free to modify the config object or create a new one. The
function needs to return the config object directly, or a promise
containing the config or a new config object.
On a different note, it looks like you are introducing a significant delay into each XHR since you are essentially blocking each request until the token is returned.
A different implementation where each response returns the new token for the next request in a header could work (as long as you don't have concurrent requests(!)), or CSRF token per page load (if you have full page reloads) might be options. I'm sure there are suggestions on the internets if you want to make that change.
I am building an authentication solution with Angular. I am using Interceptors to validate Request and if there is not valid token prevent from further processing and redirect. Here is my simplified Interceptor:
prism.service('APIInterceptor', function($rootScope) {
var service = this;
service.request = function(config) {
.....
return config;
};
})
Just for the sake of POC that I am working on what would the correct way of stopping this request from any further processing be?
Thanks
From the Angular Docs on Interceptors for the request method:
request: interceptors get called with a http config object. The function is free to modify the config object or create a new one. The function needs to return the config object directly, or a promise containing the config or a new config object.
The rest of the documentation can be found here. From this you can see that the method can also return a promise (which is actually pretty awesome) so you could always reject it.
Try something like this:
prism.service('APIInterceptor', function($q, $rootScope) {
this.request = function(config) {
if( /*config is not valid*/ ) {
return $q.reject({message: 'ERROR, ERROR... INTRUDER ALERT!', status: 401, config: config});
} else {
return config;
}
};
});
And see how it might be handled (I have no idea what your application will do). Let me know if it works out for you!
EDIT: My answer has been accepted, but is incomplete and it will haunt me forever if I don't complete it. So, after writing some test code of my own I've realized that you can do 1 of 2 things in this situation. The first is to handle the unauthorized request in the interceptor:
...
this.request = function(config) {
if(/* config is not authorized */) {
// Do something here like redirect/issue another request... whatever
return $q.reject({/*whatever the hell you want*/});
} else ...
};
...
This obviously works best if you want to handle all unauthorized requests the same. If you don't, however, the second option is to defer to the service that issued the request. For example, if you're using $http you can do this:
$http.get('/words/words/words/').then(function(){
// This is where you handle a successful request.
}, function(error) {
// Handle your error here. Please take note that this error message is
// whatever you sent back in the `reject` previously
});
Hopefully that clears a few things up.
I'm new to Angular, and am working on an interceptor. I created an angular factory to get some data from an API like that :
app.factory('Connection',['$resource',function($resource) {
return $resource('url',{param1: '1',param2: '55'},);
}]);
I also created the interceptor which looks like that :
app.factory('connectionInterceptor', function($q,$location) {
var connectionInterceptor = {
response: // code here
responseError: // code here
};
return connectionInterceptor;
});
The interceptor works well. But it intercepts every http request I do, and I'd like to make it work for a specific $resource. I read in angular $resource doc that there is a way to make it by adding an interceptor action/param to $resource. So I tried :
app.factory('Connection',['$resource',function($resource) {
return $resource('http://localhost:8080/api/login',{user: '1',password: '55'}, {},
query: {
method : 'GET',
interceptor : 'connectionInterceptor'
}
});
}]);
which didn't work. The thrown error is : Error in resource configuration for action query. Expected response to contain an object but got an array.
What did I miss ?
As you said, interceptors are globally set. I had to add a test to my response to check the $resource URL and add some specific treatment.
module.factory('interceptor', function() {
var interceptor = {
response: function(response) {
if (response.config.url.startsWith('my url')) {
// some treatment
}
else
// other treatment
return response;
}
return connectionInterceptor;
});
I'm constructing elements from metadata and i need to set a calculated class for each element.
This is what I currently do,
var promisses =_.map(templates, function (tmpl) {
return $http.get(tmpl.template, {
cache : $templateCache,
// Generated class name is carried to the resolving function using the config
classes : scope.generate_class(tmpl.columns)
}).then(function (data) {
if ( data.status != 200 )
throw new Error('Failed to fetch template');
var elm = angular.element(data.data);
elm.addClass(data.config.classes);
return elm;
});
});
$q.all(promisses).success....
If I want to use success instead of then fir the $http bit (which evaluates in case of an error as well) how would i do that ? when using success the config is not carried on to the resolving function (only the data).
Thanks.
From $http docs:
Returns a promise object with the standard then method and two http
specific methods: success and error. The then method takes two
arguments a success and an error callback which will be called with a
response object. The success and error methods take a single argument
- a function that will be called when the request succeeds or fails respectively. The arguments passed into these functions are
destructured representation of the response object passed into the
then method.
The response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
So you can pass the config like so:
.success(function(data, status, headers, config) {
Do not throw errors when using promises, if your server doesn't return an error code you can use q.reject to transform it to a rejection, also q.all promises doesn't have a success method:
var promisses =_.map(templates, function (tmpl) {
return $http.get(tmpl.template, {
cache : $templateCache,
// Generated class name is carried to the resolving function using the config
classes : scope.generate_class(tmpl.columns)
}).then(function(res) {
if ( res.status != 200 ) {
return $q.reject('Failed to fetch template');
} else {
var elm = angular.element(res.data);
elm.addClass(res.config.classes);
return elm;
}
});
});
$q.all(promisses)
.then(function() { ... })
.catch(function() { .. })