Sometimes requests fail, and in case it's not due to client error (i.e.: not 4xx) then I'd like to retry to send the same request.
All my requests have a request id header, and when I send a certain request again (retry) I need to send the same id as the one used in the first place.
This sounds like a simple thing, but it turns out to be quite hard to accomplish with Retrofit 2, unless of course I'm missing something.
All of my requests are async, and so in the callback, in case I need to retry I'm doing this:
public void onResponse(final Call<T> call, final Response<T> response) {
if (response.isSuccessful()) {
handleResponse(response);
} else if (response.code() >= 400 && response.code() < 500) {
handleClientError(response);
} else {
call.clone().enqueue(this);
}
}
I also have an interceptor for adding headers to all requests:
new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
final Request request = chain.request();
final Request.Builder newRequestBuilder = request.newBuilder()
.addHeader("Header1-name", "Header1-value")
.addHeader("Header2-name", "Header2-value")
...
.addHeader("HeaderN-name", "HeaderN-value");
if (request.header("REQUEST-ID") == null) {
newRequestBuilder.addHeader("REQUEST-ID", UUID.randomUUID().toString());
}
return chain.proceed(newRequestBuilder.build());
}
};
I thought that since I'm cloning the Call then the (retry) request will have the headers of the previous one but that's not the case (probably because the Call is cloned and not the Request).
My problem is that I have no way of identifying the request or call in my Interceptor.intercept so I can't maintain a map of request/calls to id.
There's also no way of adding info to the calls/requests (as they are not generated by me and lack and setters for such a case).
I thought that maybe I can use the Request.tag method but again, I have no control of the object instance that is assigned there and the objects are different between requests.
And if we're already on this subject, what is this tag anyway? I can't find documentation about it.
Any idea how I can somehow pull this off?
Thanks
I took another approach to this, instead of using network interceptors and callbacks, I wrote a solution which uses a network interceptor:
class BackoffRetryInteceptor implements Interceptor {
private static final long RETRY_INITIAL_DELAY = 500;
private static final long RETRY_MAX_DELAY = 35000;
#Override
public Response intercept(final Chain chain) throws IOException {
Request request = chain.request();
final Headers.Builder headersBuilder = new Headers.Builder();
addHeaders(headersBuilder);
final Headers headers = headersBuilder.build();
long delay = RETRY_INITIAL_DELAY;
Response response = null;
IOException exception = null;
while (delay < RETRY_MAX_DELAY) {
exception = null;
request = request.newBuilder().headers(headers).build();
try {
response = chain.proceed(request);
if (response.isSuccessful() || response.code() != 500) {
return response;
}
} catch (IOException e) {
exception = e;
}
try {
Thread.sleep(delay);
delay *= 2;
} catch (InterruptedException e) {
delay = RETRY_MAX_DELAY;
}
}
if (exception != null) {
throw exception;
}
return response;
}
private static void addHeaders(final Headers.Builder headers) {
headers.add("Header1-name", "Header1-value")
.add("Header2-name", "Header2-value")
...
.add("HeaderN-name", "HeaderN-value")
.add("Request-Id", UUID.randomUUID().toString());
}
}
This seems to work well in my tests.
The main problem though is the blocking of the network threads.
If anyone can think up a better solution I'd love to hear it.
Related
This is AS-IS code.
public void send() throws IOException {
CloseableHttpResponse httpResponse = null;
CloseableHttpClient httpClient = null;
try {
HttpPost post = new HttpPost(url);
httpResponse = httpClient.execute(post);
} finally
{
closeapi(httpClient);
closeapi(httpResponse);
}
}
public static void closeapi(Closeable obj)
{
if (obj != null)
{
try
{
obj.close();
} catch (IOException e)
{
logger.error(LP.EXCEPTION, e);
}
}
}
And I changed that code using feign client. like this.
**[MessageFeignClient.class]**
#FeignClient(
contextId = "messageClient",
url = "${url}",
name = "messageclient",
configuration = {FeignConfiguration.class, FeignRetryConfiguration.class},
primary = false
)
public interface MessageClient {
#PostMapping("write")
PushMessageResultRdo write(
#RequestHeader("Key1") String key1,
#RequestHeader("Key2") String key2,
#RequestBody PushMessage pushMessage);
#PostMapping("end")
PushMessageResultRdo end(
#RequestHeader("Key1") String key1,
#RequestHeader("Key2") String key2,
#RequestBody PushMessage pushMessage);
}
**[MessageService.java]**
#Slf4j
#Component
#RequiredArgsConstructor
public class MessageService {
private final MessageClient messageClient;
#Override
public PushResultRdo apply(PushMessage pushMessage) {
try {
return messageClient.write(pushMessage.getKey1(), pushMessage.getKey2(), pushMessage);
} catch (HystrixRuntimeException e) {
log.error(e);
}
return PushResultRdo.defaultError();
}
}
Functionally, there is no problem.
But using feign call is not closed httpclient.
Response status is 200. But request is alived.
I checked tcp stream through wireshark program.
When messageClient.write called, after then I expect TCP [FIN, ACK] sequence.
But If using feign client, there is no connection close.
I want to close request connection.
Anyone help, please.
Thank you!
This is a useful way, but I think it's not a good solution.
I figure out feign options.
[HttpClientFeignConfiguration.class]
this.connectionManagerTimer.schedule(new TimerTask() {
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000L, (long)httpClientProperties.getConnectionTimerRepeat());
Feign client has a schedule task about closing connections.
This task's period is 3000 seconds(this is default value. you can change period. ex. feign.httpclient.connectionTimerRepeat = 30)
And connectionManager call PoolingHttpClientConnectionManager.class, it checks pool expired.
[PoolingHttpClientConnectionManager.class]
if (timeToLive > 0L) {
long deadline = this.created + timeUnit.toMillis(timeToLive);
this.validityDeadline = deadline > 0L ? deadline : 9223372036854775807L;
} else {
this.validityDeadline = 9223372036854775807L;
}
Feign default timetoLive is 900 Seconds. So If you want to close immediately after feign call, then change the timetolive value and timeToLiveUnit.
feign:
httpclient:
timeToLive: 30
# timeToLiveUnit : MILLISECONDS
I calling to the api with the basic retrofit Call object:
public interface dataApi {
#GET("animal/cats")
Call<AllAnimals> getAllData(
#Query("api_key") String apiKey
);
}
And I can get the response inside my view model like this:
call.enqueue(new Callback<AllAnimals>() {
#Override
public void onResponse(Call<AllAnimals> call, Response<AllAnimals> response) {
animals.setValue(response.body());
}
#Override
public void onFailure(Call<AllAnimals> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
Nothing speical here.
I've several problem with this approach
FIRST - if I give the wrong api key for example, the response should give me a response with the code of the problem, instead I just get null body.
SECOND I am planning to have more api calls, and it's a huge code duplication to handle errors every call I wrote.
How can I implement custom error handling for this situation, that will be apply to other calls too?
I think you can use okhttp interceptor and define yourself ResponseBody converter to fix your problem.
First,intercept you interested request and response;
Second,check the response,if response is failed then modify the response body to empty。
define a simple interceptor
Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String url = request.url().toString();
System.out.println(request.url());
okhttp3.Response response = chain.proceed(request);
if (!response.isSuccessful() && url.contains("animal/cats")) {
// request failed begin to modify response body
response = response.newBuilder()
.body(ResponseBody.create(MediaType.parse("application/json"), new byte[] {}))
.build();
}
return response;
}
};
define self ResponseBody converter
most code from com.squareup.retrofit2:converter-jackson we just add two lines:
final class JacksonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final ObjectReader adapter;
JacksonResponseBodyConverter(ObjectReader adapter) {
this.adapter = adapter;
}
#Override public T convert(ResponseBody value) throws IOException {
try {
if (value.contentLength() == 0) {
return null;
}
return adapter.readValue(value.charStream());
} finally {
value.close();
}
}
}
the below code is added:
if (value.contentLength() == 0) {
return null;
}
I have a situation where I need to return an "accepted" response for every request received and publish the actual response later to a separate endpoint outside the service.
To implement the 'accepted' Response I implemented a filter.
public class AcknowledgementFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.abortWith(Response.accepted().build());
// call Resource method in new Thread() . <------ ?
}
}
Implementation of service endpoints:
#Path("/vendor")
public class VendorHandler {
#POST
public void addVendor(VendorRequest addVendorRequest)){
vendor = new Vendor();
Client.publish(vendor); // publish request to an endpoint
return null;
}
How do I call the addVendor of VendorHandler(or any method depends on request) from the acknowledgement filter?
Is there any other way to implement an accepted response for every request then process the request separately?
You can use AsyncResponse,
#GET
#ManagedAsync
#Produces(MediaType.APPLICATION_JSON)
public void getLives(#Suspended final AsyncResponse asyncResponse,
#DefaultValue("0") #QueryParam("newestid") final int newestId,
#QueryParam("oldestid") final Integer oldestId) {
asyncResponse.setTimeoutHandler(asyncResponse1 -> {
logger.info("reached timeout");
asyncResponse1.resume(Response.ok().build());
});
asyncResponse.setTimeout(5, TimeUnit.MINUTES);
try {
List<Life> lives = oldestId == null ?
Lifes.getLastLives(newestId) : Lifes.getOlderLives(oldestId);
if (lives.size() > 0) {
final GenericEntity<List<Life>> entity = new GenericEntity<List<Life>>(lives) {
};
asyncResponse.resume(entity);
} else LifeProvider.suspend(asyncResponse);
} catch (SQLException e) {
logger.error(e, e);
asyncResponse.resume(new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR));
}
}
Check this Link for more details.
Hi folks I'm creating an android application's login/register part using the Android Volley Library. My application was working well, but the UI and logic were at the same class. So, I have separated them into two classes. My app makes requests to my NodeJS server using POST methods and gets JSON response. So I have tried to keep the POST request function in another class.
After separating the classes, I have a problem while waiting for response. Here is the function;
public String doWebRequestLogin(Context context, boolean checkLoginForm, final Map<String,String> json){
result[0] = "FREE";
this.context = context;
if(checkLoginForm){
StringRequest post = new StringRequest(Request.Method.POST, loginUrl, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
try {
Log.d("Login Response: ",response);
data = response;
res = new JSONObject(data);
if (res.getString(KEY_SUCCESS) != null) {
int success = Integer.parseInt(res.getString(KEY_SUCCESS));
if (success == 1) {
result[0] = "LOGGED";
} else if (success == 0) {
result[0] = "LOGIN ERROR";
} else {
result[0] = "INVALID POST";
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.d("Response Error", error.toString());
result[0] = "INVALID POST";
}
}){
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> map = json;
return map;
}
};
VolleyController.getInstance(this.context).getRequestQueue().add(post);
}
return result[0];
}
This function returns result[0] as "FREE" at every time due to response time. How could it wait for the response and set result[0] according to the response? I need to know what happened while making requests.
I'm calling doWebRequestLogin() on the UI within an onclick function
Then you do NOT want to "wait for the response". That will freeze your UI for however long the network I/O takes, and your users will... be unimpressed.
Instead, update your UI in the onResponse() and onErrorResponse() methods.
This sort of asynchronous call, handling the results via callbacks, is core to the event-driven programming model at the heart of Android.
The request is asynchronous and you must not block the main thread waiting for a response. Make the method void and use a callback to handle the response once it's received.
public void doWebRequestLogin(SomeCallback callback, Context context, boolean checkLoginForm, final Map<String,String> json){
[...]
if (res.getString(KEY_SUCCESS) != null) {
int success = Integer.parseInt(res.getString(KEY_SUCCESS));
callback.someMethod(success);
}
}
For the callback:
public interface SomeCallback{
void someMethod(int result); // response received, handle it
}
Callback may also have a return type or be generic, this depends solely on your needs...
I'm in the midst of testing my application which is using an HTTP-server. Instead of mocking I decided to go with a HTTP server fixture. Meaning that I do not have to mock any productional code. To accomplish this goal I currently chose for a free to use 3rd party library fixd.
I was able to successfully create several unit tests - which are working by means of a GET request. Most are quite simple, i.e.:
#Test
public void verifyConnectionTest()
{
try
{
final String body = FileUtils.readFileToString(RESOURCE);
final String path = "/";
this.server.handle(Method.GET, path).with(
new HttpRequestHandler() {
#Override
public void handle(final HttpRequest request,
final HttpResponse response)
{
response.setStatusCode(200);
response.setContentType("text/xml");
response.setBody(body);
}
});
// Setting up my HTTP client
// Execute some tasks
// asserting of everything was valid
}
catch (final IOException e)
{
fail(e.getMessage());
}
}
But I now have to send a POST request with multipart/form-data. Which does not make much of a difference other than changing the method and content-type:
#Test
public void executeStepTest()
{
try
{
final String body = FileUtils.readFileToString(SERVICE_RESPONSE);
final String path = "/";
this.server.handle(Method.POST, path, "multipart/form-data").with(
new HttpRequestHandler() {
#Override
public void handle(final HttpRequest request,
final HttpResponse response)
{
response.setStatusCode(200);
response.setContentType("text/xml");
response.setBody(body);
}
});
// Setting up my HTTP client
// Execute some tasks
// asserting of everything was valid
}
catch (final IOException e)
{
fail(e.getMessage());
}
}
However I get the following error: [ERROR] could not find a handler for POST - / - multipart/form-data; boundary=bqCBI7t-VW1xaJW7BADmTiGMg9w_YM2sHH8ukJYx and my guess is that fixd doesn't recognize the boundary-party. Since the documentation does not show an example I'm quite stuck on this part.
I tried using some wildcards such as '*', no succes. Thus; I need a way to either tell fixd to accept that boundary or use some wildcards I didn't yet discover. Any help would be great, thanks!
I've been making some debug and it seems to be that the problem is in the fixd core.
Basically, fixd indexes every RequestHandlerImpl by a HandlerKey (which includes ContentType as part of the key) in the map handlerMap. See method org.bigtesting.fixd.core.FixtureContainer#resolve.
...
HandlerKey key = new HandlerKey(method, route, contentType);
RequestHandlerImpl handler = handlerMap.get(key);
if (handler == null) {
// Error
}
...
Problem: When the request is multipart/form-data, boundary data (which it's generated dinamically every request) is part of the content type. So, any handler is found in handlerMap because the key changes with every running.
I've made a little test only to check that this is the cause of the problem, passing the contentType to fixd server.handle after the creation of the multipart request, and it works fine.
See the test below:
#Test
public void verifyConnectionTest_multipart() {
try {
// 1. Create multipart request (example with http-commons 3.1)
PostMethod filePost = new PostMethod(url);
Part[] parts = { new StringPart("param", "value") };
MultipartRequestEntity request = new MultipartRequestEntity(parts, filePost.getParams());
filePost.setRequestEntity(request);
// 2. fixd server handle (passing the request content type)
this.server.handle(Method.POST, "/", request.getContentType()).with(
new HttpRequestHandler() {
#Override
public void handle(final HttpRequest request,
final HttpResponse response) {
response.setStatusCode(200);
response.setContentType("text/xml");
}
});
// 3. Execute multipart request
HttpClient client = new HttpClient();
int status = client.executeMethod(filePost);
// 4. Assertions
Assert.assertEquals(200, status);
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
Hope it helps you to clarify the problem. Cheers
This was a bug in fixd, and has been fixed in version 1.0.3. Your original code should work using this new version of fixd.