Propagate exception from CXF interceptor to exception mapper - java

I've a flow where on CXF client I've jaxrs-in-interceptor, provider and exception mapper. In my case I'm catching bad response from client through in-interceptor and then I would like abort the cxf bus chain and throw a fault. Unfortunately I couldn't do it, cause in every situation exception thrown from interceptor is being only logged, but the main error (wrong json format) is propagated to exception mapper. I would like to avoid Exception mapper, but I don't know how. I'm using WebClient to implement interceptors like this:
#Component
public class MyInterceptor extends AbstractPhaseInterceptor<Message> {
public MyInterceptor() {
super(POST_STREAM);
}
#Override
public void handleMessage(Message message) throws Fault {
if (message != null) {
//message.getExchange().setOneWay(true);
//message.getExchange().put(Exception.class, new MyException());
//message.getInterceptorChain().abort();
//message.setContent(Exception.class, new MyException());
//Endpoint ep = message.getExchange().get(Endpoint.class);
//message.getInterceptorChain().abort();
//if (ep.getInFaultObserver() != null) {
// ep.getInFaultObserver().onMessage(message);
//}
//throw new WebApplicationException( new MyException());
//message.setContent(Response.class, response);
throw new Fault(new MyException());
}
}
I read that I should implement jaxrs-filter cause exceptions thrown by interceptor are not propagated to exception mapper. Is it any way to do that in java thanks to WebClient implementation?
S client = create(url, clazz, list(jsonProvider(), providers));
WebClient.getConfig(client).getInInterceptors().add(new MyInterceptor());
I've also tried to use different phases on interceptor, but it also didn't work.

I have been researching and testing with your issue. The problem is that the exceptions thrown from the CXF interceptors escape the JAX-RS flow (see the answer of CXF team)
A Fault generated from interceptor can be catched implementing handleFault in the interceptor itself
public void handleFault(Message message) {
Exception e = message.getContent(Exception.class);
}
Or implementing a FaultListener and registering it at CXF Bus
WebClient.getConfig(client).getBus().getProperties().put("org.apache.cxf.logging.FaultListener",new MyFaultListener());
public class MyFaultListener implements FaultListener{
public boolean faultOccurred(final Exception exception,final String description,final Message message) {
//return false to avoid warning of default CXF logging interceptor
return false;
}
}
But you can not return custom response from interceptor or respond a Fault to client.
The workaround I have found to achieve the desired behaviour consist in replacing the Response with a custom object that could be processed by your usual method invokation, like an exceptionMapper
See CXF/ JAX-RS : Return Custom response from interceptor
Into Interceptor.handleMessage check the conditions you need and create a Response with custom status and entity. After this, stop the chain
public class MyInterceptor extends AbstractPhaseInterceptor<Message> {
public MyInterceptor() {
super(Phase.POST_STREAM);
}
#Override
public void handleMessage(Message message) throws Fault {
if (message != null) {
//check the condition to raise the error
//build the custom Response replacing service call
Response response = Response
.status(Response.Status.BAD_REQUEST)
.entity("custom error")
.build();
message.getExchange().put(Response.class, response);
//abort interceptor chain in you want to stop processing or throw a Fault (catched by handleFault)
//message.getInterceptorChain().abort();
//throw new Fault (new MyException());
}
public void handleFault(Message messageParam) {
}
}
Add the ResponseExceptionMapper as provider when creating the JAXRS client
providers.add(new ResponseExceptionMapper<WebApplicationException>() {
#Override
public WebApplicationException fromResponse(Response r) {
return new WebApplicationException(r);
}
});
YourService proxy = JAXRSClientFactory.create(url, clazz,providers);
Client client = WebClient.client(proxy);
WebClient.getConfig(client).getInInterceptors().add(new MyInterceptor());
After this, a call to proxy.yourService() will raise a WebApplicationException if acomplish the interceptor check. You can catch it or rethrow in the desired way
try{
proxy.yourService();
}catch (WebApplicationException e){
}
Hope this helps

I fully agree with previous answer. My implementation looks like:
#Component
public class ServiceFailureInterceptor extends AbstractPhaseInterceptor<Message> {
private static final Logger LOG = LoggerFactory.getLogger(ServiceFailureInterceptor.class);
public ServiceFailureInterceptor() {
super(PRE_STREAM);
}
#Override
public void handleMessage(Message message) {
if (message != null) {
int responseCode = (int) message.get(Message.RESPONSE_CODE);
LogicException logicException = ErrorMapper.HTTP_STATUS_CODE_MAPPER.get(responseCode);
InputStream is = b2stream(MapperUtils.json().toBytes(logicException));
// clear old message & exchange
Exchange exchange = message.getExchange();
for (Class<?> contentFormat : message.getContentFormats()) {
message.setContent(contentFormat, null);
}
resetOrigInterceptorChain(message);
resetFault(exchange);
message.setContent(InputStream.class, is);
Message outMessage = createOutMessage(exchange, is);
prepareMessage(outMessage);
prepareMessage(message);
}
}
private void prepareMessage(Message message) {
message.put(Message.REQUESTOR_ROLE, true);
message.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
}
private Message createOutMessage(Exchange exchange, InputStream logicException) {
Endpoint ep = exchange.get(Endpoint.class);
Message outMessage = ep != null ? ep.getBinding().createMessage() : new MessageImpl();
outMessage.setContent(InputStream.class, logicException);
exchange.setOutMessage(outMessage);
outMessage.setExchange(exchange);
return outMessage;
}
private void resetFault(Exchange exchange) {
exchange.put(Exception.class, null);
}
private void resetOrigInterceptorChain(Message message) {
InterceptorChain chain = message.getInterceptorChain();
if (chain != null) {
for (Interceptor<?> interceptor : chain) {
chain.remove(interceptor);
}
chain.reset();
}
}
}
After setting this exception manually I'm going to ExceptionMapper implementation where my LogicException is consumed and response with exception is building. I cannot avoid Exception mapper when is declared as a provider through WebClient, so I've decided to use it and remapped Exception later.

Related

Feign Client Error Handling - Suppress the Error/Exception and convert to 200 success response

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);
}
}

Netflix's Zuul Error filter implementation

What would be an example implementation of an error filter for netflix's zuul gateway service? All the examples I have found have been either incorrect or too old to work.
You can create your own custom filter and put that filter to be executed right before Zuul's default SendErrorFilter, but sometimes the default filter will override your body or HTTP error.
I prefer to disable the default filter by putting this in application properties:
zuul.SendErrorFilter.error.disable=true
Then create your own CustomSendErrorFilter by extending the default one.
An example implementation would be:
#Component
public class SendErrorCustomFilter extends SendErrorFilter {
private static final Logger LOG = LoggerFactory.getLogger(SendErrorCustomFilter.class);
private static final String SERVLET_ERROR_STATUS_CODE = "javax.servlet.error.status_code";
private static final String SERVLET_ERROR_EXCEPTION = "javax.servlet.error.exception";
private static final String SERVLET_ERROR_MESSAGE = "javax.servlet.error.message";
#Value("${error.path:/error}")
private String errorPath;
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
ExceptionHolder exception = findZuulException(ctx.getThrowable());
HttpServletRequest request = ctx.getRequest();
Throwable cause = exception.getThrowable().getCause();
int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
if (causeIsIOError(cause)) {
statusCode = HttpServletResponse.SC_CONFLICT;
} else if (causeIsAuthorizationError(cause)) {
statusCode = HttpServletResponse.SC_UNAUTHORIZED;
}
request.setAttribute(SERVLET_ERROR_STATUS_CODE, statusCode);
LOG.warn("Error during filtering", cause);
request.setAttribute(SERVLET_ERROR_EXCEPTION, cause);
if (StringUtils.hasText(exception.getErrorCause())) {
request.setAttribute(SERVLET_ERROR_MESSAGE, cause.getMessage());
}
RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPath);
if (dispatcher != null) {
ctx.set(SEND_ERROR_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.getStatusCode());
dispatcher.forward(request, ctx.getResponse());
}
}
} catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private boolean causeIsIOError(Throwable cause) {
return cause instanceof InvalidTokenPayloadException
|| cause instanceof InvalidResponseBodyException;
}
public boolean causeIsAuthorizationError(Throwable cause) {
return cause instanceof InvalidJWTTokenException ||
cause instanceof NoPermissionForResourceException ||
cause instanceof MissingAuthorizationHeaderException;
}
This way you have all the control for the error that is going to be sent back to the client. I have extracted a few methods that check for different kinds of Exceptions and put different HTTP errors depending on these Exceptions.
I am using the cause of the exception because these exceptions are wrapped inside a ZuulException.

I am not able to alter Spring cloud gate way response in Global filter based on response headers from down stream?

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

Spring Soap Webservice exception handling

I am creating soap web service using spring. I am getting hibernate exception while trying to save the request.
I am trying to catch the hibernate exception in the catch block but control not even coming to the catch block
and soap service returning with the soap fault error message. Below are the list of classes which i am using,
Could any one please let me know how to handle the exception and rethrow the exception.
#WebService(serviceName = "submitService")
public class SubmitService extends AbstractWebServiceImpl {
#Autowired
private Validate validate;
#WebMethod
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public ResponseData submitRequest(RequestData request) {
ResponseData response = validate.submitRequest(request);
return response;
}
}
My Base class
public class AbstractWebServiceImpl extends SpringBeanAutowiringSupport {
#Resource
private WebServiceContext webServiceContext;
protected void handleWSException(Exception e) throws BusinessFault, InfrastructureFault {
if ( e instanceof BusinessException) {
ReturnMessage rm = ((BusinessException)e).getReturnMessage();
throw new BusinessFault(rm, e);
} else if (e instanceof BaseException) {
ReturnMessage rm = ((BaseException)e).getReturnMessage();
throw new InfrastructureFault(rm, e);
} else {
ReturnMessage rm = new ReturnMessage(ReturnCode.GENERIC_WEB_SERVICE_ERROR, e.toString());
throw new InfrastructureFault(rm, e);
}
}
public void setWebServiceContext(WebServiceContext webServiceContext) {
this.webServiceContext = webServiceContext;
}
public WebServiceContext getWebServiceContext() {
return webServiceContext;
}
}
My Business layer implementation class
#Component
public class ValidateImpl implements Validate {
#Autowired
private SomeHibernateDao dao;
#Override
#Transactional
public ResponseData submitRequest(RequestData request) {
ResponseData response = new ResponseData();
try {
dao.save(request);
} catch (Exception e) {
// Control never execute this block of code if dao layer throwing any exception.
// I want to catch the exception here modify the response and return to the client
e.printStackTrace();
response.setErrorDetails("More meaningful error message");
}
return response;
}
This code returning default soap error message back to client.
I want to catch the exception and modify the exception before returning to client. Please let me know what change i have to make so that i can handle the soap error message before i return the response back to client.
I am able to catch the exception in SubmitService itself but not sure why not able to catch the exception in ValidateImpl. however my issue is resolved now.

ResponseExceptionMapper in cxf client

I am trying to handle exceptions using the ResponseExceptionMapper class for my cxf client.
ExceptionHandlingCode:
public class MyServiceRestExceptionMapper implements ResponseExceptionMapper<Exception> {
private static final Logger LOGGER = LoggerFactory.getLogger(MyServiceRestExceptionMapper .class);
public MyServiceRestExceptionMapper () {
}
#Override
public Exception fromResponse(Response response) {
LOGGER.info("Executing MyServiceRestExceptionMapper class");
Response.Status status = Response.Status.fromStatusCode(response.getStatus());
LOGGER.info("Status: ", status.getStatusCode());
switch (status) {
case BAD_REQUEST:
throw new InvalidServiceRequestException(response.getHeaderString("exception"));
case UNAUTHORIZED:
throw new AuthorizationException(response.getHeaderString("exception"));
case FORBIDDEN:
throw new AuthorizationException(response.getHeaderString("exception"));
case NOT_FOUND:
throw new
EmptyResultDataAccessException(response.getHeaderString("exception"));
default:
throw new InvalidServiceRequestException(response.getHeaderString("exception"));
}
}
}
CXF Client Code:
String url1=
WebClient client = createWebClient(url1).path(/document);
client.headers(someHeaders);
Response response = client.post(byteArry);
For success scenarios, I am getting the correct response code of 200, but for failure scenarios, I never get a response code.
Also is there a better way of handling exceptions in cxf client.
Could someone please help on this.
How have you registered the ResponseExceptionMapper to the WebClient?
You need something like this
List<Object> providers = new ArrayList<Object>();
providers.add(new MyServiceRestExceptionMapper()
WebClient client = WebClient.create(url, providers);
I suggest to use a WebApplicationException insteadof Exception because default behaviour will raise this kind of exception if no ResponseExceptionMapper is registered. Also, return the exception, do not throw it. The exception mapper should looks like this.
public class MyServiceRestExceptionMapper implements ResponseExceptionMapper<WebApplicationException>
public MyServiceRestExceptionMapper () {
}
#Override
public WebApplicationException fromResponse(Response response) {
//Create your custom exception with status code
WebApplicationException ex = ...
return ex;
}
}

Categories