Spring5.0 + Kotlinで1つのjarにHTTPサーバーとgRPCサーバーを相乗りさせてみた


Spring Boot 2.0.0 M1がリリースされました。以前のエントリで試した当時は 2.0.0.BUILD-SNAPSHOTでありHTTPサーバーが起動している状態でgRPCクライアントを動かすとエラーになっていた。

2.0.0 M1のリリースに伴いHTTPサーバーとgRPCサーバーが1つのjarに相乗りできるようになっているか確認するのが今回のモチベーション。

次のようなアプリケーション構成を実現したい。

ここからは試した過程での気づきなどをアウトプットしていく。

CommandLineRunnerでgRPCサーバーを起動する

HTTPルーティングが動いている状態で CommandLineRunnerをつかいgRPCサーバーを次のように起動させた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
class GrpcServerRunner(private val appProperties: AppProperties,
                       private val echoServer: EchoServer,
                       private val taskServer: TaskServer) : CommandLineRunner, DisposableBean {

    private val logger = KotlinLogging.logger {}

    lateinit var server: Server

    override fun run(args: Array<String>) {

        val port = appProperties.grpc.server.port

        logger.info { "Starting gRPC Server ..." }
        val serverBuilder = NettyServerBuilder.forPort(port)
        serverBuilder.addService(echoServer)
        serverBuilder.addService(taskServer)
        server = serverBuilder.build().start()
        logger.info {"gRPC Server started, listening on port $port."}

        startDaemonAwaitThread()
    }
}

Spring Boot 2.0.0 M1で試したところ問題なく起動した!2.0.0.BUILD-SNAPSHOTではエラーになっていたところ)
2.0.0 M1では300以上のissueやプルリクエストがマージされたので、その中のどれかで解消されたということだろう。

grpc-javaの 1.3.0はエラーになる

Spring Bootには直接関係はないところであるが最新のgrpc-javaの1.3.0を使うとエラーになった。

1
2
3
4
5
6
7
8
java.lang.NoClassDefFoundError: io/netty/handler/codec/http2/internal/hpack/Decoder
        at io.grpc.netty.GrpcHttp2HeadersDecoder.<init>(GrpcHttp2HeadersDecoder.java:85) ~[grpc-netty-1.2.0.jar:1.2.0]
        at io.grpc.netty.GrpcHttp2HeadersDecoder$GrpcHttp2ServerHeadersDecoder.<init>(GrpcHttp2HeadersDecoder.java:135) ~[grpc-netty-1.2.0.jar:1.2.0]
        at io.grpc.netty.NettyServerHandler.newHandler(NettyServerHandler.java:109) ~[grpc-netty-1.2.0.jar:1.2.0]
        at io.grpc.netty.NettyServerTransport.createHandler(NettyServerTransport.java:132) ~[grpc-netty-1.2.0.jar:1.2.0]
        at io.grpc.netty.NettyServerTransport.start(NettyServerTransport.java:77) ~[grpc-netty-1.2.0.jar:1.2.0]
        at io.grpc.netty.NettyServer$1.initChannel(NettyServer.java:141) ~[grpc-netty-1.2.0.jar:1.2.0]
・・・

Netty 4.1.9ではio.netty.handler.codec.http2.internal.hpack.Decoderが削除されているのが原因。

gradleは次のようにgrpc-nettyのみ1.4.0-SNAPSHOTのバージョンを指定することで解消できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
buildscript {
    ext.grpc_version = "1.3.0"
    ext.grpc_version_snapshot = "1.4.0-SNAPSHOT"

    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/milestone" }
        maven { url 'http://repo.spring.io/plugins-release' }
        maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
    }
}

・・・

dependencies {
・・・
    // grpc
    compile "io.grpc:grpc-netty:${grpc_version_snapshot}"
    compile "io.grpc:grpc-protobuf:${grpc_version}"
    compile "io.grpc:grpc-stub:${grpc_version}"
    compile "io.grpc:grpc-okhttp:${grpc_version}"
    compile "com.google.api.grpc:googleapis-common-protos:0.0.3"
・・・
}

BackendサーバーにはHTTPサーバーがいらない

BackendサーバーにはHTTPサーバーがいらないので次のように@SpringBootApplicationがついたメインクラスの起動でWebアプリケーションを動かさないようにした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@SpringBootApplication
@EnableConfigurationProperties(AppProperties::class)
class Application {

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SpringApplicationBuilder(Application::class).web(WebApplicationType.NONE).run(*args)
        }
    }
}

コード

今回試したコードはgithubに置いてあるので参考になれば嬉しい。

まとめ