JettyServletAdapter.kt

  1. package com.hexagonkt.http.server.jetty

  2. import com.hexagonkt.core.Jvm
  3. import com.hexagonkt.core.fieldsMapOf
  4. import com.hexagonkt.http.model.HttpProtocol
  5. import com.hexagonkt.http.model.HttpProtocol.*
  6. import com.hexagonkt.http.server.HttpServer
  7. import com.hexagonkt.http.server.HttpServerFeature
  8. import com.hexagonkt.http.server.HttpServerFeature.ZIP
  9. import com.hexagonkt.http.server.HttpServerPort
  10. import com.hexagonkt.http.server.HttpServerSettings
  11. import com.hexagonkt.http.server.servlet.ServletFilter
  12. import jakarta.servlet.DispatcherType
  13. import jakarta.servlet.MultipartConfigElement
  14. import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory
  15. import org.eclipse.jetty.ee10.servlet.DefaultServlet
  16. import org.eclipse.jetty.ee10.servlet.ServletContextHandler
  17. import org.eclipse.jetty.ee10.servlet.ServletContextHandler.NO_SESSIONS
  18. import org.eclipse.jetty.ee10.servlet.ServletHolder
  19. import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory
  20. import org.eclipse.jetty.server.*
  21. import org.eclipse.jetty.server.handler.gzip.GzipHandler
  22. import org.eclipse.jetty.util.VirtualThreads
  23. import org.eclipse.jetty.util.VirtualThreads.getDefaultVirtualThreadsExecutor
  24. import org.eclipse.jetty.util.ssl.SslContextFactory
  25. import org.eclipse.jetty.util.thread.ExecutorThreadPool
  26. import org.eclipse.jetty.util.thread.QueuedThreadPool
  27. import org.eclipse.jetty.util.thread.ThreadPool
  28. import java.security.KeyStore
  29. import java.util.*
  30. import org.eclipse.jetty.server.Server as JettyServer

  31. /**
  32.  * Implements [HttpServerPort] using [JettyServer].
  33.  */
  34. class JettyServletAdapter(
  35.     private val maxThreads: Int = 200,
  36.     private val minThreads: Int = 8,

  37.     private val acceptors: Int = -1,
  38.     private val selectors: Int = -1,

  39.     private val sendDateHeader: Boolean = false,
  40.     private val sendServerVersion: Boolean = false,
  41.     private val sendXPoweredBy: Boolean = false,
  42.     private val useVirtualThreads: Boolean = false,
  43. ) : HttpServerPort {

  44.     private var jettyServer: JettyServer? = null

  45.     constructor() : this(
  46.         maxThreads = 200,
  47.         minThreads = 8,

  48.         acceptors = -1,
  49.         selectors = -1,

  50.         sendDateHeader = false,
  51.         sendServerVersion = false,
  52.         sendXPoweredBy = false,
  53.         useVirtualThreads = false,
  54.     )

  55.     init {
  56.         if (useVirtualThreads)
  57.             check(VirtualThreads.areSupported()) {
  58.                 val jvm = "JVM: ${Jvm.version}"
  59.                 "Virtual threads not supported or not enabled (--enable-preview) $jvm"
  60.             }
  61.     }

  62.     override fun runtimePort(): Int =
  63.         jettyConnector().localPort

  64.     override fun started() =
  65.         jettyServer?.isStarted ?: false

  66.     private fun jettyConnector(): ServerConnector =
  67.         jettyServer?.connectors?.get(0) as? ServerConnector ?: error("Jetty must have a connector")

  68.     private fun createThreadPool(): ThreadPool =
  69.         if (useVirtualThreads) {
  70.             val virtualThreadPool = getDefaultVirtualThreadsExecutor()
  71.             val threadPool = ExecutorThreadPool(maxThreads, minThreads)
  72.             threadPool.virtualThreadsExecutor = virtualThreadPool
  73.             threadPool
  74.         }
  75.         else {
  76.             QueuedThreadPool(maxThreads, minThreads)
  77.         }

  78.     override fun startUp(server: HttpServer) {
  79.         val settings = server.settings
  80.         val serverInstance = JettyServer(createThreadPool())
  81.         jettyServer = serverInstance

  82.         val pathHandler = server.handler.addPrefix(settings.contextPath)

  83.         val context = createServerContext(settings)
  84.         val filter = ServletFilter(pathHandler)
  85.         val filterBind = context.servletContext.addFilter("filters", filter)
  86.         val dispatcherTypes = EnumSet.allOf(DispatcherType::class.java)
  87.         filterBind.addMappingForUrlPatterns(dispatcherTypes, true, "/*")

  88.         val servletHolder = ServletHolder("default", DefaultServlet())
  89.         servletHolder.registration.setMultipartConfig(MultipartConfigElement("/tmp"))
  90.         context.addServlet(servletHolder, "/*")

  91.         val serverConnector =
  92.             if (settings.sslSettings != null) setupSsl(settings, serverInstance)
  93.             else ServerConnector(serverInstance, acceptors, selectors)

  94.         serverConnector.host = settings.bindAddress.hostName
  95.         serverConnector.port = settings.bindPort
  96.         serverConnector.connectionFactories
  97.             .filterIsInstance<HttpConnectionFactory>()
  98.             .map { it.httpConfiguration }
  99.             .map {
  100.                 it.sendDateHeader = sendDateHeader
  101.                 it.sendServerVersion = sendServerVersion
  102.                 it.sendXPoweredBy = sendXPoweredBy
  103.             }

  104.         serverInstance.connectors = arrayOf(serverConnector)
  105.         serverInstance.handler = context
  106.         serverInstance.stopAtShutdown = true
  107.         serverInstance.start()
  108.     }

  109.     override fun shutDown() {
  110.         jettyServer?.stop()
  111.     }

  112.     override fun supportedProtocols(): Set<HttpProtocol> =
  113.         setOf(HTTP, HTTPS, HTTP2)

  114.     override fun supportedFeatures(): Set<HttpServerFeature> =
  115.         setOf(ZIP)

  116.     override fun options(): Map<String, *> =
  117.         fieldsMapOf(
  118.             JettyServletAdapter::maxThreads to maxThreads,
  119.             JettyServletAdapter::minThreads to minThreads,
  120.             JettyServletAdapter::acceptors to acceptors,
  121.             JettyServletAdapter::selectors to selectors,
  122.             JettyServletAdapter::sendDateHeader to sendDateHeader,
  123.             JettyServletAdapter::sendServerVersion to sendServerVersion,
  124.             JettyServletAdapter::sendXPoweredBy to sendXPoweredBy,
  125.             JettyServletAdapter::useVirtualThreads to useVirtualThreads,
  126.         )

  127.     private fun createServerContext(settings: HttpServerSettings): ServletContextHandler {

  128.         val context = ServletContextHandler(NO_SESSIONS)

  129.         if (settings.zip)
  130.             context.insertHandler(GzipHandler())

  131.         return context
  132.     }

  133.     private fun setupSsl(
  134.         settings: HttpServerSettings, serverInstance: JettyServer): ServerConnector {

  135.         val httpConfiguration = HttpConfiguration()
  136.         httpConfiguration.secureScheme = "https"
  137.         httpConfiguration.securePort = settings.bindPort
  138.         httpConfiguration.addCustomizer(SecureRequestCustomizer())

  139.         val sslContextFactory = SslContextFactory.Server()
  140.         val sslSettings = settings.sslSettings ?: error("SSL settings cannot be 'null'")
  141.         sslContextFactory.needClientAuth = sslSettings.clientAuth

  142.         val keyStore = sslSettings.keyStore
  143.         if (keyStore != null) {
  144.             val keyStorePassword = sslSettings.keyStorePassword
  145.             val keyStoreStream = keyStore.openStream()
  146.             sslContextFactory.keyStore = KeyStore.getInstance("pkcs12")
  147.             sslContextFactory.keyStore.load(keyStoreStream, keyStorePassword.toCharArray())
  148.             sslContextFactory.keyStorePassword = keyStorePassword
  149.         }

  150.         val trustStore = sslSettings.trustStore
  151.         if (trustStore != null) {
  152.             val trustStorePassword = sslSettings.trustStorePassword
  153.             val trustStoreStream = trustStore.openStream()
  154.             sslContextFactory.trustStore = KeyStore.getInstance("pkcs12")
  155.             sslContextFactory.trustStore.load(trustStoreStream, trustStorePassword.toCharArray())
  156.             sslContextFactory.setTrustStorePassword(trustStorePassword)
  157.         }

  158.         return if (settings.protocol != HTTP2)
  159.             ServerConnector(
  160.                 serverInstance,
  161.                 SslConnectionFactory(sslContextFactory, "http/1.1"),
  162.                 HttpConnectionFactory(httpConfiguration)
  163.             )
  164.         else {
  165.             val alpn = ALPNServerConnectionFactory()
  166.             ServerConnector(
  167.                 serverInstance,
  168.                 SslConnectionFactory(sslContextFactory, alpn.protocol),
  169.                 alpn,
  170.                 HTTP2ServerConnectionFactory(httpConfiguration),
  171.                 HttpConnectionFactory(httpConfiguration)
  172.             )
  173.         }
  174.     }
  175. }