Skip to content

Commit

Permalink
Add brotli compression (#2646)
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil committed May 20, 2024
1 parent ade9245 commit 9d0713e
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 57 deletions.
1 change: 1 addition & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ object Dependencies {
"io.netty" % "netty-transport-native-kqueue" % NettyVersion,
"io.netty" % "netty-transport-native-kqueue" % NettyVersion % Runtime classifier "osx-x86_64",
"io.netty" % "netty-transport-native-kqueue" % NettyVersion % Runtime classifier "osx-aarch_64",
"com.aayushatharva.brotli4j" % "brotli4j" % "1.16.0" % "provided",
)

val `netty-incubator` =
Expand Down
29 changes: 20 additions & 9 deletions zio-http/jvm/src/main/scala/zio/http/netty/model/Conversions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ package zio.http.netty.model

import scala.collection.AbstractIterator

import zio.stacktracer.TracingImplicits.disableAutoTrace

import zio.http.Server.Config.CompressionOptions
import zio.http._

import io.netty.handler.codec.compression.{DeflateOptions, StandardCompressionOptions}
import com.aayushatharva.brotli4j.encoder.Encoder
import io.netty.handler.codec.compression.StandardCompressionOptions
import io.netty.handler.codec.http._
import io.netty.handler.codec.http.websocketx.WebSocketScheme

Expand Down Expand Up @@ -132,14 +131,26 @@ private[netty] object Conversions {
case _ => None
}

def compressionOptionsToNetty(compressionOptions: CompressionOptions): DeflateOptions =
compressionOptions.kind match {
case CompressionOptions.CompressionType.GZip =>
StandardCompressionOptions.gzip(compressionOptions.level, compressionOptions.bits, compressionOptions.mem)
case CompressionOptions.CompressionType.Deflate =>
StandardCompressionOptions.deflate(compressionOptions.level, compressionOptions.bits, compressionOptions.mem)
def compressionOptionsToNetty(
compressionOptions: CompressionOptions,
): io.netty.handler.codec.compression.CompressionOptions =
compressionOptions match {
case CompressionOptions.GZip(cfg) =>
StandardCompressionOptions.gzip(cfg.level, cfg.bits, cfg.mem)
case CompressionOptions.Deflate(cfg) =>
StandardCompressionOptions.deflate(cfg.level, cfg.bits, cfg.mem)
case CompressionOptions.Brotli(cfg) =>
StandardCompressionOptions.brotli(
new Encoder.Parameters().setQuality(cfg.quality).setWindow(cfg.lgwin).setMode(brotliModeToJava(cfg.mode)),
)
}

def brotliModeToJava(brotli: CompressionOptions.Mode): Encoder.Mode = brotli match {
case CompressionOptions.Mode.Font => Encoder.Mode.FONT
case CompressionOptions.Mode.Text => Encoder.Mode.TEXT
case CompressionOptions.Mode.Generic => Encoder.Mode.GENERIC
}

def versionToNetty(version: Version): HttpVersion = version match {
case Version.Http_1_0 => HttpVersion.HTTP_1_0
case Version.Http_1_1 => HttpVersion.HTTP_1_1
Expand Down
142 changes: 94 additions & 48 deletions zio-http/shared/src/main/scala/zio/http/Server.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import java.net.{InetAddress, InetSocketAddress}
import java.util.concurrent.atomic._

import zio._
import zio.stacktracer.TracingImplicits.disableAutoTrace

import zio.http.Server.Config.ResponseCompressionConfig

Expand Down Expand Up @@ -248,69 +247,116 @@ object Server extends ServerPlatformSpecific {
ResponseCompressionConfig(0, IndexedSeq(CompressionOptions.gzip(), CompressionOptions.deflate()))
}

/**
* @param level
* defines compression level, {@code 1} yields the fastest compression and
* {@code 9} yields the best compression. {@code 0} means no compression.
* @param bits
* defines windowBits, The base two logarithm of the size of the history
* buffer. The value should be in the range {@code 9} to {@code 15}
* inclusive. Larger values result in better compression at the expense of
* memory usage
* @param mem
* defines memlevel, How much memory should be allocated for the internal
* compression state. {@code 1} uses minimum memory and {@code 9} uses
* maximum memory. Larger values result in better and faster compression
* at the expense of memory usage
*/
final case class CompressionOptions(
level: Int,
bits: Int,
mem: Int,
kind: CompressionOptions.CompressionType,
)
sealed trait CompressionOptions

object CompressionOptions {
val DefaultLevel = 6
val DefaultBits = 15
val DefaultMem = 8

final case class GZip(cfg: DeflateConfig) extends CompressionOptions
final case class Deflate(cfg: DeflateConfig) extends CompressionOptions
final case class Brotli(cfg: BrotliConfig) extends CompressionOptions

/**
* @param level
* defines compression level, {@code 1} yields the fastest compression
* and {@code 9} yields the best compression. {@code 0} means no
* compression.
* @param bits
* defines windowBits, The base two logarithm of the size of the history
* buffer. The value should be in the range {@code 9} to {@code 15}
* inclusive. Larger values result in better compression at the expense
* of memory usage
* @param mem
* defines memlevel, How much memory should be allocated for the
* internal compression state. {@code 1} uses minimum memory and
* {@code 9} uses maximum memory. Larger values result in better and
* faster compression at the expense of memory usage
*/
final case class DeflateConfig(
level: Int,
bits: Int,
mem: Int,
)

object DeflateConfig {
val DefaultLevel = 6
val DefaultBits = 15
val DefaultMem = 8
}

final case class BrotliConfig(
quality: Int,
lgwin: Int,
mode: Mode,
)

object BrotliConfig {
val DefaultQuality = 4
val DefaultLgwin = -1
val DefaultMode = Mode.Text
}

sealed trait Mode
object Mode {
case object Generic extends Mode
case object Text extends Mode
case object Font extends Mode

def fromString(s: String): Mode = s.toLowerCase match {
case "generic" => Generic
case "text" => Text
case "font" => Font
case _ => Text
}
}

/**
* Creates GZip CompressionOptions. Defines defaults as per
* io.netty.handler.codec.compression.GzipOptions#DEFAULT
*/
def gzip(level: Int = DefaultLevel, bits: Int = DefaultBits, mem: Int = DefaultMem): CompressionOptions =
CompressionOptions(level, bits, mem, CompressionType.GZip)
def gzip(
level: Int = DeflateConfig.DefaultLevel,
bits: Int = DeflateConfig.DefaultBits,
mem: Int = DeflateConfig.DefaultMem,
): CompressionOptions =
CompressionOptions.GZip(DeflateConfig(level, bits, mem))

/**
* Creates Deflate CompressionOptions. Defines defaults as per
* io.netty.handler.codec.compression.DeflateOptions#DEFAULT
*/
def deflate(level: Int = DefaultLevel, bits: Int = DefaultBits, mem: Int = DefaultMem): CompressionOptions =
CompressionOptions(level, bits, mem, CompressionType.Deflate)

sealed trait CompressionType

private[http] object CompressionType {
case object GZip extends CompressionType
case object Deflate extends CompressionType
def deflate(
level: Int = DeflateConfig.DefaultLevel,
bits: Int = DeflateConfig.DefaultBits,
mem: Int = DeflateConfig.DefaultMem,
): CompressionOptions =
CompressionOptions.Deflate(DeflateConfig(level, bits, mem))

lazy val config: zio.Config[CompressionType] =
zio.Config.string.mapOrFail {
case "gzip" => Right(GZip)
case "deflate" => Right(Deflate)
case other => Left(zio.Config.Error.InvalidData(message = s"Invalid compression type: $other"))
}
}
/**
* Creates Brotli CompressionOptions. Defines defaults as per
* io.netty.handler.codec.compression.BrotliOptions#DEFAULT
*/
def brotli(
quality: Int = BrotliConfig.DefaultQuality,
lgwin: Int = BrotliConfig.DefaultLgwin,
mode: Mode = BrotliConfig.DefaultMode,
): CompressionOptions =
CompressionOptions.Brotli(BrotliConfig(quality, lgwin, mode))

lazy val config: zio.Config[CompressionOptions] =
(
zio.Config.int("level").withDefault(DefaultLevel) ++
zio.Config.int("bits").withDefault(DefaultBits) ++
zio.Config.int("mem").withDefault(DefaultMem) ++
CompressionOptions.CompressionType.config.nested("type")
).map { case (level, bits, mem, kind) =>
CompressionOptions(level, bits, mem, kind)
(zio.Config.int("level").withDefault(DeflateConfig.DefaultLevel) ++
zio.Config.int("bits").withDefault(DeflateConfig.DefaultBits) ++
zio.Config.int("mem").withDefault(DeflateConfig.DefaultMem)) ++
zio.Config.int("quantity").withDefault(BrotliConfig.DefaultQuality) ++
zio.Config.int("lgwin").withDefault(BrotliConfig.DefaultLgwin) ++
zio.Config.string("mode").map(Mode.fromString).withDefault(BrotliConfig.DefaultMode) ++
zio.Config.string("type")
).map { case (level, bits, mem, quantity, lgwin, mode, typ) =>
typ.toLowerCase match {
case "gzip" => gzip(level, bits, mem)
case "deflate" => deflate(level, bits, mem)
case "brotli" => brotli(quantity, lgwin, mode)
}
}
}
}
Expand Down

0 comments on commit 9d0713e

Please sign in to comment.