All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.http4s.server.jetty.JettyBuilder.scala Maven / Gradle / Ivy

There is a newer version: 0.16.6a
Show newest version
package org.http4s
package server
package jetty

import java.util
import javax.servlet.{DispatcherType, Filter}

import com.codahale.metrics.{InstrumentedExecutorService, MetricRegistry}
import com.codahale.metrics.jetty9.InstrumentedQueuedThreadPool
import org.eclipse.jetty.util.ssl.SslContextFactory
import org.eclipse.jetty.util.thread.QueuedThreadPool
import org.http4s.server.SSLSupport.{StoreInfo, SSLBits}

import java.net.InetSocketAddress
import java.util.concurrent.ExecutorService
import javax.servlet.http.HttpServlet
import org.eclipse.jetty.server.ServerConnector
import org.http4s.servlet.{ServletIo, ServletContainer, Http4sServlet}

import scala.concurrent.duration._
import scalaz.concurrent.Task

import org.eclipse.jetty.util.component.AbstractLifeCycle.AbstractLifeCycleListener
import org.eclipse.jetty.util.component.LifeCycle
import org.eclipse.jetty.server.{Server => JServer, _}
import org.eclipse.jetty.servlet.{FilterHolder, ServletHolder, ServletContextHandler}

sealed class JettyBuilder private (
  socketAddress: InetSocketAddress,
  private val serviceExecutor: ExecutorService,
  private val idleTimeout: Duration,
  private val asyncTimeout: Duration,
  private val servletIo: ServletIo,
  sslBits: Option[SSLBits],
  mounts: Vector[Mount],
  metricRegistry: Option[MetricRegistry],
  metricPrefix: String
)
  extends ServerBuilder
  with ServletContainer
  with IdleTimeoutSupport
  with SSLSupport
  with MetricsSupport
{
  type Self = JettyBuilder

  private def copy(socketAddress: InetSocketAddress = socketAddress,
                   serviceExecutor: ExecutorService = serviceExecutor,
                   idleTimeout: Duration = idleTimeout,
                   asyncTimeout: Duration = asyncTimeout,
                   servletIo: ServletIo = servletIo,
                   sslBits: Option[SSLBits] = sslBits,
                   mounts: Vector[Mount] = mounts,
                   metricRegistry: Option[MetricRegistry] = metricRegistry,
                   metricPrefix: String = metricPrefix): JettyBuilder =
    new JettyBuilder(socketAddress, serviceExecutor, idleTimeout, asyncTimeout, servletIo, sslBits, mounts, metricRegistry, metricPrefix)

  override def withSSL(keyStore: StoreInfo, keyManagerPassword: String, protocol: String, trustStore: Option[StoreInfo], clientAuth: Boolean): Self = {
    copy(sslBits = Some(SSLBits(keyStore, keyManagerPassword, protocol, trustStore, clientAuth)))
  }

  override def bindSocketAddress(socketAddress: InetSocketAddress): JettyBuilder =
    copy(socketAddress = socketAddress)

  override def withServiceExecutor(serviceExecutor: ExecutorService): JettyBuilder =
    copy(serviceExecutor = serviceExecutor)

  override def mountServlet(servlet: HttpServlet, urlMapping: String, name: Option[String] = None): JettyBuilder =
    copy(mounts = mounts :+ Mount { (context, index, _) =>
      val servletName = name.getOrElse(s"servlet-$index")
      context.addServlet(new ServletHolder(servletName, servlet), urlMapping)
    })

  override def mountFilter(filter: Filter, urlMapping: String, name: Option[String], dispatches: util.EnumSet[DispatcherType]): JettyBuilder =
    copy(mounts = mounts :+ Mount { (context, index, _) =>
      val filterName = name.getOrElse(s"filter-$index")
      val filterHolder = new FilterHolder(filter)
      filterHolder.setName(filterName)
      context.addFilter(filterHolder, urlMapping, dispatches)
    })

  override def mountService(service: HttpService, prefix: String): JettyBuilder =
    copy(mounts = mounts :+ Mount { (context, index, builder) =>
      val servlet = new Http4sServlet(
        service = service,
        asyncTimeout = builder.asyncTimeout,
        servletIo = builder.servletIo,
        threadPool = builder.instrumentedServiceExecutor
      )
      val servletName = s"servlet-$index"
      val urlMapping = ServletContainer.prefixMapping(prefix)
      context.addServlet(new ServletHolder(servletName, servlet), urlMapping)
    })

  override def withIdleTimeout(idleTimeout: Duration): JettyBuilder =
    copy(idleTimeout = idleTimeout)

  override def withAsyncTimeout(asyncTimeout: Duration): JettyBuilder =
    copy(asyncTimeout = asyncTimeout)

  override def withServletIo(servletIo: ServletIo): Self =
    copy(servletIo = servletIo)

  /**
   * Collects metrics into the specified [[MetricRegistry]], including:
   * * Connections via [[InstrumentedConnectionFactory]]
   * * The Jetty thread pool via [[InstrumentedQueuedThreadPool]]
   * * HTTP responses via [[InstrumentedHandler]]
   *
   * @param metricRegistry The registry to collect metrics into..
   */
  override def withMetricRegistry(metricRegistry: MetricRegistry): Self =
    copy(metricRegistry = Some(metricRegistry))

  override def withMetricPrefix(metricPrefix: String): Self = copy(metricPrefix = metricPrefix)

  private def getConnector(jetty: JServer): ServerConnector = sslBits match {
    case Some(sslBits) =>
      // SSL Context Factory
      val sslContextFactory = new SslContextFactory()
      sslContextFactory.setKeyStorePath(sslBits.keyStore.path)
      sslContextFactory.setKeyStorePassword(sslBits.keyStore.password)
      sslContextFactory.setKeyManagerPassword(sslBits.keyManagerPassword)
      sslContextFactory.setNeedClientAuth(sslBits.clientAuth)
      sslContextFactory.setProtocol(sslBits.protocol)

      sslBits.trustStore.foreach { trustManagerBits =>
        sslContextFactory.setTrustStorePath(trustManagerBits.path)
        sslContextFactory.setTrustStorePassword(trustManagerBits.password)
      }

      // SSL HTTP Configuration
      val https_config = new HttpConfiguration()

      https_config.setSecureScheme("https")
      https_config.setSecurePort(socketAddress.getPort)
      https_config.addCustomizer(new SecureRequestCustomizer())

      val connectionFactory = instrumentConnectionFactory(new HttpConnectionFactory(https_config))
      new ServerConnector(jetty, new SslConnectionFactory(sslContextFactory,
        org.eclipse.jetty.http.HttpVersion.HTTP_1_1.asString()),
        connectionFactory)

    case None =>
      val connectionFactory = instrumentConnectionFactory(new HttpConnectionFactory)
      new ServerConnector(jetty, connectionFactory)
  }

  private def instrumentConnectionFactory(connectionFactory: ConnectionFactory) =
    metricRegistry.fold(connectionFactory) { reg =>
      val timer = reg.timer(MetricRegistry.name(metricPrefix, "connections"))
      new InstrumentedConnectionFactory(connectionFactory, timer)
    }

  private def instrumentedServiceExecutor = metricRegistry.fold(serviceExecutor) {
    new InstrumentedExecutorService(serviceExecutor, _, MetricRegistry.name(metricPrefix, "service-executor"))
  }

  def start: Task[Server] = Task.delay {
    val threadPool = metricRegistry.fold(new QueuedThreadPool)(new InstrumentedQueuedThreadPool(_))
    val jetty = new JServer(threadPool)

    val context = new ServletContextHandler()
    context.setContextPath("/")

    val handler = metricRegistry.fold[Handler](context) { reg =>
      val h = InstrumentedHandler(reg, Option(metricPrefix))
      h.setHandler(context)
      h
    }

    jetty.setHandler(handler)

    val connector = getConnector(jetty)

    connector.setHost(socketAddress.getHostString)
    connector.setPort(socketAddress.getPort)
    connector.setIdleTimeout(if (idleTimeout.isFinite()) idleTimeout.toMillis else -1)
    jetty.addConnector(connector)

    for ((mount, i) <- mounts.zipWithIndex)
      mount.f(context, i, this)

    jetty.start()

    new Server {
      override def shutdown: Task[Unit] =
        Task.delay {
          jetty.stop()
        }

      override def onShutdown(f: => Unit): this.type = {
        jetty.addLifeCycleListener { new AbstractLifeCycleListener {
          override def lifeCycleStopped(event: LifeCycle): Unit = f
        }}
        this
      }

      lazy val address: InetSocketAddress = {
        val host = socketAddress.getHostString
        val port = jetty.getConnectors()(0).asInstanceOf[ServerConnector].getLocalPort
        new InetSocketAddress(host, port)
      }
    }
  }
}

object JettyBuilder extends JettyBuilder(
  socketAddress = ServerBuilder.DefaultSocketAddress,
  serviceExecutor = ServerBuilder.DefaultServiceExecutor,
  idleTimeout = IdleTimeoutSupport.DefaultIdleTimeout,
  asyncTimeout = AsyncTimeoutSupport.DefaultAsyncTimeout,
  servletIo = ServletContainer.DefaultServletIo,
  sslBits = None,
  mounts = Vector.empty,
  metricRegistry = None,
  metricPrefix = MetricsSupport.DefaultPrefix
)

private case class Mount(f: (ServletContextHandler, Int, JettyBuilder) => Unit)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy