Attempt to stabilise web server tests that use h2c

Apache HttpClient 5.1 doesn't cope with Jetty 10 sending
SETTINGS_ENABLE_CONNECT_PROTOCOL in the settings frame. It also appears
to be unstable when using Undertow, resulting in a failure and
"UT005032: Listener not making progress on framed channel, closing
channel to prevent infinite loop" being logged on the server-side.

Local experimentation suggests that Jetty's HTTP/2 client is more
robust and that it does not trigger the problem with Undertow. It also
fixes the problem with SETTINGS_ENABLE_CONNECT_PROTOCOL when testing
against Jetty 10 so this commit updates the tests to use Jetty's client.

Closes gh-26040
This commit is contained in:
Andy Wilkinson 2021-04-13 16:15:41 +01:00
parent 17a13de855
commit 5873dddc1c
6 changed files with 38 additions and 79 deletions

View File

@ -98,8 +98,10 @@ dependencies {
testImplementation("mysql:mysql-connector-java") testImplementation("mysql:mysql-connector-java")
testImplementation("net.sourceforge.jtds:jtds") testImplementation("net.sourceforge.jtds:jtds")
testImplementation("org.apache.derby:derby") testImplementation("org.apache.derby:derby")
testImplementation("org.apache.httpcomponents:httpasyncclient")
testImplementation("org.awaitility:awaitility") testImplementation("org.awaitility:awaitility")
testImplementation("org.eclipse.jetty:jetty-client")
testImplementation("org.eclipse.jetty.http2:http2-client")
testImplementation("org.eclipse.jetty.http2:http2-http-client-transport")
testImplementation("org.firebirdsql.jdbc:jaybird-jdk18") testImplementation("org.firebirdsql.jdbc:jaybird-jdk18")
testImplementation("org.hsqldb:hsqldb") testImplementation("org.hsqldb:hsqldb")
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")

View File

@ -43,11 +43,4 @@ class Jetty10ReactiveWebServerFactoryTests extends JettyReactiveWebServerFactory
} }
@Test
@Override
@Disabled("https://github.com/eclipse/jetty.project/issues/6164")
protected void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed() {
}
} }

View File

@ -50,11 +50,4 @@ public class Jetty10ServletWebServerFactoryTests extends JettyServletWebServerFa
protected void jettyConfigurations() throws Exception { protected void jettyConfigurations() throws Exception {
} }
@Test
@Override
@Disabled("https://github.com/eclipse/jetty.project/issues/6164")
protected void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed() {
}
} }

View File

@ -35,11 +35,12 @@ import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@EnabledForJreRange(min = JRE.JAVA_11) @EnabledForJreRange(min = JRE.JAVA_11)
@ClassPathExclusions({ "jetty-*.jar", "tomcat-embed*.jar" }) @ClassPathExclusions({ "jetty-*.jar", "tomcat-embed*.jar", "http2-*.jar" })
@ClassPathOverrides({ "org.slf4j:slf4j-api:1.7.25", "org.eclipse.jetty:jetty-io:10.0.2", @ClassPathOverrides({ "org.slf4j:slf4j-api:1.7.25", "org.eclipse.jetty:jetty-client:10.0.2",
"org.eclipse.jetty:jetty-server:10.0.2", "org.eclipse.jetty:jetty-servlet:10.0.2", "org.eclipse.jetty:jetty-io:10.0.2", "org.eclipse.jetty:jetty-server:10.0.2",
"org.eclipse.jetty:jetty-util:10.0.2", "org.eclipse.jetty:jetty-webapp:10.0.2", "org.eclipse.jetty:jetty-servlet:10.0.2", "org.eclipse.jetty:jetty-util:10.0.2",
"org.eclipse.jetty.http2:http2-common:10.0.2", "org.eclipse.jetty.http2:http2-hpack:10.0.2", "org.eclipse.jetty:jetty-webapp:10.0.2", "org.eclipse.jetty.http2:http2-common:10.0.2",
"org.eclipse.jetty.http2:http2-hpack:10.0.2", "org.eclipse.jetty.http2:http2-http-client-transport:10.0.2",
"org.eclipse.jetty.http2:http2-server:10.0.2", "org.mortbay.jasper:apache-jsp:8.5.40" }) "org.eclipse.jetty.http2:http2-server:10.0.2", "org.mortbay.jasper:apache-jsp:8.5.40" })
@interface TestWithJetty10 { @interface TestWithJetty10 {

View File

@ -41,15 +41,12 @@ import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -459,35 +456,24 @@ public abstract class AbstractReactiveWebServerFactoryTests {
} }
@Test @Test
protected void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed() protected void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed() throws Exception {
throws InterruptedException, ExecutionException, IOException {
AbstractReactiveWebServerFactory factory = getFactory(); AbstractReactiveWebServerFactory factory = getFactory();
Http2 http2 = new Http2(); Http2 http2 = new Http2();
http2.setEnabled(true); http2.setEnabled(true);
factory.setHttp2(http2); factory.setHttp2(http2);
this.webServer = factory.getWebServer(new EchoHandler()); this.webServer = factory.getWebServer(new EchoHandler());
this.webServer.start(); this.webServer.start();
try (CloseableHttpAsyncClient http2Client = HttpAsyncClients.createHttp2Default()) { org.eclipse.jetty.client.HttpClient client = new org.eclipse.jetty.client.HttpClient(
http2Client.start(); new HttpClientTransportOverHTTP2(new HTTP2Client()));
SimpleHttpRequest request = SimpleHttpRequests.post("http://localhost:" + this.webServer.getPort()); client.start();
request.setBody("Hello World", ContentType.TEXT_PLAIN); try {
SimpleHttpResponse response = http2Client.execute(request, new FutureCallback<SimpleHttpResponse>() { ContentResponse response = client.POST("http://localhost:" + this.webServer.getPort())
.content(new StringContentProvider("Hello World"), "text/plain").send();
@Override assertThat(response.getStatus() == HttpStatus.OK.value());
public void failed(Exception ex) { assertThat(response.getContentAsString()).isEqualTo("Hello World");
} }
finally {
@Override client.stop();
public void completed(SimpleHttpResponse result) {
}
@Override
public void cancelled() {
}
}).get();
assertThat(response.getCode() == HttpStatus.OK.value());
assertThat(response.getBodyText()).isEqualTo("Hello World");
} }
} }

View File

@ -79,12 +79,6 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory; import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.InputStreamFactory; import org.apache.http.client.entity.InputStreamFactory;
@ -103,6 +97,9 @@ import org.apache.jasper.EmbeddedServletOptions;
import org.apache.jasper.servlet.JspServlet; import org.apache.jasper.servlet.JspServlet;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -1130,35 +1127,22 @@ public abstract class AbstractServletWebServerFactoryTests {
} }
@Test @Test
protected void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed() protected void whenHttp2IsEnabledAndSslIsDisabledThenH2cCanBeUsed() throws Exception {
throws InterruptedException, ExecutionException, IOException {
AbstractServletWebServerFactory factory = getFactory(); AbstractServletWebServerFactory factory = getFactory();
Http2 http2 = new Http2(); Http2 http2 = new Http2();
http2.setEnabled(true); http2.setEnabled(true);
factory.setHttp2(http2); factory.setHttp2(http2);
this.webServer = factory.getWebServer(exampleServletRegistration()); this.webServer = factory.getWebServer(exampleServletRegistration());
this.webServer.start(); this.webServer.start();
try (CloseableHttpAsyncClient http2Client = HttpAsyncClients.createHttp2Default()) { org.eclipse.jetty.client.HttpClient client = new org.eclipse.jetty.client.HttpClient(
http2Client.start(); new HttpClientTransportOverHTTP2(new HTTP2Client()));
SimpleHttpRequest request = SimpleHttpRequests client.start();
.get("http://localhost:" + this.webServer.getPort() + "/hello"); try {
SimpleHttpResponse response = http2Client.execute(request, new FutureCallback<SimpleHttpResponse>() { ContentResponse response = client.GET("http://localhost:" + this.webServer.getPort() + "/hello");
assertThat(response.getStatus() == HttpStatus.OK.value());
@Override }
public void failed(Exception ex) { finally {
} client.stop();
@Override
public void completed(SimpleHttpResponse result) {
}
@Override
public void cancelled() {
}
}).get();
assertThat(response.getCode()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getBodyText()).isEqualTo("Hello World");
} }
} }