Implement Eclipse Jetty core HTTP handler adapter

This provides an implementation of an HTTP Handler Adapter that is coded
directly to the Eclipse Jetty core API, bypassing any servlet
implementation.

This includes a Jetty implementation of the spring `WebSocketClient`
interface, `JettyWebSocketClient`, using an explicit dependency to the
jetty-websocket-api.

Closes gh-32097

Co-authored-by: Lachlan Roberts <lachlan@webtide.com>
Co-authored-by: Arjen Poutsma <arjen.poutsma@broadcom.com>
This commit is contained in:
gregw
2024-01-15 19:05:09 +11:00
committed by Simon Baslé
parent b7ec028149
commit 0a60c622cc
33 changed files with 1701 additions and 526 deletions

View File

@@ -126,6 +126,7 @@ public abstract class AbstractHttpHandlerIntegrationTests {
static Stream<Named<HttpServer>> httpServers() {
return Stream.of(
named("Jetty", new JettyHttpServer()),
named("Jetty Core", new JettyCoreHttpServer()),
named("Reactor Netty", new ReactorHttpServer()),
named("Tomcat", new TomcatHttpServer()),
named("Undertow", new UndertowHttpServer())

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.testfixture.http.server.reactive.bootstrap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.websocket.server.ServerWebSocketContainer;
import org.springframework.http.server.reactive.JettyCoreHttpHandlerAdapter;
/**
* @author Rossen Stoyanchev
* @author Sam Brannen
* @author Greg Wilkins
* @since 6.2
*/
public class JettyCoreHttpServer extends AbstractHttpServer {
protected Log logger = LogFactory.getLog(getClass().getName());
private ArrayByteBufferPool byteBufferPool;
private Server jettyServer;
@Override
protected void initServer() {
if (logger.isTraceEnabled())
this.byteBufferPool = new ArrayByteBufferPool.Tracking();
this.jettyServer = new Server(null, null, byteBufferPool);
ServerConnector connector = new ServerConnector(this.jettyServer);
connector.setHost(getHost());
connector.setPort(getPort());
this.jettyServer.addConnector(connector);
this.jettyServer.setHandler(createHandlerAdapter());
ServerWebSocketContainer.ensure(jettyServer);
}
private JettyCoreHttpHandlerAdapter createHandlerAdapter() {
return new JettyCoreHttpHandlerAdapter(resolveHttpHandler());
}
@Override
protected void startInternal() throws Exception {
this.jettyServer.start();
setPort(((ServerConnector) this.jettyServer.getConnectors()[0]).getLocalPort());
}
@Override
protected void stopInternal() {
boolean wasRunning = this.jettyServer.isRunning();
try {
this.jettyServer.stop();
}
catch (Exception ex) {
// ignore
}
// TODO remove this or make debug only
if (wasRunning && this.byteBufferPool instanceof ArrayByteBufferPool.Tracking tracking) {
if (!tracking.getLeaks().isEmpty()) {
System.err.println("Leaks:\n" + tracking.dumpLeaks());
throw new IllegalStateException("LEAKS");
}
}
}
@Override
protected void resetInternal() {
try {
if (this.jettyServer.isRunning()) {
stopInternal();
}
this.jettyServer.destroy();
}
finally {
this.jettyServer = null;
}
}
}

View File

@@ -54,7 +54,6 @@ public class JettyHttpServer extends AbstractHttpServer {
connector.setPort(getPort());
this.jettyServer.addConnector(connector);
this.jettyServer.setHandler(this.contextHandler);
this.contextHandler.start();
}
private ServletHttpHandlerAdapter createServletAdapter() {
@@ -70,24 +69,10 @@ public class JettyHttpServer extends AbstractHttpServer {
@Override
protected void stopInternal() throws Exception {
try {
if (this.contextHandler.isRunning()) {
this.contextHandler.stop();
}
this.jettyServer.stop();
}
finally {
try {
if (this.jettyServer.isRunning()) {
// Do not configure a large stop timeout. For example, setting a stop timeout
// of 5000 adds an additional 1-2 seconds to the runtime of each test using
// the Jetty sever, resulting in 2-4 extra minutes of overall build time.
this.jettyServer.setStopTimeout(100);
this.jettyServer.stop();
this.jettyServer.destroy();
}
}
catch (Exception ex) {
// ignore
}
catch (Exception ex) {
// ignore
}
}
@@ -95,18 +80,14 @@ public class JettyHttpServer extends AbstractHttpServer {
protected void resetInternal() {
try {
if (this.jettyServer.isRunning()) {
// Do not configure a large stop timeout. For example, setting a stop timeout
// of 5000 adds an additional 1-2 seconds to the runtime of each test using
// the Jetty sever, resulting in 2-4 extra minutes of overall build time.
this.jettyServer.setStopTimeout(100);
this.jettyServer.stop();
this.jettyServer.destroy();
}
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
finally {
this.jettyServer.destroy();
this.jettyServer = null;
this.contextHandler = null;
}