Swift Alamofire RequestInterceptor not called if using SessionDelegate - swift

I am trying to refresh my authentication credential for my request using Alamofire's RequestInterceptor.
I noticed I am unable to enter the validate() portion of the interceptor as my breakpoints show me I am entering my SessionDelegate handlers instead. More precisely the urlSession(_:dataTask:didReceive:completionHandler:) and urlSession(_:task:didCompleteWithError:) methods of my SessionDelegate class seems to swallow the logic for the RequestInterceptor. If I remove these two handlers from my SessionDelegate I am able to enter the validate() code which then triggers the refresh method on my Authenticator class.
Is there a way to force the RequestInteceptor to intercept my request and refresh my credential if needed before it enters my SessionDelegate handlers? Removing the handlers from my SessionDelegate is not an option as I need to perform specific business logic in there.
Code:
self.session = Session(delegate: mySessionDelegate, interceptor: myRequestInterceptor)
self.session.request(myURL)
.validate({ _, response, data -> DataRequest.ValidationResult in
let statusCode = response.statusCode
//this code is not entered if I have my SessionDelegate handler
if 200 ... 299 ~= statusCode { return .success(Void()) }
return .failure(AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: statusCode)))
})
.resume()

If you're writing a custom SessionDelegate subclass (which isn't recommended; there's almost always a better way to accomplish what you need), it's your responsibility to call super in your overridden methods, as they contain important events. Otherwise you'll break important Alamofire events, as you've found.

Related

Swift semaphore behavior

I've a problem. I need that a resource it's ready to use it. I've implemented this mechanism:
func shouldRetry(request: Request, error: Error) -> Bool {
let semaphore = DispatchSemaphore(value: 0)
var shouldRetry: Bool = false
self.interceptor?.retry(request.response, for: self.session!, dueTo: error, completion: { (retryResult) in
switch retryResult {
case .retry:
shouldRetry = true
semaphore.signal()
case .doNotRetry:
shouldRetry = false
semaphore.signal()
case .retryWithDelay(let delay):
shouldRetry = true
self.retryTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { (_) in
semaphore.signal()
self.retryTimer?.invalidate()
self.retryTimer = nil
})
}
})
semaphore.wait()
return shouldRetry
}
The problem is that interceptor?.retry func use is called in the same thread of semaphore, and this block the process. Any suggestions?
UPDATE:
I solved my question. I subclasses URLProtocol abstract class. This class works with URLSession and it's possible suspend a HTTP call, make async code and resume
I don't think there's an answer without more context. However, if you try to implement a "refresh-token adapter / retrier", the strategy is as follows:
You need a specialised adapter (a struct or class) which sets the Authorization header with the access token. Accessing a token MUST be thread-safe, and even getting a token may fail - for example, because there's none. In case of an error, the adapter tries NOT to get a new access token, this will be done in the retrier. The adapter may just not set the access token. The request will fail and will be handled in the retrier.
The adapter dispatches is functions on a dedicated queue, let's call it "process_queue". When finished setting the header it calls the completion handler.
You also need a specialised Retrier (a struct or class). It accesses a shared state which holds the result of a token request. This can be a Swift.Result for example.
This retrier's functions execute on a dedicated dispatch queue.
The specialised Retrier determines the response status and if the status code is a 401 (not authorised) AND if the Authorization header is a bearer token, it needs to make a refresh-token request.
When starting this refresh-token task, it suspenses the process_queue, so that no more request adaption take place with an expired access token.
Now, the token request completes and say it was successful. The retrier stores the access token (into the key chain), updates the original request with the new access token, then resumes the process_queue and then it calls its completion handler.
Proper Error handling makes this slightly more complex. The question is how to treat pending requests when the refresh-token request has failed. I have used an approach, which let fail all the pending requests that have been queued until the refresh-token request completed. They just fail with the error 401. Then a new cycle starts, beginning with attempting to get a new refresh-token.
This approach prevents that multiple failed requests call into the token endpoint at the same time. You can even extend the approach when the refresh token also expires. In that case you need to sign-in the user. All this may happen within the retrier. It completes only after sign-in is complete, after getting a new access token is complete, or when an error occurred.

How to call custom method right before sessionTask resume in Alamofire

I need to record some data/info when Alamofire called resume to start the request.
(I used Swift in my project)
Is there anyway without do method_swizzling?
the timeline will be like this:
Call a request (put in request queue or execute right away) -> [custom method] -> SessionTask.resume()
I know Moya did something similar called WillSend. But I would like to know how to do it without using Moya.
Thank you.
If all you need to do is inspect various request elements you can use Alamofire 5's EventMonitor protocol, which is called during various lifetime events as Alamofire makes requests. There's a built in ClosureEventMonitor which allows you to set closures for those events. For example:
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
Please see our documentation for ClosureEventMonitor and the EventMonitor protocol itself.

How to check for Auth user in Swift while allowing JWT Refresh?

I want to be able to have a single Bool method that checks if a user is logged in, and if so returns the token to be used in the API header.
func isLoggedIn() -> Bool {
if let authToken = AuthManager.loadAuthToken() {
if authToken.expiration.isGreaterThanDate(Date()) {
return true
} else {
let authAPI = AuthenticationAPI()
authAPI.refreshToken(token: authToken.token) { (results) in
switch results {
case .success(let response):
[HAVE PARENT METHOD RETURN TRUE]
case .failure:
removeAuthToken()
[HAVE PARENT METHOD RETURN FALSE]
}
}
}
}
return false
}
I have this mostly working, however, I am running into a wall trying to figure out how to allow the app to attempt the refresh token call when there is a token present, but it is expired.
Semaphores seem like the way to go, but seem to quickly complicate the code. Is this the way it should be done or is there a better approach that is usually done for this?
I take a different approach.
In an AuthManager type of class, I store the token in an optional var. If the token is present, then I assume it is valid to be used in an API call. In the didSet for the token, there is a Notification.Name.authStatusDidChange notification which is sent so the app can respond accordingly.
If the API call returns a 401 then I notify the AuthManager to clear the token (which triggers the notification, and in my case shows the login flow).
However, you could have additional logic which rather than clearing the token, the Authmanager first tries to refresh it. If the refresh succeeds, then a different notification is sent to retry the API request. Otherwise it clears the token and triggers a sign in.
While I was Typing - Like Michal said, with a little more detail.

Restangular - how to cancel/implement my own request

I found a few examples of using fullRequestInterceptor and httpConfig.timeout to allow canceling requests in restangular.
example 1 | example 2
this is how I'm adding the interceptor:
app.run(function (Restangular, $q) {
Restangular.addFullRequestInterceptor(function (element, operation, what, url, headers, params, httpConfig) {
I managed to abort the request by putting a resolved promise in timeout (results in an error being logged and the request goes out but is canceled), which is not what I want.
What I'm trying to do - I want to make the AJAX request myself with my own requests and pass the result back to whatever component that used Restangular. Is this possible?
I've been looking a restangular way to solve it, but I should have been looking for an angular way :)
Overriding dependency at runtime in AngularJS
Looks like you can extend $http before it ever gets to Restangular. I haven't tried it yet, but it looks like it would fit my needs 100%.
I'm using requestInterceptor a lot, but only to change parameters and headers of my request.
Basically addFullRequestInterceptor is helping you making change on your request before sending it. So why not changing the url you want to call ?
There is the httpConfig object that you can modify and return, and if it's close to the config of $http (and I bet it is) you can change the url and even method, and so change the original request to another one, entirely knew.
After that you don't need timeout only returning an httpConfig customise to your need.
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
httpConfig.url = "http://google.com";
httpConfig.method = "GET";
httpConfig.params = "";
return {
httpConfig: httpConfig
};
});
It will be pass on and your service or controller won't know that something change, that's the principle of interceptor, it allow you to change stuff and returning to be use by the next process a bit like a middleware. And so it will be transparent to the one making the call but the call will be made to what you want.

Retry an NSURLSessionDataTask request after re-authorizing (AKA NSInvocation equivalent in Swift)

TL;DR version: What is the nearest equivalent to NSInvocation - or similar way to package up a method call, including arguments - in Swift?
Background: I have a ServerController object which is used across my app to make requests to an API. The magic happens in this method:
private func makeRequestWithPath(
path: String,
method: String = "GET",
body: NSData? = nil,
successBlock: (AnyObject?) -> Void) {
// ...
self.urlSession.dataTaskWithRequest(request,
completionHandler: { (data, resp, error) -> Void in
if let httpResp = resp as? NSHTTPURLResponse {
switch httpResp.statusCode {
case 200:
successBlock(/* pass process response in here */)
case 401:
self.postNotificationOnMainThread(kAppTokenRefreshRequiredNotification)
// ...
}.resume()
Periodically API requests fail with a 401, which gets broadcast in the app as a notification, as many parties are interested in the fact the request is going to take a bit longer. Another API request is used to refresh an app token and then other API requests start working again.
This is the part I need help with: I then need to repeat the original API request following re-authorization.
Previously in Objective-C I might have used an NSInvocation object to package up the original method call, but this is not available in Swift and I'm therefore thinking there must be a more Swift-like approach. However I've not yet managed to get it figured out.
The best approach I've come up with so far is to:
put the arguments, along with a reference to the ServerController into a dictionary and pass them into the notification as the object: parameter
in the notification observer (after completing the re-authorization), pull the arguments out of the dictionary and manually call the same method again to repeat the request
This seems like a sub-optimal solution and there must be better way.