Controlling how each catch in $httpBackend.when() is handled - angularjs

I'm trying to build some jasmine unit tests around my angular project. I've run into a situation I'm not sure how best to work around.
I use a response interceptor that's capable of retrying a request if it determines the error was a 401 error. It will make a call to renew the authorization token, and then reissue the request transparently.
(Orginal Call) -> 401 ? (recert and retry) : (return fail)
My problem lies with:
$httpBackend.whenPOST('/mymockedendpoint').respond(401, null);
This is the behavior I want the FIRST time it is queried. However, because it also controls all subsequent queries, my token renewal works, and then it reissues this request, but instead of returning a 200 like it would in production, it still returns the 401.
How can I extend the handling of that whenPOST so that I can control the behavior in a handler of some sort? Is that even possible?
Right now executing my test produces an infinite loop because the both reissues the request (since it successfully renewed the token) and catches again because the would-be-200 requests returns a 401).

Instead of using the "when" based functions of $httpBackend you can use the "expect" versions. This will let you assert that a particular request is made in a particular order. The documentation for $httpBackend describes the differences pretty well:
$httpBackend.expect - specifies a request expectation
$httpBackend.when - specifies a backend definition
Request Expectations vs Backend Definitions
Request expectations provide a way
to make assertions about requests made by the application and to
define responses for those requests. The test will fail if the
expected requests are not made or they are made in the wrong order.
Backend definitions allow you to define a fake backend for your
application which doesn't assert if a particular request was made or
not, it just returns a trained response if a request is made. The test
will pass whether or not the request gets made during testing.
Given that, try this in your test:
$httpBackend.expectPOST('/mymockedendpoint').respond(401, null);
$httpBackend.expectPOST('/mymockedendpoint').respond(200, { });
$httpBackend.flush();
Also, note the function $httpBackend.resetExpectations(), which could be useful in this type of scenario.

Related

Network request failed from fetch in reactjs app

I am using fetch in a NodeJS application. Technically, I have a ReactJS front-end calling the NodeJS backend (as a proxy), and then the proxy calls out to backend services on a different domain.
However, from logging errors from consumers (I haven't been able to reproduce this issue myself) I see that a lot of these proxy calls (using fetch) throw an error that just says Network Request Failed, which is of no help. Some context:
This only occurs on a subset of all total calls (lets say 5% of traffic)
Users that encounter this error can often make the same call again some time later (next couple minutes/hours/days) and it will go through
From Application Insights, I can see no correlation between browsers, locations, etc
Calls often return fast, like < 100 ms
All calls are HTTPS, non are HTTP
We have a fetch polyfill from fetch-ponyfill that will take over if fetch is not available (Internet Explorer). I did test this package itself and the calls went through fine. I also mentioned that this error does occur on browsers that do support fetch, so I don't think this is the error.
Fetch settings for all requests
Method is set per request, but I've seen it fail on different types (GET, POST, etc)
Mode is set to 'same-origin'. I thought this was odd, since we were sending a request from one domain to another, but I tried to set it differently and it didn't affect anything. Also, why would some requests work for some, but not for others?
Body is set per request, based on the data being sent.
Headers is usually just Accept and Content-Type, both set to JSON.
I have tried researching this topic before, but most posts I found referenced React native applications running on iOS, where you have to set some security permissions in the plist file to allow HTTP requests or something to do with transport security.
I have implement logging specific points for the data in Application Insights, and I can see that fetch() was called, but then() was never reached; it went straight to the .catch(). So it's not even reaching code that parses the request, because apparently no request came back (we then parse the JSON response and call other functions, but like I said, it doesn't even reach this point).
Which is also odd, since the request never comes back, but it fails (often) within 100 ms.
My suspicions:
Some consumers have some sort of add-on for there browser that is messing with the request. Although, I run with uBlock Origin and HTTPS Everywhere and I have not seen this error. I'm not sure what else could be modifying requests that would cause it to immediately fail.
The call goes through, which then reaches an Azure Application Gateway, which might fail for some reason (too many connected clients, not enough ports, etc) and returns a response that immediately fails the fetch call without running the .then() on the response.
For #2, I remember I had traced a network call that failed and returned Network Request Failed: Made it through the proxy -> made it through the Application Gateway -> hit the backend services -> backend services sent a response. I am currently requesting access to backend service logs in order to verify this on some more recent calls (last time I did this, I did it through a screenshare with a backend developer), and hopefully clear up the path back to the client (the ReactJS application). I do remember though that it made it to the backend services successfully.
So I'm honestly not sure what's going on here. Does anyone have any insight?
Based on your excellent description and detective work, it's clear that the problem is between your Node app and the other domain. The other domain is throwing an error and your proxy has no choice but to say that there's an error on the server. That's why it's always throwing a 500-series error, the Network Request Failed error that you're seeing.
It's an intermittent problem, so the error is inconsistent. It's a waste of your time to continue to look at the browser because the problem will have been created beyond that, either in your proxy translating that request or on the remote server. You have to find that error.
Here's what I'd do...
Implement brute-force logging in your Node app. You can use Bunyan, or Winston or just require(fs) and write out to some file when an error occurs. Then look at the results. Only log it out when the response code from the other server is in the 400 or 500 ranges. Log the request object and the response object.
Something like this with Bunyan:
fetch(urlToRemoteServer)
.then(res => res.json())
.then(res => whateverElseYoureDoing(res))
.catch(err => {
// Get the request & response to the remote server
log.info({request: req, response: res, err: err});
});
where the res in this case is the response we just got from the other domain and req is our request to them.
The logs on your Azure server will then have the entire request and response. From this you can find commonalities. and (🤞) the cause of the problem.

Resend REST Call in Restangular after JWT Refresh

I am currently working on a web application that uses a JWT for authentication on all REST calls. The problem we are having is when a customer is performing an action needing a REST call and the JWT is expired, for whatever reason. At that point it kicks back a 401 response, as it should.
The functionality I am looking for is some way to intercept the 401 error, refresh the JWT, and retry the request without sending errors to the user.
The application runs on AngularJS and uses Restangular to handle all of the rest calls. So far I have been looking closely at the setErrorInterceptor as outlined here and at the Restangular documentation. Using a version of that posted code, I could successfully resend the request and in the .then() portion I had a successful response. However, it seems from the documentation that this method can only return false or true, so I couldn't get at it.
At the moment we have a timer that basically compares the jwt expiration time to the current system time and refreshes the jwt when there are 60 seconds left before expiration. This caused issues when the user's system clock was off compared to the server clock, resulting in a token not being refreshed because the application thought there was more time.
A solution to that specifically would be something like getting the system time, comparing to server time and creating an offset variable, but that doesn't cover all the bases for things that could go wrong. On another topic, checking the JWT before every request is not feasible.
Is there a way within Restangular's interceptors to accomplish the error interception and resending? If not, are there ways outside of it?

Differences between Angular's $httpBackend expectPost and whenPost

While working on some tests, I was surprised to find, that simply changing the $httpBackend.expectPost to a $httpBackend.whenPost fixed some broken tests...
Looking at the docs, it says that an expect "Creates a new request expectation.", while a when "Creates a new backend definition.". Unfortunately this doesn't mean much too me...
Can someone explain the difference between these two methods?
As mentioned in the docs,
Request expectations provide a way to make assertions about requests
made by the application and to define responses for those requests.
The test will fail if the expected requests are not made or they are
made in the wrong order.
Backend definitions allow you to define a fake backend for your
application which doesn't assert if a particular request was made or
not, it just returns a trained response if a request is made. The test
will pass whether or not the request gets made during testing.
With whenPost() definition, whenever your code makes a POST request through $http, this method will intercept and serve the response. But in case of expectPost(), it actually creates an expectation about POST request to that URL and if your code doesn't make any POST request to that URL, test will fail.
In case a request is made, then it will also respond with mock object.

Angularjs proceed with server call even if CORS does not allow a header value

I have an AngularJS application that talks to various java services. In the application I have a global http header setting in an http interceptor. That means all the service requests from my application will get the header values.
Now the trouble is that all the services CORS settings won't allow this header value. Couple of services does, while others does not. The service calls to the servers that do not support the header fails, since the http interceptor always puts the header values.
Is there a better way to design, in the above said case, so as to avoid the issue stated?
Appreciate any help...
How about adding a response interceptor, looking for a 401 status? If you get a 401, attempt to do the same request again without the headers this time. If this succeeds, 'whitelist' this domain to make all following requests without the headers that you don't want.
Otherwise, if you have a limited number of services that you are making calls to, maybe whitelist them inside of your request interceptor? This would probably be easier, but it's not very elegant.

How do I fully intercept AND requeue http requests using dojo

I am down to a choice between angularjs and dojo as my front end MV* framework. One particular functionality which in my mind is critical is the ability to do unobtrusive and transparent authentication. I was extremely impressed with angular's capability in this regard using http interceptors. See http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application for a good sample implementation/explanation.
It allows one to queue up any and all 401 responses while waiting for successful login and then re-transmit them once login is successful by using a combination of broadcast events, event handling, and http interceptor (plus a request queue). The overhead on each request appears to be minimal.
I wanted to compare the ability of dojo to do the same, but from my examination it does not appear to be possible. Examining the dojo documentation, it appears that this would probably have been possible in dojo 1.4 using the ioPipline, but that that is deprecated in favor of dojo/request/notify which does not appear to provide as low-level of an interception (specifically it doesn't appear to give access to the request object when an error is received). it is also not clear if it is possible to modify or replace the response from within the notified function...
Can anyone point me to a clear example of this having been accomplished using dojo's request/notify service or an alternate methodology using dojo? Critical requirements are that the authentication be transparent to the protected functions/controllers/objects (they should simply receive a promise which is eventually fulfilled), that the authentication code be able to be centralized (it should not require modification to every object/event/etc that makes a request), and that it should be secure (in the sense that both authentication/authorization should happen on the server side and no javascript manipulation should be able to bypass that requirement)...
You can register handlers within dojo/request/registry to intercept and handle requests made using dojo/request any way you want. Here’s a modified (untested, probably slightly defective) example based on the reference guide:
require([ "dojo/request/registry", "dojo/request/xhr" ], function (registry, xhr) {
request.register(function (url, options) {
return xhr(url, options).response.then(function (response) {
if (response.status === 401) {
// retry later
}
return response;
});
}, true);
});
With the above example, any request made with dojo/request will be intercepted and the method provided will be called. If you wanted to conditionally only match certain requests with this handler, you can pass another tester function as the first argument. This is detailed in the reference guide linked above.
All your handler function then needs to do is return a promise that will eventually resolve to the expected data.

Resources