1

What is the correct way to alter a Request performing an asynchronous task before the Request happens?

So any request Rn need to become transparently Tn then Rn.

A little of background here: The Task is a 3rd party SDK that dispatch a Token I need to use as Header for the original request.

My idea is to decorate the Rn, but in doing this I need to convert my Tn task into a Siesta Request I can chain then.

So I wrapped the Asynchronous Task and chained to my original request. Thus any Rn will turn into Tn.chained { .passTo(Rn) } In that way, this new behaviour is entirely transparent for the whole application.

The problem

Doing this my code end up crashing in a Siesta internal precondition: precondition(completedValue == nil, "notifyOfCompletion() already called")

In my custom AsyncTaskRequest I collect the callbacks for success, failure, progress etc, in order to trigger them on the main queue when the SDK deliver the Token.

I noticed that removing all the stored callback once they are executed, the crash disappear, but honestly I didn't found the reason why.

I hope there are enough informations for some hints or suggests. Thank you in advance.

Gabriele
  • 1,163
  • 1
  • 11
  • 24

1 Answers1

1

Yes, implementing Siesta’s Request interface is no picnic. Others have had exactly the same problem — and luckily Siesta version 1.4 includes a solution.

Documentation for the new feature is still thin. To use the new API, you’ll implement the new RequestDelegate protocol, and pass your implementation to Resource.prepareRequest(using:). That will return a request that you can use in a standard Siesta request chain. The result will look something like this (WARNING – untested code):

struct MyTokenHandlerThingy: RequestDelegate {
  // 3rd party SDK glue goes here
}

...

service.configure(…) {
  if let authToken = self.authToken {
    $0.headers["X-Auth-Token"] = authToken  // authToken is an instance var or something
  }

  $0.decorateRequests {
    self.refreshTokenOnAuthFailure(request: $1)
  }
}

func refreshTokenOnAuthFailure(request: Request) -> Request {
  return request.chained {
    guard case .failure(let error) = $0.response,  // Did request fail…
      error.httpStatusCode == 401 else {           // …because of expired token?
        return .useThisResponse                    // If not, use the response we got.
    }

    return .passTo(
      self.refreshAuthToken().chained {            // If so, first request a new token, then:
        if case .failure = $0.response {           // If token request failed…
          return .useThisResponse                  // …report that error.
        } else {
          return .passTo(request.repeated())       // We have a new token! Repeat the original request.
        }
      }
    )
  }
}

func refreshAuthToken() -> Request {
  return Request.prepareRequest(using: MyTokenHandlerThingy())
    .onSuccess {
      self.authToken = $0.jsonDict["token"] as? String  // Store the new token, then…
      self.invalidateConfiguration()                    // …make future requests use it
    }
  }
}

To understand how to implement RequestDelegate, you best bet for now is to look at the new API docs directly in the code.

Since this is a brand new feature not yet released, I’d greatly appreciate a report on how it works for you and any troubles you encounter.

Paul Cantrell
  • 9,175
  • 2
  • 40
  • 48
  • That's a great news! I didn't came across this PR. I will try to explore it. Any ETA for 1.4 release? – Gabriele Jun 25 '18 at 19:54
  • It's a pity I can't use in this due to Swift 4.1. My project will get migrated soon but not now :( Anyway I keep seeing the failure pattern that for authToken is great but this does not fit my needs. (R -> Fail -> GetToken -> Success -> Invalidate -> R.Repeated) Is there any pitfall in doing GetToken -> Success -> Invalidate -> R.Repeated? The reason is: the 3rd SDK suggest this behaviour. Token get cached by the SDK and get refreshed only on real needs. – Gabriele Jun 25 '18 at 20:19
  • Yes, the pattern you describe should work just fine. The whole point of the request chaining API is to allow you to set up arbitrary retry paths, your our miniature multi-request state machine. One possible pitfall: to force it to re-read configuration after you get your token, you _might_ need to do `request.repeated()` even though you never initiated the original request in the first place. – Paul Cantrell Jun 25 '18 at 21:17
  • Oh, and per the 1.4 release: ETA is just as soon as I can find time to finish up docs & release housekeeping. – Paul Cantrell Jun 25 '18 at 21:17
  • 1.4 is out now. – Paul Cantrell Jun 26 '18 at 22:21