You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2020/05/01 01:58:27 UTC
[cxf] branch master updated: CXF-8271: The async JAX-RS client
never completes the response in case of an exception during the interceptor
chain processing. (#665)
This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/master by this push:
new 568b967 CXF-8271: The async JAX-RS client never completes the response in case of an exception during the interceptor chain processing. (#665)
568b967 is described below
commit 568b9674b079050cbafde5ce6448734720b49ce9
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Thu Apr 30 21:58:15 2020 -0400
CXF-8271: The async JAX-RS client never completes the response in case of an exception during the interceptor chain processing. (#665)
---
.../apache/cxf/jaxrs/client/AbstractClient.java | 1 +
.../client/spec/ClientResponseFilterTest.java | 88 ++++++++++++++++-
.../org/apache/cxf/systest/jaxrs/BookServer20.java | 53 +++++-----
.../systest/jaxrs/JAXRS20ClientServerBookTest.java | 91 ++++++++++++++++++
.../cxf/systest/jaxrs/JAXRSAsyncClientTest.java | 107 ++++++++++++++++++++-
5 files changed, 315 insertions(+), 25 deletions(-)
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
index c65945c..511dba4 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
@@ -962,6 +962,7 @@ public abstract class AbstractClient implements Client {
List<Interceptor<? extends Message>> i3 = cfg.getConduitSelector().getEndpoint().getInInterceptors();
PhaseInterceptorChain chain = new PhaseChainCache().get(pm.getInPhases(), i1, i2, i3);
chain.add(new ClientResponseFilterInterceptor());
+ chain.setFaultObserver(setupInFaultObserver(cfg));
return chain;
}
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterTest.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterTest.java
index 9056212..70f5017 100644
--- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterTest.java
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterTest.java
@@ -19,6 +19,8 @@
package org.apache.cxf.jaxrs.client.spec;
import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
import javax.annotation.Priority;
import javax.ws.rs.DELETE;
@@ -42,6 +44,9 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -107,8 +112,7 @@ public class ClientResponseFilterTest {
.target(ADDRESS)
.request()
.get()) {
- assertEquals(200, response.getStatus());
- assertEquals("hello rabbit", response.readEntity(String.class));
+ fail("Should raise ResponseProcessingException");
}
}
@@ -165,4 +169,84 @@ public class ClientResponseFilterTest {
fail("Should be handled by ResponseProcessingException block");
}
}
+
+ @Test
+ public void testAsyncClientResponseFilter() throws Exception {
+ try (Response response = ClientBuilder.newClient()
+ .register(AddHeaderClientResponseFilter.class)
+ .target(ADDRESS)
+ .request()
+ .async()
+ .get()
+ .get(10, TimeUnit.SECONDS)) {
+ assertEquals(200, response.getStatus());
+ assertEquals("true", response.getHeaderString("X-Done"));
+ }
+ }
+
+ @Test
+ public void testExceptionWhenMultipleAsyncClientResponseFilters() {
+ try (Response response = ClientBuilder.newClient()
+ .register(AddHeaderClientResponseFilter.class)
+ .register(FaultyClientResponseFilter.class)
+ .target(ADDRESS)
+ .request()
+ .async()
+ .put(null)
+ .get(10, TimeUnit.SECONDS)) {
+ fail("Should not be invoked");
+ } catch (ExecutionException ex) {
+ assertThat(ex.getCause(), is(instanceOf(ResponseProcessingException.class)));
+ } catch (Throwable ex) {
+ fail("Should be handled by ResponseProcessingException block");
+ }
+ }
+
+ @Test
+ public void testExceptionInAsyncClientResponseFilter() throws Exception {
+ try (Response response = ClientBuilder.newClient()
+ .register(FaultyClientResponseFilter.class)
+ .target(ADDRESS)
+ .request()
+ .async()
+ .get()
+ .get(10, TimeUnit.SECONDS)) {
+ fail("Should raise ResponseProcessingException");
+ } catch (ExecutionException ex) {
+ assertThat(ex.getCause(), is(instanceOf(ResponseProcessingException.class)));
+ } catch (Throwable ex) {
+ fail("Should be handled by ResponseProcessingException block");
+ }
+ }
+
+ @Test
+ public void testExceptionInAsyncClientResponseFilterWhenNotFound() throws Exception {
+ try (Response response = ClientBuilder.newClient()
+ .register(FaultyClientResponseFilter.class)
+ .target(ADDRESS)
+ .request()
+ .async()
+ .put(null)
+ .get(10, TimeUnit.SECONDS)) {
+ fail("Should not be invoked");
+ } catch (ExecutionException ex) {
+ assertThat(ex.getCause(), is(instanceOf(ResponseProcessingException.class)));
+ } catch (Throwable ex) {
+ fail("Should be handled by ResponseProcessingException block");
+ }
+ }
+
+ @Test
+ public void testAsyncClientResponseFilterWhenNotFound() throws Exception {
+ try (Response response = ClientBuilder.newClient()
+ .register(AddHeaderClientResponseFilter.class)
+ .target(ADDRESS)
+ .request()
+ .async()
+ .put(null)
+ .get(10, TimeUnit.SECONDS)) {
+ assertEquals(404, response.getStatus());
+ assertEquals("true", response.getHeaderString("X-Done"));
+ }
+ }
}
\ No newline at end of file
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookServer20.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookServer20.java
index ae87a19..bda7f19 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookServer20.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookServer20.java
@@ -293,31 +293,34 @@ public class BookServer20 extends AbstractBusTestServerBase {
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
- String ct = responseContext.getMediaType().toString();
- if (requestContext.getProperty("filterexception") != null) {
- if (!"text/plain".equals(ct)) {
- throw new RuntimeException();
+ if (responseContext.getMediaType() != null) {
+ String ct = responseContext.getMediaType().toString();
+ if (requestContext.getProperty("filterexception") != null) {
+ if (!"text/plain".equals(ct)) {
+ throw new RuntimeException();
+ }
+ responseContext.getHeaders().putSingle("FilterException",
+ requestContext.getProperty("filterexception"));
}
- responseContext.getHeaders().putSingle("FilterException",
- requestContext.getProperty("filterexception"));
- }
- Object entity = responseContext.getEntity();
- Type entityType = responseContext.getEntityType();
- if (entity instanceof GenericHandler && InjectionUtils.getActualType(entityType) == Book.class) {
- ct += ";charset=ISO-8859-1";
- if ("getGenericBook2".equals(rInfo.getResourceMethod().getName())) {
- Annotation[] anns = responseContext.getEntityAnnotations();
- if (anns.length == 4 && anns[3].annotationType() == Context.class) {
- responseContext.getHeaders().addFirst("Annotations", "OK");
+
+ Object entity = responseContext.getEntity();
+ Type entityType = responseContext.getEntityType();
+ if (entity instanceof GenericHandler && InjectionUtils.getActualType(entityType) == Book.class) {
+ ct += ";charset=ISO-8859-1";
+ if ("getGenericBook2".equals(rInfo.getResourceMethod().getName())) {
+ Annotation[] anns = responseContext.getEntityAnnotations();
+ if (anns.length == 4 && anns[3].annotationType() == Context.class) {
+ responseContext.getHeaders().addFirst("Annotations", "OK");
+ }
+ } else {
+ responseContext.setEntity(new Book("book", 124L));
}
} else {
- responseContext.setEntity(new Book("book", 124L));
+ ct += ";charset=";
}
- } else {
- ct += ";charset=";
+ responseContext.getHeaders().putSingle("Content-Type", ct);
+ responseContext.getHeaders().add("Response", "OK");
}
- responseContext.getHeaders().putSingle("Content-Type", ct);
- responseContext.getHeaders().add("Response", "OK");
}
}
@@ -333,8 +336,14 @@ public class BookServer20 extends AbstractBusTestServerBase {
&& "addBook2".equals(ri.getResourceMethod().getName())) {
return;
}
- if (!responseContext.getHeaders().containsKey("Response")) {
- throw new RuntimeException();
+
+ if (ri.getResourceMethod() != null
+ && "addBook2".equals(ri.getResourceMethod().getName())) {
+ return;
+ }
+
+ if (requestContext.getUriInfo().getPath().contains("/notFound")) {
+ return;
}
if ((!responseContext.getHeaders().containsKey("DynamicResponse")
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRS20ClientServerBookTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRS20ClientServerBookTest.java
index 845f196..ead9898 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRS20ClientServerBookTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRS20ClientServerBookTest.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import javax.annotation.Priority;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
@@ -41,6 +42,7 @@ import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.ResponseProcessingException;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
@@ -75,6 +77,8 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -854,6 +858,74 @@ public class JAXRS20ClientServerBookTest extends AbstractBusClientServerTestBase
assertEquals(entity, response.readEntity(String.class));
}
+ @Test
+ public void testClientResponseFilter() {
+ final String address = "http://localhost:" + PORT + "/bookstore/books/wildcard";
+ try (Response response = ClientBuilder.newClient()
+ .register(AddHeaderClientResponseFilter.class)
+ .target(address)
+ .request("text/plain")
+ .get()) {
+ assertEquals(200, response.getStatus());
+ assertEquals("true", response.getHeaderString("X-Done"));
+ }
+ }
+
+ @Test
+ public void testExceptionWhenMultipleClientResponseFilters() {
+ final String address = "http://localhost:" + PORT + "/bookstore/books/wildcard";
+ try (Response response = ClientBuilder.newClient()
+ .register(AddHeaderClientResponseFilter.class)
+ .register(FaultyClientResponseFilter.class)
+ .target(address)
+ .request("text/plain")
+ .put(null)) {
+ fail("Should not be invoked");
+ } catch (ResponseProcessingException ex) {
+ // Seems to be an issue here, CXF creates the response context only once
+ // for all client response filters, the changes performed upstream the chain
+ // are not visible to the downstream filters.
+ assertEquals(null, ex.getResponse().getHeaderString("X-Done"));
+ } catch (Throwable ex) {
+ fail("Should be handled by ResponseProcessingException block");
+ }
+ }
+
+ @Test(expected = ResponseProcessingException.class)
+ public void testExceptionInClientResponseFilter() {
+ final String address = "http://localhost:" + PORT + "/bookstore/books/wildcard";
+ try (Response response = ClientBuilder.newClient()
+ .register(FaultyClientResponseFilter.class)
+ .target(address)
+ .request("text/plain")
+ .get()) {
+ fail("Should raise ResponseProcessingException");
+ }
+ }
+
+ @Test(expected = ResponseProcessingException.class)
+ public void testExceptionInClientResponseFilterWhenNotFound() {
+ final String address = "http://localhost:" + PORT + "/bookstore/notFound";
+ try (Response response = ClientBuilder.newClient()
+ .register(FaultyClientResponseFilter.class)
+ .target(address)
+ .request("text/plain")
+ .put(null)) {
+ fail("Should not be invoked");
+ }
+ }
+
+ @Test
+ public void testNotFound() throws Exception {
+ final String address = "http://localhost:" + PORT + "/bookstore/notFound";
+ try (Response response = ClientBuilder.newClient()
+ .target(address)
+ .request("text/plain")
+ .put(null)) {
+ assertThat(response.getStatus(), equalTo(404));
+ }
+ }
+
private static class ReplaceBodyFilter implements ClientRequestFilter {
@Override
@@ -1017,4 +1089,23 @@ public class JAXRS20ClientServerBookTest extends AbstractBusClientServerTestBase
}
}
+
+
+ @Priority(2)
+ public static class AddHeaderClientResponseFilter implements ClientResponseFilter {
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext)
+ throws IOException {
+ responseContext.getHeaders().add("X-Done", "true");
+ }
+ }
+
+ @Priority(1)
+ public static class FaultyClientResponseFilter implements ClientResponseFilter {
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext)
+ throws IOException {
+ throw new IOException("Exception from client response filter");
+ }
+ }
}
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSAsyncClientTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSAsyncClientTest.java
index c1c4fa2..af0fe42 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSAsyncClientTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSAsyncClientTest.java
@@ -29,7 +29,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Priority;
import javax.ws.rs.Consumes;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.ProcessingException;
@@ -63,6 +65,10 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -391,6 +397,89 @@ public class JAXRSAsyncClientTest extends AbstractBusClientServerTestBase {
wc.close();
}
+ @Test
+ public void testClientResponseFilter() throws Exception {
+ final String address = "http://localhost:" + PORT + "/bookstore/books/wildcard";
+ try (Response response = ClientBuilder.newClient()
+ .register(AddHeaderClientResponseFilter.class)
+ .target(address)
+ .request("text/plain")
+ .async()
+ .get()
+ .get()) {
+ assertEquals(200, response.getStatus());
+ assertEquals("true", response.getHeaderString("X-Done"));
+ }
+ }
+
+ @Test
+ public void testExceptionWhenMultipleClientResponseFilters() {
+ final String address = "http://localhost:" + PORT + "/bookstore/books/wildcard";
+ try (Response response = ClientBuilder.newClient()
+ .register(AddHeaderClientResponseFilter.class)
+ .register(FaultyClientResponseFilter.class)
+ .target(address)
+ .request()
+ .async()
+ .put(null)
+ .get(10, TimeUnit.SECONDS)) {
+ fail("Should not be invoked");
+ } catch (ExecutionException ex) {
+ assertThat(ex.getCause(), is(instanceOf(ResponseProcessingException.class)));
+ } catch (Throwable ex) {
+ fail("Should be handled by ResponseProcessingException block");
+ }
+ }
+
+ @Test
+ public void testExceptionInClientResponseFilter() throws Exception {
+ final String address = "http://localhost:" + PORT + "/bookstore/books/wildcard";
+ try (Response response = ClientBuilder.newClient()
+ .register(FaultyClientResponseFilter.class)
+ .target(address)
+ .request("text/plain")
+ .async()
+ .get()
+ .get(10, TimeUnit.SECONDS)) {
+ fail("Should raise ResponseProcessingException");
+ } catch (ExecutionException ex) {
+ assertThat(ex.getCause(), is(instanceOf(ResponseProcessingException.class)));
+ } catch (Throwable ex) {
+ fail("Should be handled by ResponseProcessingException block");
+ }
+ }
+
+ @Test
+ public void testExceptionInClientResponseFilterWhenNotFound() throws Exception {
+ final String address = "http://localhost:" + PORT + "/bookstore/notFound";
+ try (Response response = ClientBuilder.newClient()
+ .register(FaultyClientResponseFilter.class)
+ .target(address)
+ .request("text/plain")
+ .async()
+ .put(null)
+ .get(10, TimeUnit.SECONDS)) {
+ fail("Should not be invoked");
+ } catch (ExecutionException ex) {
+ assertThat(ex.getCause(), is(instanceOf(ResponseProcessingException.class)));
+ } catch (Throwable ex) {
+ fail("Should be handled by ResponseProcessingException block");
+ }
+ }
+
+ @Test
+ public void testNotFound() throws Exception {
+ final String address = "http://localhost:" + PORT + "/bookstore/notFound";
+ try (Response response = ClientBuilder.newClient()
+ .target(address)
+ .request("text/plain")
+ .async()
+ .put(null)
+ .get(10, TimeUnit.SECONDS)) {
+ assertThat(response.getStatus(), equalTo(404));
+ }
+ }
+
private WebClient createWebClient(String address) {
return WebClient.create(address);
}
@@ -470,7 +559,23 @@ public class JAXRSAsyncClientTest extends AbstractBusClientServerTestBase {
public Response getResult() {
return (Response)result;
}
+ }
+
+ @Priority(2)
+ public static class AddHeaderClientResponseFilter implements ClientResponseFilter {
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext)
+ throws IOException {
+ responseContext.getHeaders().add("X-Done", "true");
+ }
+ }
-
+ @Priority(1)
+ public static class FaultyClientResponseFilter implements ClientResponseFilter {
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext)
+ throws IOException {
+ throw new IOException("Exception from client response filter");
+ }
}
}