diff --git a/build.gradle.kts b/build.gradle.kts index b3047685bef..94a8bc00f95 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -200,6 +200,7 @@ subprojects { add("api", platform(rootProject.libs.jacksonBom)) add("api", platform(rootProject.libs.jerseyBom)) add("api", platform(rootProject.libs.jettyBom)) + add("api", platform(rootProject.libs.jettyEe8Bom)) add("api", platform(rootProject.libs.kotlinBom)) add("api", platform(rootProject.libs.nettyBom)) add("api", platform(rootProject.libs.prometheusClientBom)) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2a4a69b53d1..4f083d7bced 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -116,19 +116,21 @@ jerseyBom = { module = "org.glassfish.jersey:jersey-bom", version = "3.1.10" } jetbrainsAnnotations = { module = "org.jetbrains:annotations", version = "26.0.2" } jettyAlpnServer = { module = "org.eclipse.jetty:jetty-alpn-server" } jettyAlpnServerJava = { module = "org.eclipse.jetty:jetty-alpn-java-server" } -jettyBom = { module = "org.eclipse.jetty:jetty-bom", version = "10.0.20" } +jettyBom = { module = "org.eclipse.jetty:jetty-bom", version = "12.1.0.alpha1" } +jettyEe8Bom = { module = "org.eclipse.jetty.ee8:jetty-ee8-bom", version = "12.1.0.alpha1" } +jettyEe8WebsocketServer = { module = "org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-server" } jettyHttp = { module = "org.eclipse.jetty:jetty-http" } -jettyHttp2 = { module = "org.eclipse.jetty.http2:http2-server" } +jettyHttp2 = { module = "org.eclipse.jetty.http2:jetty-http2-server" } jettyIo = { module = "org.eclipse.jetty:jetty-io" } jettyServer = { module = "org.eclipse.jetty:jetty-server" } -jettyServlet = { module = "org.eclipse.jetty:jetty-servlet" } +jettyServlet = { module = "org.eclipse.jetty.ee8:jetty-ee8-servlet" } jettyServletApi = { module = "org.eclipse.jetty.toolchain:jetty-servlet-api", version = "4.0.6" } -jettyServlets = { module = "org.eclipse.jetty:jetty-servlets" } +jettyServlets = { module = "org.eclipse.jetty.ee8:jetty-ee8-servlets" } jettyUds = { module = "org.eclipse.jetty:jetty-unixdomain-server" } -jettyUnixSocket = { module = "org.eclipse.jetty:jetty-unixsocket-server" } +jettyUnixSocket = { module = "org.eclipse.jetty:jetty-unixdomain-server" } jettyUtil = { module = "org.eclipse.jetty:jetty-util" } -jettyWebsocketApi = { module = "org.eclipse.jetty.websocket:websocket-jetty-api" } -jettyWebsocketServer = { module = "org.eclipse.jetty.websocket:websocket-jetty-server" } +jettyWebsocketApi = { module = "org.eclipse.jetty.websocket:jetty-websocket-jetty-api" } +jettyWebsocketServer = { module = "org.eclipse.jetty.websocket:jetty-websocket-jetty-server" } jnrUnixsocket = { module = "com.github.jnr:jnr-unixsocket", version = "0.38.23" } jooq = { module = "org.jooq:jooq", version = "3.18.25" } jsqlparser = { module = "com.github.jsqlparser:jsqlparser", version = "5.1" } diff --git a/misk/build.gradle.kts b/misk/build.gradle.kts index d23a9a600ea..adc8bc64e51 100644 --- a/misk/build.gradle.kts +++ b/misk/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { api(project(":misk-service")) implementation(libs.jCommander) implementation(libs.jettyAlpnServer) + implementation(libs.jettyEe8WebsocketServer) implementation(libs.jettyHttp) implementation(libs.jettyHttp2) implementation(libs.jettyIo) diff --git a/misk/src/main/kotlin/misk/web/jetty/JettyHealthService.kt b/misk/src/main/kotlin/misk/web/jetty/JettyHealthService.kt index 0b181e1ce82..91fa8625bfb 100644 --- a/misk/src/main/kotlin/misk/web/jetty/JettyHealthService.kt +++ b/misk/src/main/kotlin/misk/web/jetty/JettyHealthService.kt @@ -8,6 +8,9 @@ import misk.annotation.ExperimentalMiskApi import misk.web.WebConfig import mu.KLogger import okhttp3.HttpUrl +import org.eclipse.jetty.ee8.servlet.ServletContextHandler +import org.eclipse.jetty.ee8.servlet.ServletHolder +import org.eclipse.jetty.ee8.websocket.server.config.JettyWebSocketServletContainerInitializer import org.eclipse.jetty.http.UriCompliance import org.eclipse.jetty.io.ConnectionStatistics import org.eclipse.jetty.server.HttpConfiguration @@ -16,10 +19,7 @@ import org.eclipse.jetty.server.NetworkConnector import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.handler.StatisticsHandler -import org.eclipse.jetty.servlet.ServletContextHandler -import org.eclipse.jetty.servlet.ServletHolder import org.eclipse.jetty.util.thread.ExecutorThreadPool -import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer import wisp.logging.getLogger import java.util.concurrent.SynchronousQueue import java.util.concurrent.ThreadPoolExecutor @@ -144,7 +144,7 @@ internal class JettyHealthService @Inject internal constructor( JettyWebSocketServletContainerInitializer.configure(servletContextHandler, null) server.addManaged(servletContextHandler) - statisticsHandler.handler = servletContextHandler + statisticsHandler.handler = servletContextHandler.get() } private fun setupServer() { diff --git a/misk/src/main/kotlin/misk/web/jetty/JettyService.kt b/misk/src/main/kotlin/misk/web/jetty/JettyService.kt index 076e2a0a3c1..45865b11620 100644 --- a/misk/src/main/kotlin/misk/web/jetty/JettyService.kt +++ b/misk/src/main/kotlin/misk/web/jetty/JettyService.kt @@ -1,7 +1,6 @@ package misk.web.jetty import com.google.common.base.Stopwatch -import com.google.common.base.Strings import com.google.common.util.concurrent.AbstractIdleService import com.google.common.util.concurrent.ThreadFactoryBuilder import com.squareup.wire.internal.newMutableList @@ -17,6 +16,11 @@ import misk.web.jetty.JettyHealthService.Companion.jettyHealthServiceEnabled import misk.web.mediatype.MediaTypes import okhttp3.HttpUrl import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory +import org.eclipse.jetty.ee8.servlet.FilterHolder +import org.eclipse.jetty.ee8.servlet.ServletContextHandler +import org.eclipse.jetty.ee8.servlet.ServletHolder +import org.eclipse.jetty.ee8.servlets.CrossOriginFilter +import org.eclipse.jetty.ee8.websocket.server.config.JettyWebSocketServletContainerInitializer import org.eclipse.jetty.http.UriCompliance import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory @@ -31,19 +35,11 @@ import org.eclipse.jetty.server.SecureRequestCustomizer import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.SslConnectionFactory -import org.eclipse.jetty.server.handler.ContextHandler import org.eclipse.jetty.server.handler.StatisticsHandler import org.eclipse.jetty.server.handler.gzip.GzipHandler -import org.eclipse.jetty.servlet.FilterHolder -import org.eclipse.jetty.servlet.ServletContextHandler -import org.eclipse.jetty.servlet.ServletHolder -import org.eclipse.jetty.servlets.CrossOriginFilter import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector -import org.eclipse.jetty.unixsocket.server.UnixSocketConnector -import org.eclipse.jetty.util.JavaVersion import org.eclipse.jetty.util.ssl.SslContextFactory import org.eclipse.jetty.util.thread.ThreadPool -import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer import wisp.logging.getLogger import java.io.File import java.io.IOException @@ -56,7 +52,6 @@ import java.util.EnumSet import java.util.concurrent.SynchronousQueue import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit -import java.util.regex.Pattern import javax.servlet.DispatcherType @Singleton @@ -258,44 +253,29 @@ class JettyService @Inject internal constructor( udsConnFactories.add(HTTP2CServerConnectionFactory(httpConfig)) } - if (isJEP380Supported(socketConfig.path)) { - logger.info("Using UnixDomainServerConnector for ${socketConfig.path}") - val udsConnector = UnixDomainServerConnector( - server, - null /* executor */, - null /* scheduler */, - null /* buffer pool */, - webConfig.acceptors ?: -1, - webConfig.selectors ?: -1, - *udsConnFactories.toTypedArray() - ) - val socketFile = File(socketConfig.path) - udsConnector.unixDomainPath = socketFile.toPath() - udsConnector.addBean(connectionMetricsCollector.newConnectionListener("http", 0)) - udsConnector.name = "uds" - - // set file permissions after socket creation so sidecars (e.g. envoy, istio) have access - try { - udsConnector.start() - setFilePermissions(socketFile) - } catch (e: Exception) { - cleanAndThrow(udsConnector, e) - } - server.addConnector(udsConnector) - } else { - val udsConnector = UnixSocketConnector( - server, - null /* executor */, - null /* scheduler */, - null /* buffer pool */, - webConfig.selectors ?: -1, - *udsConnFactories.toTypedArray() - ) - udsConnector.setUnixSocket(socketConfig.path) - udsConnector.addBean(connectionMetricsCollector.newConnectionListener("http", 0)) - udsConnector.name = "uds" - server.addConnector(udsConnector) + logger.info("Using UnixDomainServerConnector for ${socketConfig.path}") + val udsConnector = UnixDomainServerConnector( + server, + null /* executor */, + null /* scheduler */, + null /* buffer pool */, + webConfig.acceptors ?: -1, + webConfig.selectors ?: -1, + *udsConnFactories.toTypedArray() + ) + val socketFile = File(socketConfig.path) + udsConnector.unixDomainPath = socketFile.toPath() + udsConnector.addBean(connectionMetricsCollector.newConnectionListener("http", 0)) + udsConnector.name = "uds" + + // set file permissions after socket creation so sidecars (e.g. envoy, istio) have access + try { + udsConnector.start() + setFilePermissions(socketFile) + } catch (e: Exception) { + cleanAndThrow(udsConnector, e) } + server.addConnector(udsConnector) } // TODO(mmihic): Force security handler? @@ -305,7 +285,7 @@ class JettyService @Inject internal constructor( JettyWebSocketServletContainerInitializer.configure(servletContextHandler, null) server.addManaged(servletContextHandler) - statisticsHandler.handler = servletContextHandler + statisticsHandler.handler = servletContextHandler.get() statisticsHandler.server = server // Kubernetes sends a SIG_TERM and gives us 30 seconds to stop gracefully. @@ -446,11 +426,11 @@ private val Server.httpsUrl: HttpUrl? } internal fun NetworkConnector.toHttpUrl(): HttpUrl { - val context = server.getChildHandlerByClass(ContextHandler::class.java) + val context = server.context val protocol = defaultConnectionFactory.protocol val scheme = if (protocol.startsWith("SSL-") || protocol == "SSL") "https" else "http" - val virtualHosts = context?.virtualHosts ?: arrayOf() + val virtualHosts = context?.virtualHosts ?: listOf() val explicitHost = if (virtualHosts.isEmpty()) host else virtualHosts[0] return HttpUrl.Builder() @@ -465,6 +445,7 @@ internal fun NetworkConnector.toHttpUrl(): HttpUrl { * the response stream before the request stream is completed. It also wants to send HTTP trailers. */ private fun HttpConfiguration.customizeForGrpc() { + @Suppress("DEPRECATION") isDelayDispatchUntilContent = false } @@ -480,19 +461,6 @@ private fun AbstractHTTP2ServerConnectionFactory.customize(webConfig: WebConfig) } } -/** - * JEP-380 is supported when running Java 16+ and the provided socket path is non-abstract. Abstract - * socket paths are identified by paths prefixed with an `@` symbol or a null byte. - */ -internal fun isJEP380Supported( - path: String, - javaVersion: Int = JavaVersion.VERSION.major -): Boolean { - return javaVersion >= 16 && - !Strings.isNullOrEmpty(path) && - !Pattern.compile("^@|\u0000").matcher(path).find() -} - private fun setFilePermissions(file: File) { try { Files.setPosixFilePermissions(file.toPath(), PosixFilePermissions.fromString("rw-rw-rw-")) diff --git a/misk/src/main/kotlin/misk/web/jetty/JettyServletUpstreamResponse.kt b/misk/src/main/kotlin/misk/web/jetty/JettyServletUpstreamResponse.kt index 7f445946986..a0e3499c8e5 100644 --- a/misk/src/main/kotlin/misk/web/jetty/JettyServletUpstreamResponse.kt +++ b/misk/src/main/kotlin/misk/web/jetty/JettyServletUpstreamResponse.kt @@ -4,8 +4,8 @@ import misk.web.ServletHttpCall import misk.web.actions.WebSocketListener import okhttp3.Headers import okhttp3.Headers.Companion.headersOf +import org.eclipse.jetty.ee8.nested.Response import org.eclipse.jetty.http.HttpFields -import org.eclipse.jetty.server.Response import java.util.function.Supplier internal class JettyServletUpstreamResponse( diff --git a/misk/src/main/kotlin/misk/web/jetty/JettyWebSocket.kt b/misk/src/main/kotlin/misk/web/jetty/JettyWebSocket.kt index d40ce203d57..118b495029b 100644 --- a/misk/src/main/kotlin/misk/web/jetty/JettyWebSocket.kt +++ b/misk/src/main/kotlin/misk/web/jetty/JettyWebSocket.kt @@ -10,12 +10,12 @@ import okhttp3.Headers import okio.ByteString import okio.ByteString.Companion.toByteString import okio.utf8Size -import org.eclipse.jetty.websocket.api.Session -import org.eclipse.jetty.websocket.api.WebSocketAdapter -import org.eclipse.jetty.websocket.api.WriteCallback -import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest -import org.eclipse.jetty.websocket.server.JettyServerUpgradeResponse -import org.eclipse.jetty.websocket.server.JettyWebSocketCreator +import org.eclipse.jetty.ee8.websocket.api.Session +import org.eclipse.jetty.ee8.websocket.api.WebSocketAdapter +import org.eclipse.jetty.ee8.websocket.api.WriteCallback +import org.eclipse.jetty.ee8.websocket.server.JettyServerUpgradeRequest +import org.eclipse.jetty.ee8.websocket.server.JettyServerUpgradeResponse +import org.eclipse.jetty.ee8.websocket.server.JettyWebSocketCreator import java.util.ArrayDeque private const val MAX_QUEUE_SIZE = 16 * 1024 * 1024 diff --git a/misk/src/main/kotlin/misk/web/jetty/WebActionsServlet.kt b/misk/src/main/kotlin/misk/web/jetty/WebActionsServlet.kt index 684bff9f31d..761c4ea1dac 100644 --- a/misk/src/main/kotlin/misk/web/jetty/WebActionsServlet.kt +++ b/misk/src/main/kotlin/misk/web/jetty/WebActionsServlet.kt @@ -20,17 +20,17 @@ import okio.BufferedSink import okio.buffer import okio.sink import okio.source +import org.eclipse.jetty.ee8.nested.Request +import org.eclipse.jetty.ee8.nested.Response +import org.eclipse.jetty.ee8.websocket.server.JettyServerUpgradeResponse +import org.eclipse.jetty.ee8.websocket.server.JettyWebSocketServlet +import org.eclipse.jetty.ee8.websocket.server.JettyWebSocketServletFactory import org.eclipse.jetty.http.HttpMethod -import org.eclipse.jetty.server.Request -import org.eclipse.jetty.server.Response import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector -import org.eclipse.jetty.unixsocket.server.UnixSocketConnector -import org.eclipse.jetty.websocket.server.JettyServerUpgradeResponse -import org.eclipse.jetty.websocket.server.JettyWebSocketServlet -import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory import wisp.logging.getLogger import java.net.HttpURLConnection +import java.net.InetSocketAddress import java.net.ProtocolException import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse @@ -128,12 +128,8 @@ internal class WebActionsServlet @Inject constructor( (this.connector as UnixDomainServerConnector).unixDomainPath.toString() ) - is UnixSocketConnector -> SocketAddress.Unix( - (this.connector as UnixSocketConnector).unixSocket - ) - is ServerConnector -> SocketAddress.Network( - this.endPoint.remoteAddress.address.hostAddress, + (this.remoteAddress as InetSocketAddress).hostString, (this.connector as ServerConnector).localPort ) diff --git a/misk/src/test/kotlin/misk/web/ssl/Http2ConnectivityTest.kt b/misk/src/test/kotlin/misk/web/ssl/Http2ConnectivityTest.kt index 1df052b3c81..b8da4b62fc6 100644 --- a/misk/src/test/kotlin/misk/web/ssl/Http2ConnectivityTest.kt +++ b/misk/src/test/kotlin/misk/web/ssl/Http2ConnectivityTest.kt @@ -3,6 +3,8 @@ package misk.web.ssl import ch.qos.logback.classic.Level import com.google.inject.Guice import com.google.inject.Provides +import jakarta.inject.Inject +import jakarta.inject.Singleton import misk.Action import misk.MiskDefault import misk.MiskTestingServiceModule @@ -48,8 +50,6 @@ import java.io.IOException import java.net.SocketTimeoutException import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.TimeUnit -import jakarta.inject.Inject -import jakarta.inject.Singleton import javax.servlet.http.HttpServletRequest import kotlin.test.assertFailsWith @@ -264,7 +264,7 @@ class Http2ConnectivityTest { @ResponseContentType(MediaTypes.TEXT_PLAIN_UTF8) fun disconnect(): Response { val request = actionScopedServletRequest.get() as org.eclipse.jetty.server.Request - request.httpChannel.abort(Exception("boom")) // Synthesize a connectivity failure. + request.fail(Exception("boom")) // Synthesize a connectivity failure. return Response(body = "") } @@ -278,7 +278,7 @@ class Http2ConnectivityTest { @ResponseContentType(MediaTypes.TEXT_PLAIN_UTF8) fun disconnect(): ResponseBody { val request = actionScopedServletRequest.get() as org.eclipse.jetty.server.Request - request.httpChannel.abort(Exception("boom")) // Synthesize a connectivity failure. + request.fail(Exception("boom")) // Synthesize a connectivity failure. return object : ResponseBody { override fun writeTo(sink: BufferedSink) {