diff --git a/README.adoc b/README.adoc index f054235d..569cf5ce 100644 --- a/README.adoc +++ b/README.adoc @@ -59,6 +59,11 @@ port: 2449 tls: enabled: false +compression: + grpc: + server: + enabled: false + proxy: host: 0.0.0.0 port: 8545 @@ -103,6 +108,7 @@ Which sets the following: - gRPC access through 0.0.0.0:2449 ** TLS security is disabled (_please don't use in production!_) +** compression is disabled for gRPC server (enabled by default) - JSON RPC access through 0.0.0.0:8545 (both HTTP and WebsScket) ** proxy requests to Ethereum and Kovan upstreams ** request path for Ethereum Mainnet is `/eth`, `/kovan` for Kovan Testnet, and `/btc` for bitcoin diff --git a/docs/03-server-config.adoc b/docs/03-server-config.adoc index 2fb2895c..f60e1583 100644 --- a/docs/03-server-config.adoc +++ b/docs/03-server-config.adoc @@ -21,6 +21,12 @@ tls: client: require: true ca: "ca.crt" + +compression: + grpc: + server: + enabled: false + cluster: include: "upstreams.yaml" ---- @@ -29,6 +35,7 @@ It configures following: - server is listening with gRCP API on `0.0.0.0:2449` - TLS is enabled +- compression is disabled for gRPC server (enabled by default) - server certificate is located at `server.crt` with the key for it at `server.p8.key` - the server requires a client authentication by TLS client certificate signed by `ca.crt` certificate - no JSON RPC is configured @@ -51,6 +58,10 @@ a| `tls` | TLS configuration for gRPC. See link:08-authentication.adoc[Authentication] for details +a| `compression` +| +| Compression configuration + a| `proxy` | | Proxy configuration @@ -60,6 +71,21 @@ a| `upstreams` | Upstreams configuration |=== +=== Compression +Compression is enabled by default on the gRPC server +(the server accepts compressed requests and can send compressed responses). +Responses will be compressed if a client supports compression (sends relevant headers), +otherwise, communication will be uncompressed. +But if for some reason you need to disable compression forcibly, +add the lines below to the config: +[source,yaml] +---- +compression: + grpc: + client: + enabled: false +---- + === Enabling JSON RPC proxy In addition to the gRPC protocol, Dshackle provides access compatible with Bitcoin and Ethereum JSON RPC. diff --git a/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt b/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt index 78618fa4..bd09d1eb 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt @@ -18,7 +18,11 @@ package io.emeraldpay.dshackle import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerGrpc +import io.grpc.Codec import io.grpc.Server +import io.grpc.ServerCall +import io.grpc.ServerCallHandler +import io.grpc.ServerInterceptor import io.grpc.netty.NettyServerBuilder import io.micrometer.core.instrument.Metrics import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics @@ -42,6 +46,17 @@ open class GrpcServer( private var server: Server? = null + class CompressionInterceptor : ServerInterceptor { + override fun interceptCall( + call: ServerCall, + headers: io.grpc.Metadata, + next: ServerCallHandler + ): ServerCall.Listener { + call.setCompression(Codec.Gzip().messageEncoding) + return next.startCall(call, headers) + } + } + @PostConstruct fun start() { log.info("Starting gRPC Server...") @@ -53,9 +68,12 @@ open class GrpcServer( .let { if (mainConfig.accessLogConfig.enabled) { it.intercept(accessHandler) - } else { - it } + if (mainConfig.compression.grpc.serverEnabled) { + it.intercept(CompressionInterceptor()) + log.info("Compression enabled for gRPC server") + } + it } tlsSetup.setupServer("Native gRPC", mainConfig.tls, true)?.let { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/CompressionConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/CompressionConfig.kt new file mode 100644 index 00000000..efbcb6f4 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/CompressionConfig.kt @@ -0,0 +1,27 @@ +package io.emeraldpay.dshackle.config + +class CompressionConfig( + var grpc: GRPC = GRPC() +) { + /** + * Config example: + * ``` + * compression: + * grpc: + * server: + * enabled: true + * client: + * enabled: false + * ``` + */ + class GRPC( + var serverEnabled: Boolean = true, + var clientEnabled: Boolean = true + ) + + companion object { + fun default(): CompressionConfig { + return CompressionConfig() + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/CompressionConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/CompressionConfigReader.kt new file mode 100644 index 00000000..563a5661 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/CompressionConfigReader.kt @@ -0,0 +1,32 @@ +package io.emeraldpay.dshackle.config + +import org.yaml.snakeyaml.nodes.MappingNode +import java.io.InputStream + +class CompressionConfigReader : YamlConfigReader(), ConfigReader { + fun read(input: InputStream): CompressionConfig? { + val configNode = readNode(input) + return read(configNode) + } + + override fun read(input: MappingNode?): CompressionConfig { + val config = CompressionConfig() + getMapping(input, "compression")?.let { node -> + readGRPC(config.grpc, getMapping(node, "grpc")) + } + return config + } + + private fun readGRPC(grpcCompressionConfig: CompressionConfig.GRPC, input: MappingNode?) { + getMapping(input, "server")?.let { serverNode -> + getValueAsBool(serverNode, "enabled")?.let { + grpcCompressionConfig.serverEnabled = it + } + } + getMapping(input, "client")?.let { clientNode -> + getValueAsBool(clientNode, "enabled")?.let { + grpcCompressionConfig.clientEnabled = it + } + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt index 4e228c01..94c06678 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt @@ -28,4 +28,5 @@ class MainConfig { var accessLogConfig: AccessLogConfig = AccessLogConfig.default() var health: HealthConfig = HealthConfig.default() var signature: SignatureConfig? = null + var compression: CompressionConfig = CompressionConfig.default() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt index e6d4dfe0..79e29c2d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt @@ -37,6 +37,7 @@ class MainConfigReader( private val accessLogReader = AccessLogReader() private val healthConfigReader = HealthConfigReader() private val signatureConfigReader = SignatureConfigReader(fileResolver) + private val compressionConfigReader = CompressionConfigReader() fun read(input: InputStream): MainConfig? { val configNode = readNode(input) @@ -83,6 +84,9 @@ class MainConfigReader( signatureConfigReader.read(input).let { config.signature = it } + compressionConfigReader.read(input).let { + config.compression = it + } return config } }