Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring RestTemplate, intercepting response before parsing to Json

I have a REST api that responds with some additional non JSON data in the body content. This breaks the use of RestTemplate and jackson. Can I intercept the http response body prior to the parsing?

I am using RestTemplate.getForObject.

I've taken a look at the RestTemplate and couldn't see an appropriate method.

like image 307
trorbyte Avatar asked Nov 02 '15 16:11

trorbyte


People also ask

How do you intercept a response in Spring boot?

To work with interceptor, you need to create @Component class that supports it and it should implement the HandlerInterceptor interface. preHandle() method − This is used to perform operations before sending the request to the controller. This method should return true to return the response to the client.

Does RestTemplate use ObjectMapper?

So, by simply using the RestTemplateBuilder our RestTemplate will automatically use a MappingJackson2HttpMessageConverter configured with an ObjectMapper that uses the required ParameterNamesModule.

What is interceptor in RestTemplate?

Interceptors are generally used in Spring to intercept requests of either self created end point at Controller, OR, to intercept other(3rd party) api calls done by RestTemplate.

Should I use WebClient instead of RestTemplate?

Even on the official Spring documentation, they advise to use WebClient instead. WebClient can basically do what RestTemplate does, making synchronous blocking calls. But it also has asynchronous capabilities, which makes it interesting. It has a functional way of programming, which makes it easy to read as well.


2 Answers

You can try to implement ClientHttpRequestInterceptor and assign it to restTemplate. Implement intercept method:

@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
        ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {

         ClientHttpResponse  response=clientHttpRequestExecution.execute(httpRequest, bytes);
         //...do magic with response body from getBody method
         return response;
}

You might have to extend AbstractClientHttpResponse with your own implementation to do that.

Another option could be to treat the response from the REST API as String, then format the string as needed and explicitly map it to object using ObjectMapper.

Then in your restTemplate you would have:

 String result = restTemplate.getForObject(url, String.class, host);
 //..trim the extra stuff
 MyClass object=objectMapper.readValue(result, MyClass.class);

Yet another option would be to implement your own HttpMessageConverter which extends AbstractJackson2HttpMessageConverter and register it with restTemplate. In my opinion that would be the cleaneast from the Spring point of view

like image 110
jny Avatar answered Sep 27 '22 20:09

jny


Another way would be to unwrap the response by implementing a ClientHttpRequestInterceptor along with a ClientHttpResponse.

        @Component
        public class MyInterceptor implements ClientHttpRequestInterceptor {

        @Autowired
        Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory;

        @Autowired
        MyRequestAdvice requestAdvice;

        @Override
          public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
            byte[] wrappedBody = requestAdvice.wrapRequest(bytes);
            ClientHttpResponse res = clientHttpRequestExecution.execute(httpRequest, wrappedBody);
            return responseWrapperBeanFactory.apply(res);
        }    

       }

Here's the bean config for the MyResponseWrapper:

@Bean
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory() {
    return this::getMyResponseWrapper;
}

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyResponseWrapper getMyResponseWrapper(ClientHttpResponse originalResponse) {
    return new MyResponseWrapper(originalResponse);
}

@Bean
public RestTemplate restTemplate(@Autowired MyInterceptor interceptor) {
    RestTemplate t = new RestTemplate();
    t.setInterceptors(Arrays.asList(interceptor));
    // other setup code

    return t;
}

And here's the ClientHttpResponse implementation:

public class MyResponseWrapper implements ClientHttpResponse {

    private byte[] filteredContent;
    private ByteArrayInputStream responseInputStream;


    private ClientHttpResponse originalResponse;

    public MyResponseWrapper(ClientHttpResponse originalResponse) {
        this.originalResponse = originalResponse;

        try {
            filteredContent = MyContentUnwrapper.unwrapResponse(originalResponse.getBody().readAllBytes());
        } catch (Exception e) {
            throw new RuntimeException("There was a problem reading/decoding the response coming from the service ", e);
        }
    }

    @Override
    public HttpStatus getStatusCode() throws IOException {
        return originalResponse.getStatusCode();
    }

    @Override
    public int getRawStatusCode() throws IOException {
        return originalResponse.getRawStatusCode();
    }

    @Override
    public String getStatusText() throws IOException {
        return originalResponse.getStatusText();
    }

    @Override
    public void close() {
        if (responseInputStream != null) {
            try {
                responseInputStream.close();
            } catch (IOException e) { /* so long */}
        }
    }

    @Override
    public InputStream getBody() throws IOException {
        if (responseInputStream == null) {
            responseInputStream = new ByteArrayInputStream(filteredContent);
        }
        return responseInputStream;
    }

    @Override
    public HttpHeaders getHeaders() {
        return originalResponse.getHeaders();
    }
}
like image 24
PA Lemire Avatar answered Sep 27 '22 20:09

PA Lemire