Repository: tipsy/javalin (kotlin)

Issue:
Separate WebSockets implementation from regular HTTP request handling
**General**
Currently, this fragment:

https://github.com/javalin/javalin/blob/25ec384648af2913b1dc9c1c36478325e19b99f3/javalin/src/main/java/io/javalin/jetty/JavalinJettyServlet.kt#L57-L60

Pulls several dependencies that could be optional for users, that don't even user WebSockets API in Javalin. It might be a good idea to finally separate those 2 handlers, so we can make these dependencies optional. 

**Goals**
- A clear distinction between HTTP request & websocket connection in sources
- Jetty's websocket related libraries can be treated as optional
- A step towards a standalone websocket module

Pull request description:
Fix #2015

Patch:
diff --git a/javalin/src/main/java/io/javalin/Javalin.java b/javalin/src/main/java/io/javalin/Javalin.java
index 6f968c287..5e2774e84 100644
--- a/javalin/src/main/java/io/javalin/Javalin.java
+++ b/javalin/src/main/java/io/javalin/Javalin.java
@@ -93,7 +93,7 @@ public static Javalin createAndStart(Consumer<JavalinConfig> config) {
 
     // Get JavalinServlet (can be attached to other servlet containers)
     public Servlet javalinServlet() {
-        return cfg.pvt.servlet.getValue();
+        return cfg.pvt.servlet.getValue().getServlet();
     }
 
     /**
diff --git a/javalin/src/main/java/io/javalin/config/PrivateConfig.kt b/javalin/src/main/java/io/javalin/config/PrivateConfig.kt
index 576333cfa..52e1ab963 100644
--- a/javalin/src/main/java/io/javalin/config/PrivateConfig.kt
+++ b/javalin/src/main/java/io/javalin/config/PrivateConfig.kt
@@ -7,15 +7,14 @@ import io.javalin.http.SinglePageHandler
 import io.javalin.router.InternalRouter
 import io.javalin.http.servlet.DefaultTasks
 import io.javalin.http.servlet.JavalinServlet
+import io.javalin.http.servlet.ServletEntry
 import io.javalin.router.exception.JavaLangErrorHandler
 import io.javalin.http.staticfiles.ResourceHandler
-import io.javalin.jetty.JavalinJettyServlet
+import io.javalin.jetty.JettyUtil.createJettyServletWithWebsocketsIfAvailable
 import io.javalin.plugin.PluginManager
-import io.javalin.util.Util
 import io.javalin.util.javalinLazy
 import io.javalin.websocket.WsConfig
 import io.javalin.websocket.WsRouter
-import jakarta.servlet.Servlet
 import org.eclipse.jetty.server.Server
 
 // @formatter:off
@@ -32,13 +31,7 @@ class PrivateConfig(val cfg: JavalinConfig) {
     @JvmField var wsLogger: WsConfig? = null
     @JvmField var compressionStrategy = CompressionStrategy.GZIP
     @JvmField var servletRequestLifecycle = listOf(DefaultTasks.BEFORE, DefaultTasks.BEFORE_MATCHED, DefaultTasks.HTTP, DefaultTasks.AFTER_MATCHED, DefaultTasks.ERROR, DefaultTasks.AFTER)
-    @JvmField var servlet: Lazy<Servlet> = javalinLazy {
-        val httpServlet = JavalinServlet(cfg)
-        when {
-            Util.classExists("org.eclipse.jetty.websocket.server.JettyWebSocketServlet") -> JavalinJettyServlet(cfg, httpServlet)
-            else -> httpServlet
-        }
-    }
+    @JvmField var servlet: Lazy<ServletEntry> = javalinLazy { createJettyServletWithWebsocketsIfAvailable(cfg) ?: ServletEntry(servlet = JavalinServlet(cfg)) }
     // Jetty
     @JvmField var server: Server? = null
 
diff --git a/javalin/src/main/java/io/javalin/http/servlet/ServletEntry.kt b/javalin/src/main/java/io/javalin/http/servlet/ServletEntry.kt
new file mode 100644
index 000000000..6b88808cb
--- /dev/null
+++ b/javalin/src/main/java/io/javalin/http/servlet/ServletEntry.kt
@@ -0,0 +1,9 @@
+package io.javalin.http.servlet
+
+import jakarta.servlet.Servlet
+import jakarta.servlet.ServletContainerInitializer
+
+data class ServletEntry(
+    val initializer: ServletContainerInitializer? = null,
+    val servlet: Servlet
+)
diff --git a/javalin/src/main/java/io/javalin/jetty/JavalinJettyServlet.kt b/javalin/src/main/java/io/javalin/jetty/JavalinJettyServlet.kt
index 4e1c8d67d..f8f7c53c7 100644
--- a/javalin/src/main/java/io/javalin/jetty/JavalinJettyServlet.kt
+++ b/javalin/src/main/java/io/javalin/jetty/JavalinJettyServlet.kt
@@ -27,10 +27,12 @@ internal const val upgradeSessionAttrsKey = "javalin-ws-upgrade-http-session"
 
 /**
  * The [JavalinJettyServlet] is responsible for both WebSocket and HTTP requests.
- * It extends Jetty's [WebSocketServlet], and has a [JavalinServlet] as a constructor arg.
+ * It extends Jetty's [JettyWebSocketServlet], and has a [JavalinServlet] as a constructor arg.
  * It switches between WebSocket and HTTP in the [service] method.
  */
-class JavalinJettyServlet(val cfg: JavalinConfig, private val httpServlet: JavalinServlet) : JettyWebSocketServlet() {
+class JavalinJettyServlet(val cfg: JavalinConfig) : JettyWebSocketServlet() {
+
+    private val httpServlet = JavalinServlet(cfg)
 
     private val servletContextConfig by lazy {
         JavalinServletContextConfig(
diff --git a/javalin/src/main/java/io/javalin/jetty/JettyServer.kt b/javalin/src/main/java/io/javalin/jetty/JettyServer.kt
index 7fa2757b2..62c38f86e 100644
--- a/javalin/src/main/java/io/javalin/jetty/JettyServer.kt
+++ b/javalin/src/main/java/io/javalin/jetty/JettyServer.kt
@@ -9,7 +9,6 @@ package io.javalin.jetty
 import io.javalin.config.JavalinConfig
 import io.javalin.event.JavalinLifecycleEvent
 import io.javalin.http.ContentType
-import io.javalin.http.servlet.JavalinServlet
 import io.javalin.util.ConcurrencyUtil
 import io.javalin.util.JavalinBindException
 import io.javalin.util.JavalinException
@@ -68,10 +67,11 @@ class JettyServer(private val cfg: JavalinConfig) {
         server().apply {
             cfg.jetty.serverConsumers.forEach{ it.accept(this) } // apply user config
             handler = handler.attachHandler(ServletContextHandler(SESSIONS).apply {
-                JettyWebSocketServletContainerInitializer.configure(this, null)
+                val (initializer, servlet) = cfg.pvt.servlet.value
+                if (initializer != null) this.addServletContainerInitializer(initializer)
                 contextPath = Util.normalizeContextPath(cfg.router.contextPath)
                 sessionHandler = defaultSessionHandler()
-                addServlet(ServletHolder(cfg.pvt.servlet.value), "/*")
+                addServlet(ServletHolder(servlet), "/*")
                 cfg.jetty.servletContextHandlerConsumers.forEach{ it.accept(this) } // apply user config
             })
             val httpConfiguration = defaultHttpConfiguration()
diff --git a/javalin/src/main/java/io/javalin/jetty/JettyUtil.kt b/javalin/src/main/java/io/javalin/jetty/JettyUtil.kt
new file mode 100644
index 000000000..1399a3b58
--- /dev/null
+++ b/javalin/src/main/java/io/javalin/jetty/JettyUtil.kt
@@ -0,0 +1,18 @@
+package io.javalin.jetty
+
+import io.javalin.config.JavalinConfig
+import io.javalin.http.servlet.ServletEntry
+import io.javalin.util.Util
+import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer
+
+internal object JettyUtil {
+
+    fun createJettyServletWithWebsocketsIfAvailable(cfg: JavalinConfig): ServletEntry? =
+        when {
+            Util.classExists("org.eclipse.jetty.websocket.server.JettyWebSocketServlet") ->
+                ServletEntry(JettyWebSocketServletContainerInitializer(null), JavalinJettyServlet(cfg))
+            else ->
+                null
+        }
+
+}
diff --git a/pom.xml b/pom.xml
index 8fb0f7f57..3d775dddc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -166,6 +166,11 @@
                 <artifactId>jetty-server</artifactId>
                 <version>${jetty.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-servlet</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.eclipse.jetty.http2</groupId>
                 <artifactId>http2-server</artifactId>


Test patch:
diff --git a/javalin-testtools/pom.xml b/javalin-testtools/pom.xml
index 2499aa72e..c3c47f805 100644
--- a/javalin-testtools/pom.xml
+++ b/javalin-testtools/pom.xml
@@ -53,8 +53,8 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.eclipse.jetty.websocket</groupId>
-            <artifactId>websocket-jetty-server</artifactId>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>