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;
}
Related
I am using feign client to connect to downstream service.
I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.
I am looking for a best way of doing this.
We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.
You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:
public class ClientWrapper extends ApacheHttpClient {
private ApacheHttpClient delegate;
public ClientWrapper(ApacheHttpClient client) {
this.client = client;
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
/* execute the request on the delegate */
Response response = this.client.execute(request, options);
/* check the response code and change */
if (response.status() == 400) {
response = Response.builder(response).status(200).build();
}
return response;
}
}
This customized client can be used on any Feign client you need.
Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using #RestControllerAdvice )
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
ResponseData responseData;
ObjectMapper mapper = new ObjectMapper();
try {
responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
} catch (Exception e) {
responseData = new ResponseData();
}
return new PartialSuccessException(responseData);
}
return FeignException.errorStatus(methodKey, response);
}}
And the Exception handler as below
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(PartialSuccessException.class)
public ResponseData handlePartialSuccessException(
PartialSuccessException ex) {
return ex.getResponseData();
}
}
Change the microservice response:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(
final SSLSocketFactory sslContextFactory, final HostnameVerifier
hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(final Request request, final Request.Options
options) throws IOException {
Response response = super.execute(request, options);
if (HttpStatus.SC_OK != response.status()) {
response =
Response.builder()
.status(HttpStatus.SC_OK)
.body(InputStream.nullInputStream(), 0)
.headers(response.headers())
.request(response.request())
.build();
}
return response;
}
}
Add a Feign Client Config:
#Configuration
public class FeignClientConfig {
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}
My Goal is to receive some token from downstream server response headers by using ServerHttpResponseDecorator without this I am not able to get response headers in GlobalFilter. based on token I am planning to alter downstream response by raising a custom exception and handled in ErrorWebExceptionHandler.
The problem is once I have read the response headers from downstream service even exception also not able to stop the flow I am getting an original response whatever is coming from downstream service but if I raised an exception before headers reading It is working as expected.
GlobalFilter Sample code
#Component
public class CustomFilter implements GlobalFilter, Ordered {
#Override
public int getOrder() {
return -2;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
#Override
public HttpHeaders getHeaders() {
String tokenFromHeader = super.getHeaders().getFirst("TOKEN");
String regIdFromHeader = super.getHeaders().getFirst("regId");
if (false) { // if (true) { It is hadled by exception handler as expected
// I have some Buginese logic here
throw new RuntimeException();
}
if (tokenFromHeader != null && regIdFromHeader != null) {
if (true) {
//I have some Buginese logic here
// No use I am getting original response from down streams
throw new RuntimeException();
}
}
return getDelegate().getHeaders();
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
}
Exception Handler
public class MyWebExceptionHandler implements ErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
byte[] bytes = ( "Some custom text").getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
exchange.getResponse().getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return exchange.getResponse().writeWith(Flux.just(buffer));
}
}
Expected out put is
Some custom text
But I am getting an original response
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.
I got the following response from my server: status code 201 Created.
There is no actual response (returned object, etc.), so there is not need to create a POJO class.
So, I don't know how I should handle this status code without creating a POJO class. Is there any option to make write the code without using a POJO class?
Retrofit API has Response class that can encapsulate your response.
As long as you don't want to bother with the response data you can implement your service as:
interface CustomService {
#GET("whatever")
Call<Response<Void>> getAll();
// Or using RxJava:
#GET("whatever")
Single<Response<Void>> getRxAll();
}
Then implement your callback:
private Callback<Response<Void>> responseHandler = new Callback<Response<Void>>() {
#Override
public void onResponse(Call<Response<Void>> call, Response<Response<Void>> response) {
final int code = response.code();
// TODO: Do whatever you want with the response code.
}
#Override
public void onFailure(Call<Response<Void>> call, Throwable t) {
// TODO: Handle failure.
}
}
Or reactive consumer:
private Consumer<Response<Void>> responseRxHandler = new Consumer<Response<Void>>() {
#Override
public void accept(Response<Void> response) throws Exception {
final int responseCode = response.code();
// TODO: Do whatever you want with the response code.
}
};
Debugging result:
You can try the following code.
Can get the response without a POJO class by getting using ResponseBody format and then you can parse it normally like ordinary JSON parsing.
Api Call:
Call<ResponseBody> call = service.callLogin(AppConstants.mApiKey, model_obj);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(response.code() == 201)
{
JSONObject jobjresponse = null;
try {
jobjresponse = new JSONObject(mResponse.body().string());
String status = jobjresponse.getString("status");
JSONObject result = jobjresponse.getJSONObject("results");
String msg = result.getString(“msg”);
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
Retrofit Interface class:
public interface RetrofitInterface {
#Headers({"Content-Type: application/json", "Cache-Control: max-age=640000"})
#POST("v1/auth/")
public Call<ResponseBody> callLogin(#Query("key") String key, #Body LoginModel body);
public static final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(“base url”)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
Sample Response:
{ "status":"true", "result":{"msg”:”created successfully”} }
I'm using GWT 2.3 and I have json-p requests in my code similar to this:
JsonpRequestBuilder jsonp = new JsonpRequestBuilder();
jsonp.requestObject(jsonUrl, new AsyncCallback<T>() {
public void onFailure(Throwable throwable) { // error }
public void onSuccess(T t) { //do something }
});
some GET-requests return 200, others 302 and so on, and I should be
able to return a different "answer" respect to this value. How can I
know what's the response value returned?
I think you can not access the response code using the JsonpRequestBuilder. But if you use the standard RequestBuilder instead you can get the response code using getStatusCode(). Of course you have to then the parse the response text yourself.
RequestBuilder r = new RequestBuilder(RequestBuilder.GET, jsonUrl);
r.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
// error
}
public void onResponseReceived(Request request, Response response) {
if (response.getStatusCode() == 200) {
//do something
} else if (response.getStatusCode() == 302) {
//do something else
}
}
});