Reputation: 598
If you read the javadoc on ClientHttpRequestInterceptor.intercept
method, it says:
execute the request using ClientHttpRequestExecution.execute(org.springframework.http.HttpRequest, byte[]), or do not execute the request to block the execution altogether.
The second part of this statement is what I'm trying to achieve (block the execution altogether based on certain criteria). However, I've not been successful in doing so. I've done my research for few hours and still could not find any examples on how that is done.
I tried returning null and I get NPE later on in the flow as Spring tries to get statusCode from the response (exception below).
java.lang.NullPointerException: null
at org.springframework.web.client.DefaultResponseErrorHandler.getHttpStatusCode(DefaultResponseErrorHandler.java:56)
at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:50)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:588)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:536)
Edit: Here's a simple interceptor for reference on what I'm trying to achieve:
public class FailSafeInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
try{
return execution.execute(request, body);
}catch(Throwable e){
return null;
}
}
}
Possible Solution: after sleeping on the problem, I managed to make it work with the following code (using MockResponse). But I'm not sure if that's the way the doc meant the method to be used. If there's a better way to do this, I'd appreciate the help.
public class FailSafeInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
try{
return execution.execute(request, body);
}catch(Throwable e){
return new MockClientHttpResponse(new byte[]{}, HttpStatus.OK);
}
}
}
Upvotes: 1
Views: 3402
Reputation: 15330
I was trying to accomplish the same and chose a different route.
In my case, I didn't want to send requests to an external service in our development environment. We are using RestTemplate
to interact with the external service (actually, we are using a Spring Integration HTTP Outbound Channel Adapter with a configured RestTemplate
). However, I wanted as little configuration to change between environments as possible.
So the solution I came up with was to use a profile-scoped bean so that when the dev
profile is selected, it replaces the RestTemplate
bean used for external communication with one that never actually executes requests.
Here is my configuration:
package com.example.myproject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.AbstractClientHttpResponse;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Configuration
public class InfrastructureConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(MyApplication.class);
/**
* This configuration is used whenever we are running in an environment that
* does not have access to external endpoints.
**/
@Configuration
@Profile("sit")
public static class FakeExternalConfig {
@Bean
@Qualifier("externalRestTemplate")
RestTemplate externalRestTemplate() {
// Returns a Rest Template which will always return HttpStatus.NO_CONTENT without actually executing the request.
return new RestTemplateBuilder()
.additionalInterceptors(((request, body, execution) -> {
LOGGER.info("Fake external request sent. Method: {} URI: {} Body: {} Headers: {}", request.getMethodValue(), request.getURI(), new String(body), request.getHeaders().toString());
return new AbstractClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
return new HttpHeaders();
}
@Override
public InputStream getBody() {
return new ByteArrayInputStream("" .getBytes(StandardCharsets.UTF_8));
}
@Override
public int getRawStatusCode() {
return HttpStatus.NO_CONTENT.value();
}
@Override
public String getStatusText() {
return HttpStatus.NO_CONTENT.getReasonPhrase();
}
@Override
public void close() {
}
};
}))
.build();
}
}
/**
* This configuration is used whenever we are running in an environment that
* does have access to external endpoints.
**/
@Configuration
@Profile({"test", "prod"})
public static class ExternalConfig {
@Bean
@Qualifier("externalRestTemplate")
RestTemplate externalRestTemplate() {
return new RestTemplateBuilder()
.build();
}
}
}
I chose to implement org.springframework.http.client.AbstractClientHttpResponse
as an in-line anonymous class because I wanted this to be the only instance where it could be used and there aren't that many methods that need to be overridden. I chose to always return HTTP status 204 No Content because that seemed to make the most sense to me but any other status code could be chosen. (I was very tempted to use I_AM_A_TEAPOT
but it is a 4xx status which is considered an error :( )
Upvotes: 0
Reputation: 90447
MockClientHttpResponse
is from spring-test
, it is awkward to include a testing class in the production codes.
If you want to abort sending the whole request once you detect something , just throw RuntimeException
(or using the built-in RestClientException
):
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
if(checkNeedAbortSendingRequst()){
throw new RestClientException("Bla blab bla");
}
return execution.execute(request, body);
}
Update :
If you want to fail safe , your current solution looks good expect that I would use a non-200 status code if exception is caught (may be 500?). Since I also cannot find a ClientHttpResponse
implementation that can be created from scratch or without any external dependencies in spring-mvc
, I would copy MockClientHttpResponse
from spring-test
to the project to use.
Upvotes: 1