Skip to content

Commit 147f0fc

Browse files
committed
CXF-9192: Inconsistent best effort read of body by web-client when protocol is HTTP/2
1 parent 8750480 commit 147f0fc

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpClientHTTPConduit.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,7 @@ protected void updateResponseHeaders(Message inMessage) throws IOException {
11471147
}
11481148

11491149
@Override
1150+
@SuppressWarnings("checkstyle:NestedIfDepth")
11501151
protected InputStream getInputStream() throws IOException {
11511152
HttpResponse<InputStream> resp = getResponse();
11521153
String method = (String)outMessage.get(Message.HTTP_REQUEST_METHOD);
@@ -1172,9 +1173,24 @@ protected InputStream getInputStream() throws IOException {
11721173
}
11731174
} else if (!fChunk.isPresent() || !"chunked".equals(fChunk.get())) {
11741175
if (resp.version() == Version.HTTP_2) {
1175-
InputStream in = resp.body();
1176+
final InputStream in = resp.body();
1177+
// The InputStream::available is a best effort, if it returns 0, issuing
1178+
// the InputStream::read will either block if data is expected or return
1179+
// immediately
11761180
if (in.available() <= 0) {
1177-
try (in) {
1181+
final PushbackInputStream pbin = new PushbackInputStream(in);
1182+
try {
1183+
// HttpResponseInputStream will block if there is data to be read
1184+
final int c = pbin.read();
1185+
if (c != -1) {
1186+
pbin.unread((byte) c);
1187+
return new HttpClientFilteredInputStream(pbin);
1188+
}
1189+
} catch (final IOException ex) {
1190+
// ignore
1191+
}
1192+
1193+
try (pbin) {
11781194
return null;
11791195
}
11801196
}

systests/transports/src/test/java/org/apache/cxf/systest/http2_jetty/AbstractJettyClientServerHttp2Test.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import jakarta.ws.rs.core.Response;
2323
import org.apache.cxf.configuration.jsse.TLSClientParameters;
24+
import org.apache.cxf.jaxrs.client.ClientConfiguration;
2425
import org.apache.cxf.jaxrs.client.WebClient;
2526
import org.apache.cxf.systest.http2_jetty.Http2TestClient.ClientResponse;
2627
import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
@@ -108,6 +109,36 @@ public void testBookWithHttp() throws Exception {
108109
assertEquals("CXF in Action", resp.readEntity(String.class));
109110
}
110111
}
112+
113+
@Test
114+
public void testBookWithHttp2Redirect() throws Exception {
115+
final WebClient wc = WebClient
116+
.create(getAddress() + getContext() + "/web/bookstore/redirect")
117+
.accept("application/xml");
118+
119+
final ClientConfiguration config = WebClient.getConfig(wc);
120+
config.getRequestContext().put(HTTPConduit.FORCE_HTTP_VERSION, "2");
121+
config.getHttpConduit().getClient().setAutoRedirect(false);
122+
123+
if (isSecure()) {
124+
final HTTPConduit conduit = WebClient.getConfig(wc).getHttpConduit();
125+
TLSClientParameters params = conduit.getTlsClientParameters();
126+
127+
if (params == null) {
128+
params = new TLSClientParameters();
129+
conduit.setTlsClientParameters(params);
130+
}
131+
132+
// Create TrustManager instance which trusts all clients and servers
133+
params.setTrustManagers(InsecureTrustManager.getNoOpX509TrustManagers());
134+
params.setDisableCNCheck(true);
135+
}
136+
137+
try (Response resp = wc.get()) {
138+
assertThat(resp.getStatus(), equalTo(307));
139+
assertEquals("Error while getting books", resp.readEntity(String.class));
140+
}
141+
}
111142

112143
protected abstract String getAddress();
113144
protected abstract String getContext();

systests/transports/src/test/java/org/apache/cxf/systest/http2_jetty/BookStore.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@
2020
package org.apache.cxf.systest.http2_jetty;
2121

2222
import java.io.IOException;
23+
import java.io.OutputStream;
24+
import java.nio.charset.StandardCharsets;
2325
import java.util.concurrent.ExecutorService;
2426
import java.util.concurrent.Executors;
27+
import java.util.concurrent.TimeUnit;
28+
import java.util.concurrent.locks.LockSupport;
2529

2630
import jakarta.ws.rs.GET;
2731
import jakarta.ws.rs.Path;
2832
import jakarta.ws.rs.Produces;
33+
import jakarta.ws.rs.core.Context;
34+
import jakarta.ws.rs.core.Response;
35+
import jakarta.ws.rs.core.StreamingOutput;
36+
import jakarta.ws.rs.core.UriInfo;
2937
import org.apache.cxf.jaxrs.ext.StreamingResponse;
3038

3139
@Path("/web/bookstore")
@@ -39,6 +47,23 @@ public byte[] getBookName() {
3947
return "CXF in Action".getBytes();
4048
}
4149

50+
@GET
51+
@Path("/redirect")
52+
@Produces("application/xml")
53+
public Response redirect(@Context final UriInfo info) {
54+
return Response
55+
.temporaryRedirect(info.getBaseUriBuilder().path("/web/bookstore/bookstream").build())
56+
.entity(new StreamingOutput() {
57+
@Override
58+
public void write(OutputStream out) throws IOException {
59+
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
60+
out.write("Error while getting books".getBytes(StandardCharsets.UTF_8));
61+
out.flush();
62+
}
63+
})
64+
.build();
65+
}
66+
4267
@GET
4368
@Path("/bookstream")
4469
@Produces("application/xml")

0 commit comments

Comments
 (0)