Cookbook
Last updated
Was this helpful?
Last updated
Was this helpful?
This section contains a cook book of useful topics.
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 . 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 Future
s 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:
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.
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?
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.
This link, , gives you an approach and also reminds you that you can write your own customized "ok" handler specific to your application needs.