zhttp.service.Server.scala Maven / Gradle / Ivy
package zhttp.service
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelPipeline
import io.netty.util.ResourceLeakDetector
import zhttp.http.Http._
import zhttp.http.{Http, HttpApp}
import zhttp.service.server.ServerSSLHandler._
import zhttp.service.server._
import zio._
import java.net.{InetAddress, InetSocketAddress}
sealed trait Server[-R, +E] { self =>
import Server._
def ++[R1 <: R, E1 >: E](other: Server[R1, E1]): Server[R1, E1] =
Concat(self, other)
private def settings[R1 <: R, E1 >: E](s: Config[R1, E1] = Config()): Config[R1, E1] = self match {
case Concat(self, other) => other.settings(self.settings(s))
case LeakDetection(level) => s.copy(leakDetectionLevel = level)
case Error(errorHandler) => s.copy(error = Some(errorHandler))
case Ssl(sslOption) => s.copy(sslOption = sslOption)
case App(app) => s.copy(app = app)
case Address(address) => s.copy(address = address)
case AcceptContinue(enabled) => s.copy(acceptContinue = enabled)
case KeepAlive(enabled) => s.copy(keepAlive = enabled)
case FlowControl(enabled) => s.copy(flowControl = enabled)
case ConsolidateFlush(enabled) => s.copy(consolidateFlush = enabled)
case UnsafeChannelPipeline(init) => s.copy(channelInitializer = init)
case RequestDecompression(enabled, strict) => s.copy(requestDecompression = (enabled, strict))
case ObjectAggregator(maxRequestSize) => s.copy(objectAggregator = maxRequestSize)
}
def make(implicit
ev: E <:< Throwable,
): ZIO[R with EventLoopGroup with ServerChannelFactory with Scope, Throwable, Start] =
Server.make(self.asInstanceOf[Server[R, Throwable]])
def start(implicit ev: E <:< Throwable): ZIO[R with EventLoopGroup with ServerChannelFactory, Throwable, Nothing] =
ZIO.scoped[R with EventLoopGroup with ServerChannelFactory](make *> ZIO.never)
/**
* Launches the app with current settings: default EventLoopGroup (nThreads =
* 0) and ServerChannelFactory.auto.
*/
def startDefault[R1 <: R](implicit ev: E <:< Throwable): ZIO[R1, Throwable, Nothing] =
start.provideSomeLayer[R1](EventLoopGroup.auto(0) ++ ServerChannelFactory.auto)
/**
* Creates a new server listening on the provided port.
*/
def withPort(port: Int): Server[R, E] = Concat(self, Server.Address(new InetSocketAddress(port)))
/**
* Creates a new server listening on the provided hostname and port.
*/
def withBinding(hostname: String, port: Int): Server[R, E] =
Concat(self, Server.Address(new InetSocketAddress(hostname, port)))
/**
* Creates a new server listening on the provided InetAddress and port.
*/
def withBinding(address: InetAddress, port: Int): Server[R, E] =
Concat(self, Server.Address(new InetSocketAddress(address, port)))
/**
* Creates a new server listening on the provided InetSocketAddress.
*/
def withBinding(inetSocketAddress: InetSocketAddress): Server[R, E] = Concat(self, Server.Address(inetSocketAddress))
/**
* Creates a new server with the errorHandler provided.
*/
def withError[R1](errorHandler: Throwable => ZIO[R1, Nothing, Unit]): Server[R with R1, E] =
Concat(self, Server.Error(errorHandler))
/**
* Creates a new server with the following ssl options.
*/
def withSsl(sslOptions: ServerSSLOptions): Server[R, E] = Concat(self, Server.Ssl(sslOptions))
/**
* Creates a new server using a HttpServerExpectContinueHandler to send a 100
* HttpResponse if necessary.
*/
def withAcceptContinue(enable: Boolean): Server[R, E] = Concat(self, Server.AcceptContinue(enable))
/**
* Creates a new server using netty FlowControlHandler if enable (@see FlowControlHandler).
*/
def withFlowControl(enable: Boolean): Server[R, E] = Concat(self, Server.FlowControl(enable))
/**
* Creates a new server with the leak detection level provided (@see ResourceLeakDetector.Level).
*/
def withLeakDetection(level: LeakDetectionLevel): Server[R, E] = Concat(self, LeakDetection(level))
/**
* Creates a new server with netty's HttpServerKeepAliveHandler to close
* persistent connections when enable is true (@see HttpServerKeepAliveHandler).
*/
def withKeepAlive(enable: Boolean): Server[R, E] = Concat(self, KeepAlive(enable))
/**
* Creates a new server with FlushConsolidationHandler to control the flush
* operations in a more efficient way if enabled (@see FlushConsolidationHandler).
*/
def withConsolidateFlush(enable: Boolean): Server[R, E] = Concat(self, ConsolidateFlush(enable))
/**
* Creates a new server by passing a function that modifies the channel
* pipeline. This is generally not required as most of the features are
* directly supported, however think of this as an escape hatch for more
* advanced configurations that are not yet support by ZIO Http.
*
* NOTE: This method might be dropped in the future.
*/
def withUnsafeChannelPipeline(unsafePipeline: ChannelPipeline => Unit): Server[R, E] =
Concat(self, UnsafeChannelPipeline(unsafePipeline))
/**
* Creates a new server with netty's HttpContentDecompressor to decompress
* Http requests (@see HttpContentDecompressor).
*/
def withRequestDecompression(enabled: Boolean, strict: Boolean): Server[R, E] =
Concat(self, RequestDecompression(enabled, strict))
/**
* Creates a new server with HttpObjectAggregator with the specified max size
* of the aggregated content.
*/
def withObjectAggregator(maxRequestSize: Int = Int.MaxValue): Server[R, E] =
Concat(self, ObjectAggregator(maxRequestSize))
}
object Server {
private[zhttp] final case class Config[-R, +E](
leakDetectionLevel: LeakDetectionLevel = LeakDetectionLevel.SIMPLE,
error: Option[Throwable => ZIO[R, Nothing, Unit]] = None,
sslOption: ServerSSLOptions = null,
// TODO: move app out of settings
app: HttpApp[R, E] = Http.empty,
address: InetSocketAddress = new InetSocketAddress(8080),
acceptContinue: Boolean = false,
keepAlive: Boolean = true,
consolidateFlush: Boolean = false,
flowControl: Boolean = true,
channelInitializer: ChannelPipeline => Unit = null,
requestDecompression: (Boolean, Boolean) = (false, false),
objectAggregator: Int = -1,
) {
def useAggregator: Boolean = objectAggregator >= 0
}
/**
* Holds server start information.
*/
final case class Start(port: Int = 0)
private final case class Concat[R, E](self: Server[R, E], other: Server[R, E]) extends Server[R, E]
private final case class LeakDetection(level: LeakDetectionLevel) extends UServer
private final case class Error[R](errorHandler: Throwable => ZIO[R, Nothing, Unit]) extends Server[R, Nothing]
private final case class Ssl(sslOptions: ServerSSLOptions) extends UServer
private final case class Address(address: InetSocketAddress) extends UServer
private final case class App[R, E](app: HttpApp[R, E]) extends Server[R, E]
private final case class KeepAlive(enabled: Boolean) extends Server[Any, Nothing]
private final case class ConsolidateFlush(enabled: Boolean) extends Server[Any, Nothing]
private final case class AcceptContinue(enabled: Boolean) extends UServer
private final case class FlowControl(enabled: Boolean) extends UServer
private final case class UnsafeChannelPipeline(init: ChannelPipeline => Unit) extends UServer
private final case class RequestDecompression(enabled: Boolean, strict: Boolean) extends UServer
private final case class ObjectAggregator(maxRequestSize: Int) extends UServer
def app[R, E](http: HttpApp[R, E]): Server[R, E] = Server.App(http)
def port(port: Int): UServer = Server.Address(new InetSocketAddress(port))
def bind(port: Int): UServer = Server.Address(new InetSocketAddress(port))
def bind(hostname: String, port: Int): UServer = Server.Address(new InetSocketAddress(hostname, port))
def bind(inetAddress: InetAddress, port: Int): UServer = Server.Address(new InetSocketAddress(inetAddress, port))
def bind(inetSocketAddress: InetSocketAddress): UServer = Server.Address(inetSocketAddress)
def error[R](errorHandler: Throwable => ZIO[R, Nothing, Unit]): Server[R, Nothing] = Server.Error(errorHandler)
def ssl(sslOptions: ServerSSLOptions): UServer = Server.Ssl(sslOptions)
def acceptContinue: UServer = Server.AcceptContinue(true)
def requestDecompression(strict: Boolean): UServer = Server.RequestDecompression(enabled = true, strict = strict)
val disableFlowControl: UServer = Server.FlowControl(false)
val disableLeakDetection: UServer = LeakDetection(LeakDetectionLevel.DISABLED)
val simpleLeakDetection: UServer = LeakDetection(LeakDetectionLevel.SIMPLE)
val advancedLeakDetection: UServer = LeakDetection(LeakDetectionLevel.ADVANCED)
val paranoidLeakDetection: UServer = LeakDetection(LeakDetectionLevel.PARANOID)
val disableKeepAlive: UServer = Server.KeepAlive(false)
val consolidateFlush: UServer = ConsolidateFlush(true)
def unsafePipeline(pipeline: ChannelPipeline => Unit): UServer = UnsafeChannelPipeline(pipeline)
def enableObjectAggregator(maxRequestSize: Int = Int.MaxValue): UServer = ObjectAggregator(maxRequestSize)
/**
* Creates a server from a http app.
*/
def apply[R, E](http: HttpApp[R, E]): Server[R, E] = Server.App(http)
/**
* Launches the app on the provided port.
*/
def start[R](
port: Int,
http: HttpApp[R, Throwable],
): ZIO[R, Throwable, Nothing] =
Server(http)
.withPort(port)
.make
.flatMap(start => ZIO.succeed(println(s"Server started on port: ${start.port}")) *> ZIO.never)
.provideSomeLayer[R](EventLoopGroup.auto(0) ++ ServerChannelFactory.auto ++ Scope.default)
def start[R](
address: InetAddress,
port: Int,
http: HttpApp[R, Throwable],
): ZIO[R, Throwable, Nothing] =
(Server(http).withBinding(address, port).make *> ZIO.never)
.provideSomeLayer[R](EventLoopGroup.auto(0) ++ ServerChannelFactory.auto ++ Scope.default)
def start[R](
socketAddress: InetSocketAddress,
http: HttpApp[R, Throwable],
): ZIO[R, Throwable, Nothing] =
(Server(http).withBinding(socketAddress).make *> ZIO.never)
.provideSomeLayer[R](EventLoopGroup.auto(0) ++ ServerChannelFactory.auto ++ Scope.default)
def make[R](
server: Server[R, Throwable],
): ZIO[R with EventLoopGroup with ServerChannelFactory with Scope, Throwable, Start] = {
val settings = server.settings()
for {
channelFactory <- ZIO.service[ServerChannelFactory]
eventLoopGroup <- ZIO.service[EventLoopGroup]
zExec <- HttpRuntime.sticky[R](eventLoopGroup)
reqHandler = settings.app.compile(zExec, settings, ServerTime.make)
init = ServerChannelInitializer(zExec, settings, reqHandler)
serverBootstrap = new ServerBootstrap().channelFactory(channelFactory).group(eventLoopGroup)
chf <- ZIO.attempt(serverBootstrap.childHandler(init).bind(settings.address))
_ <- ChannelFuture.asZIO(chf)
port <- ZIO.attempt(chf.channel().localAddress().asInstanceOf[InetSocketAddress].getPort)
} yield {
ResourceLeakDetector.setLevel(settings.leakDetectionLevel.jResourceLeakDetectionLevel)
Start(port)
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy