how to read body using RestTemplate in case of an error? - java

I'm using RestTemplate to communicate with one REST service, i use
restTemplate.postForEntity(uri, request, MyResponse.class);
} catch (HttpClientErrorException e)
however when there is an error (4xx or 5xx) REST service returns description in JSON in a body, but HttpClientErrorException.getResponseBodyAsString()
returns null. RestTemplate (if i try to retrieve response as String), doesn't return anything as well.
How to retrieve body in case of error?

You can access the response for errors via a ResponseErrorHandler. The ResponseErrorHandler needs to be set for the RestTemplate with the setErrorHandler method.
The ResponseErrorHandler provides the response via the two methods
boolean hasError(ClientHttpResponse response) throws IOException;
void handleError(ClientHttpResponse response) throws IOException;

Problem was recognised, it's described here
https://jira.spring.io/browse/SPR-16781?focusedCommentId=159473&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-159473
and here
https://jira.spring.io/browse/SPR-9367
you need to use different http library, for example Apache HttpClient (not a default one from JDK).
Add to your project:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
and configure RestTemplate with:
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
you can use ResponseErrorHandler if you need, but it's not necessary.
Now response body (even in error situation) is stored in HttpClientErrorException, read it with getResponseBodyAsString() method.

Related

Accessing StreamListener headers from RequestContext or similar

I have a service which calls a dozen other services. This reads from a Kafka topic using a #StreamListener in a controller class. For traceability purposes, the same headers(original request ID) from the Kafka message need to be forwarded to all the other services as well
Traditionally, with a #PostMapping("/path") or GetMapping, a request context is generated, and one can access the headers from anywhere using RequestContextHolder.currentRequestAttributes() and I would just pass the HttpHeaders object into a RequestEntity whenever I need to make an external call
However in a StreamListener, no request context is generated and trying to access the RequestContextHolder results in an exception
Here's an example of what I tried to do, which resulted in an exception:
public class Controller {
#Autowired Service1 service1
#Autowired Service2 service2
#StreamListener("stream")
public void processMessage(Model model) {
service1.execute(model);
service2.execute(model);
}
}
public class Service {
RestTemplate restTemplate;
public void execute(Model model){
// Do some stuff
HttpHeaders httpHeaders = RequestContextHolder.currentRequestAttributes().someCodeToGetHttpHeaders();
HttpEntity<Model> request = new HttpEntity(model, httpHeaders);
restTemplate.exchange(url, HttpMethod.POST, request, String.class);
}
}
My current workaround is to change the StreamListener to a PostMapping and have another PostMapping which calls that so a request context can be generated. Another option was to use a ThreadLocal but it seems just as janky
I'm aware of the #Headers MessageHeaders annotation to access the stream headers, however, this isn't accessible easily without passing the headers down to each and every service and would affect many unit tests
Ideally, I need a way to create my own request context (or whatever the proper terminology is) to have a place to store request scoped objects (the HttpHeader) or another thread safe way to have request headers passed down the stack without adding a request argument to service.execute
I've found a solution and am leaving it here for anyone else trying to achieve something similar
If your goal is to forward a bunch of headers end-to-end through REST controllers and Stream listeners, you might want to consider using Spring Cloud Sleuth
Add it to your project through your maven or gradle configuration:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
Specifically, in Spring Cloud Sleuth there is a feature to forward headers or "baggage" by setting the property spring.sleuth.propagation-keys in your application.properties. These key-value pairs are persisted through the entire trace, including any downstream http or stream calls which also implement the same propagation keys
If these fields need to be accessed on a code level, you can get and set them using the ExtraFieldPropagation static functions:
ExtraFieldPropagation.set("country-code", "FO"); // Set
String countryCode = ExtraFieldPropagation.get("country-code"); // Get
Note that the ExtraFieldPropagation setter cannot set a property not present in the defined spring.sleuth.propagation-keys so arbitrary keys won't be accepted
You can read up on the documentation for more information

Spring Boot how to ignore HttpStatus Exceptions

I'm building an Application using Spring Boot. This application is distributed, which means I have multiple API's that call each others.
One of my underlying services interacts with a database and responds with the requested data. If a request to an unexisting ID is made, I response with a 404 HttpStatus:
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
(Same with 400 error on certain operations, or 204 for deleting an entry etc).
The problem is that I have some other Spring Boot applications that call these API's, throw an org.springframework.web.client.HttpClientErrorException: 404 Not Found Exception when they request, in this example, an unexisting entry. But the 404 status code is intended and should not return this exception (causing my Hystrix circuit breaker to call its fallback function).
How can I solve this problem?
The call to the service is implemented like this in my code: ResponseEntity<Object> data = restTemplate.getForEntity(url, Object.class);
My RestTemplate is set up like this:
private RestTemplate restTemplate = new RestTemplate();
Spring's RestTemplate uses a ResponseErrorHandler to handle errors in responses. This interface provides both a way to determine if the response has an error (ResponseErrorHandler#hasError(ClientHttpResponse)) and how to handle it (ResponseErrorHandler#handleError(ClientHttpResponse)).
You can set the RestTemplate's ResponseErrorHandler with RestTemplate#setErrorHandler(ResponseErrorHandler) whose javadoc states
By default, RestTemplate uses a DefaultResponseErrorHandler.
This default implementation
[...] checks for the status code on the
ClientHttpResponse: any code with series
HttpStatus.Series.CLIENT_ERROR or HttpStatus.Series.SERVER_ERROR is
considered to be an error. This behavior can be changed by overriding
the hasError(HttpStatus) method.
In case of an error, it throws the exception you are seeing.
If you want to change this behavior, you can provide your own ResponseErrorHandler implementation (maybe by overriding DefaultResponseErrorHandler) which doesn't consider 4xx as an error or that doesn't throw an exception.
For example
restTemplate.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false; // or whatever you consider an error
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// do nothing, or something
}
});
You can then check the status code of the ResponseEntity returned by getForEntity and handle it yourself.

Setting response header using interceptor?

I'm writing jax-rs end points. For some set of end points (existing code), I want to set an extra response header which was actually generated in #AroundInvoke interceptor and set to HttpServletRequest attribute. In #AroundInvoke I'm able to access HttpServletRequest using #Inject. But it seems I cannot access HttpServletResponse in the same interceptor itself.
It seems I can do with PostProcessorInterceptor but again I'm confused with the following doc.
The org.jboss.resteasy.spi.interception.PostProcessInterceptor runs after the JAX-RS method was invoked but before MessageBodyWriters are invoked. They can only be used on the server side. Use them if you need to set a response header when there might not be any MessageBodyWriter invoked.
I'm using resteasy, jackson. If I use PostProcessorInterceptor can I inject HttpServletResponse? Or Can I set new http header there some how?
Any code example/direction would be appreciated.
With JaxRS 2 (which comes with javaEE 7) you can use a ContainerResponseFilter see also
public class PoweredByResponseFilter implements ContainerResponseFilter {
#Inject
HttpServletRequest request;
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
String name = "X-My-Header";
String value = "";// some data from request
responseContext.getHeaders().add(name, value);
}
}

How can you access the Response object inside ContainerResponseFilter?

In Jersey 1 you can create a container response filter and get access to the Response:
public ContainerResponse filter(ContainerRequest request, ContainerResponse response)
{
Response r = response.getResponse();
// Now I have access to Reponse.getMetadata(), etc.
}
But in Jersey 2, the ContainerResponseFilter only gives me the response context:
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException
{
// responseContext gives me the entity, but I want the JAX-RS Response object that my resources created
}
The implementation of ContainerResponseContext is ContainerResponse, which requires a JAX-RS Response object to build. So why can't I get access to it from within the filter? Am I missing something?
The problem I have is that my resources all build Response objects and attach meta data to them, and I'd like a response filter that can examine this meta data. Without access to the raw Response, this doesn't seem possible.
According to the Javadocs API the Response.getMetadata() is considered deprecated, even though it's not marked as such as of 2.12. The preferred alternative is to use HTTP headers.
you can get access to every response object by implementing a custom ResourceMethodInvocationHandler. Look at my answer # Registering a custom ResourceMethodInvocationHandler in Jersey

Make RestTemplate process response body regardless of status

I'm using Spring MVC, and I'm trying to experiment with the Facebook API, just for fun.
The problem I'm having currently, is that Facebook's GRAPH Api returns other status codes than 200 when it encounters an OAuthException. However, the body of the response is still a valid json object, and I would like to parse it into my object.
This way, my restTemplate will invoke the errorhandler, when the status code is anything else than HTTP.2xx, and not parse the response to my object.
Is there any way of configuring the RestTemplate so that it should parse the response body regardless of http status?
Thanks!
you could set a customer ResponseErrorHandler
restTemplate.setErrorHandler(customerErrorHandler)
you'll just need to implement the following two methods
boolean hasError(ClientHttpResponse response) throws IOException;
void handleError(ClientHttpResponse response) throws IOException;
in your case hasErrorcould always return false

Categories