Cookbook
This section contains a cook book of useful topics.
Retries
Retries can be used when your initial request fails to complete and you wish to automatically retry the request. dispatch has direct support for retries independent of the underlying technology. async-http-client also has builtin retry capabilities (.setMaxRequestRetry) and you can adjust which exceptions trigger a retry although this aspect of configuration applies to all http clients. async-http-client can also implement a per-request retry strategy by using the onFailure method in the handler interface. dispatch's retry mechanism can be specified on a per request basis regardless of the client.
A good blog on the dispatch retry approach is here. Contrary to the blog, it's not true that your future has to return an Option or Either, it could return anything that returns a boolean where false indicates an error and true that it returned successfully. There are some implicit definitions that make Option and Either convenient though which is why the author mentions returning an Option or Either in the blog.
The essential idea is that since a Future cannot be restarted once finished so you need to provide the retry framework a function that returns a Future (think "call by name") that creates the "same" request. Once you have a function that generates a new Future, the retry framework can call this function to generate a new request if the previous request fails.
Retries can be based on directly trying a retry if there is a failure, retrying up to a specified number of times, retrying with a specified pause between retries and exponential backup retrying pauses.
Since the dispatch retry framework really just uses scala Future's you can use the framework anywhere you use a future. Of course, you could also use Futures recover and recoverWith to handle retry logic.
Here's a quick example of how to implement retry using dispatch's mechanism. You'll soon realize, however, that this forces you to alter your calling pattern a bit in order to use the API.
First, we can show that the dispatch retry mechanism is independent of http calls using amm. In our first example, we have a function that fails every time by returning None (remember dispatch expects the future to return Either or None although you can get around this). We will do 5 retries and each retry should call doit and hence, print out doit! so that we know the call occurred:
import retry.Succcess._
@ def doit() = { println("doit!"); Future(None) }
defined function doit
@ doit()
doit!
res15: Future[None.type] = Success(None)
@ retry.Directly(5)(doit _)
doit!
doit!
doit!
doit!
doit!
doit!
res16: Future[None.type] = Success(None)Now we will rewrite doit to always succeeed:
which only needs to call doit once and we see that in the output.
Now we will re-use our example-server of delaying a response. See the other section on the changes needed to example-server. The changes allow us to delay a response based on the URL. Fore example, running:
causes the response be delayed by 30 seconds. If we set the timeout in async-http-client to 5 seconds and call the delay URL, we should get a timeout and we can then force a retry.
We need to setup the function to call. Note that the actual http call is cast to an either then projected to the right since our assumption is that the call should succeed. Then, if it were to succeed, we convert the returned value to a long just for fun. Of course, we are going to force every http call to fail to demonstrate dispatch's retry capability. Note also that we imported retry.Success._ so that the retry mechanism knows how to convert our return value to a "signal" that a retry should occur. You could pass in your own "predicate"
Then call the dispatch retry mechanism:
Our output should be the initial "callit!" output then 5 more println as the retry occurs. Because we have set the request time to 2 seconds in the async-http-client layer but we request a 30 second delay, every call will fail. Here is the output:
The returned value or error will be that which is generated on the last "retry." The other retry calls work similarly e.g. retry.Backoff and retry.Pause.
Dispatch contains the retry mechanism is a package `retry` but you can use your own. You will find that the retry capabilities of Dispatch are a slimmer version of what you can find in the following two projects that include retry functions that can also contain jitter.
The API is very similar (!) so its mostly a drop-in replacement.
Response Body Access When Errors Occur
When your call results in an error, say you are expecting 200 but get a 400 (bad request), you want to be able to inspect the body of the returned response in addition to throwing an exception. Throwing an exception is handled in the Future that is returned when you dispatch a request, but if an exception occurs, you might wonder how you get the response body to inspect and understand any additional error information that may have been returned by the server?
This link, http://stackoverflow.com/questions/25233422/how-to-get-response-headers-and-body-in-dispatch-request, gives you an approach and also reminds you that you can write your own customized "ok" handler specific to your application needs.
First you need to recognize that dispatch is a thin layer on top of http-async-client. The underlying library uses an AsyncHandler to handle responses. Dispatch includes an OK default handler that conforms to the AsyncHandler interface:
If you recall, the dispatch client needs a (request, handler) tuple. The first class provides the OK definition. The handler in this case is the OkFunctionHandler. It takes a function to apply to the response, for example as.String or as.xml.Elem (which are functions that transform a response to the desired output type.
The OkHandler trait adheres to the AsyncHandler interface and defines a onStatusReceived method. Not shown, there is also a onCompleted method as well. We can use the same code structure with the onCompleted method to check the status code and if it is not 200, throw an exception of our choosing with the response body as a string. We could obviously use a higher order function that throws an exception type of our choice or do something else entirely, such as print out the response body to a log file. Here's the code, slightly changed from the blog:
To use this we simply:
If we get an exception, we can get access to the response body. But let's give this a try to make sure. First lets modify our example server so it returns a status code of 400 when we want it to:
which if we run a request to this URL gives us the error we expect:
So now we can try this with our program:
Which is not what we expected. The inner exception is our ApiHttpError but its wrapped in an ExceutionException exception. This wrapping would make it very hard to pattern match on in a recover statement or otherwise in our program. In fact, this would not work:
Because the actual exception value is wrapped, you would never match on the case ApiHttpError portion. But we could use dispatch's either:
The string "Bad Request!" is the body of the 400 response from our example server. That last line was very ugly but I did not want to write any pattern matchers to extract out the value to demonstrate my point.
You may wonder how the .either can pick out the inner exception. That's straight forward from the disptach code and in fact mirrors what we would have to do:
If the Future from Http succeeds its wrapped in the Right and if its an exception recover is called to extract out the inner exception and wrap it in a Left. It is not dispatch's fault that its so goofy coming from the java layer which is the layer where the exception is caught when it is thrown in the OkWithBody handler. And since its in the java layer, the direct type information is really lost.
Perhaps the best way to handle these types of problems is to really avoid them altogether and always ask dispatch to give you back the raw response object from async-http-client and process the response object directly using your own framework, for example, similar to way that play-json handles parsing json conten.
In reality, whether you define your own result object (with a good result and a bad result variant) or use Either (which is already available to you), its much the same thing e.g. you are using a co-product data structure to contain the error or the result value and in fact you could pattern match on the error if you wanted to since you know that the Future must have a valid value (an Either) so you could call dispatch's Future enhancement () on the Future and obtain the value to get:
But beware, the () (apply operator in scala) for enhanced futures in Dispatch calls Await which is generally considered to be bad form in a reactive application.
If you just want to unwrap an ExecutionException so you can pattern match using standard scala Future methods, then you could write an unwrap implicit class (scala 2.11+) that unwraps only that exception:
then use it:
You can call unwrapEx anywhwere in the chain but make sure it occurs before the recover part of the statement.
By hiding the parsing down in the handler, you can make it easier to parse results at the next level up, with the loss of composability somewhat. Let's say that we are working with SOAP calls and we always get an XML response. And let's use the xtract library for extracting values from XML objects. We know we can have only a limited number of errors, aside from catastrophic errors:
We should have used a typeclass for the logging but let's be lazy for the moment. Now, like before, we need an AsyncHandler to receive the response, check the status code, parse the body then return th appropriate object. Let's use cats`Xor class to handle the disjunction:
Now instead of worrying about ApiHttpError we just have values representing errors, whwich is more functional. In our calling program, let's assume we have a reader:
We can use our handler in our dispatch call and handle how error messages are reported to the user (if that's important for your application):
Or something like that...it may be easier to setup a set of streams and stream the left or right side through different processors to handle the values...it's up to you.
Last updated
Was this helpful?