Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to configure Netty http compression #4056

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.netty.channel.kqueue.{KQueue, KQueueEventLoopGroup, KQueueServerSocket
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.channel.{ChannelHandler, ChannelPipeline, EventLoopGroup, ServerChannel}
import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.codec.http.{HttpContentCompressor, HttpServerCodec}
import io.netty.handler.logging.LoggingHandler
import io.netty.handler.ssl.SslContext
import org.playframework.netty.http.HttpStreamsServerHandler
Expand Down Expand Up @@ -50,6 +50,13 @@ import scala.concurrent.duration._
*
* @param serverHeader
* If set, send this value in the 'Server' response header. If None, don't set the header.
*
* @param compressionEnabled
* If set to true, the server will compress responses.
*
* @param compressionContentSizeThreshold
* The minimum size of the response content that will be compressed. If the response content is smaller than this value, it will not be
* compressed.
*/
case class NettyConfig(
host: String,
Expand All @@ -68,7 +75,9 @@ case class NettyConfig(
initPipeline: NettyConfig => (ChannelPipeline, ChannelHandler) => Unit,
gracefulShutdownTimeout: Option[FiniteDuration],
idleTimeout: Option[FiniteDuration],
serverHeader: Option[String]
serverHeader: Option[String],
compressionEnabled: Boolean,
compressionContentSizeThreshold: Int
) {
def host(h: String): NettyConfig = copy(host = h)

Expand Down Expand Up @@ -109,6 +118,9 @@ case class NettyConfig(

def serverHeader(h: String): NettyConfig = copy(serverHeader = Some(h))

def withCompressionEnabled: NettyConfig = copy(compressionEnabled = true)
def compressionContentSizeThreshold(t: Int): NettyConfig = copy(compressionContentSizeThreshold = t)

def isSsl: Boolean = sslContext.isDefined
}

Expand All @@ -131,12 +143,15 @@ object NettyConfig {
eventLoopConfig = EventLoopConfig.auto,
socketConfig = NettySocketConfig.default,
initPipeline = cfg => defaultInitPipeline(cfg)(_, _),
serverHeader = Some(s"tapir/${buildinfo.BuildInfo.version}")
serverHeader = Some(s"tapir/${buildinfo.BuildInfo.version}"),
compressionEnabled = false,
compressionContentSizeThreshold = 0
)

def defaultInitPipeline(cfg: NettyConfig)(pipeline: ChannelPipeline, handler: ChannelHandler): Unit = {
cfg.sslContext.foreach(s => pipeline.addLast(s.newHandler(pipeline.channel().alloc())))
pipeline.addLast(ServerCodecHandlerName, new HttpServerCodec())
if (cfg.compressionEnabled) pipeline.addLast(new HttpContentCompressor(cfg.compressionContentSizeThreshold, Seq.empty: _*))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way it was failing for me in Runtime for netty 4.1.112 and Tapir 1.10.15

[info] 2024-11-23 21:07:49.260 WARN  io.netty.channel.ChannelInitializer - Failed to initialize a channel. Closing: [id: 0xf0ede386, L:/127.0.0.1:9000 - R:/127.0.0.1:59802]
[info] java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Lio.netty.handler.codec.compression.CompressionOptions; ([Ljava.lang.Object; is in module java.base of loader 'bootstrap'; [Lio.netty.handler.codec.compression.CompressionOptions; is in unnamed module of loader 'app')
[info] 	at server.HttpServer$.pipeline$1(HttpServer.scala:42)
[info] 	at server.HttpServer$.$anonfun$run$4(HttpServer.scala:50)
[info] 	at server.HttpServer$.$anonfun$run$4$adapted(HttpServer.scala:50)
[info] 	at sttp.tapir.server.netty.internal.NettyBootstrap$$anon$1.initChannel(NettyBootstrap.scala:26)
[info] 	at io.netty.channel.ChannelInitializer.initChannel(ChannelInitializer.java:129)
[info] 	at io.netty.channel.ChannelInitializer.handlerAdded(ChannelInitializer.java:112)
[info] 	at io.netty.channel.AbstractChannelHandlerContext.callHandlerAdded(AbstractChannelHandlerContext.java:1130)
[info] 	at io.netty.channel.DefaultChannelPipeline.callHandlerAdded0(DefaultChannelPipeline.java:608)
[info] 	at io.netty.channel.DefaultChannelPipeline.access$100(DefaultChannelPipeline.java:45)
[info] 	at io.netty.channel.DefaultChannelPipeline$PendingHandlerAddedTask.execute(DefaultChannelPipeline.java:1460)
[info] 	at io.netty.channel.DefaultChannelPipeline.callHandlerAddedForAllHandlers(DefaultChannelPipeline.java:1114)
[info] 	at io.netty.channel.DefaultChannelPipeline.invokeHandlerAddedIfNeeded(DefaultChannelPipeline.java:649)
[info] 	at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:513)
[info] 	at io.netty.channel.AbstractChannel$AbstractUnsafe.access$200(AbstractChannel.java:428)
[info] 	at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:485)
[info] 	at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
[info] 	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
[info] 	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:469)
[info] 	at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:408)
[info] 	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994)
[info] 	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
[info] 	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
[info] 	at java.base/java.lang.Thread.run(Thread.java:1575)

The following code works

          new HttpContentCompressor(config.compressionThreshold, List.empty[CompressionOptions]: _*)

pipeline.addLast(new HttpStreamsServerHandler())
pipeline.addLast(handler)
if (cfg.addLoggingHandler) pipeline.addLast(new LoggingHandler())
Expand Down
Loading