すきま風

勉強したことのメモとか

protobufをHTTP通信する

protocol bufferは通常gRPCで利用しますが、API定義をprotoファイルで行い 通信自体はHTTPで行いたい、というケースが実務でありましたので方法を記載します。

検証環境

software version
OS MacOS Mojave
Spring Boot 2.1.5
Java 1.8.0_192-b12
Kotlin 1.3.40
com.google.protobuf 0.8.8

protobufを送信する

content-typeを「application/x-protobuf」 にすれば普通に送れます。基本これでOK

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.demo.web.proto";

import "google/protobuf/timestamp.proto";

package demo;

message Foo {
    FooId id = 1;
    repeated FooProperty property = 2;
    google.protobuf.Timestamp createdAt = 3;
}

message FooId {
    int32 id = 1;
}

message FooProperty {
    string code = 1;
    string name = 2;
}
// クライアント
suspend fun requestByProtobuf(foo: Foo): FooResponse {
    val client = WebClient.builder()
        .baseUrl("http://localhost:8081")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/x-protobuf")
        .build()

    return client
        .post()
        .uri("foo/proto")
        .body(BodyInserters.fromObject(foo))
        .exchange()
        .flatMap {
            it.bodyToMono(FooResponse::class.java)
        }
        .awaitFirst()
}

// サーバ
@PostMapping(
    "foo/proto"
)
fun test(
    @RequestBody foo: Foo
): FooResponse {
    return FooResponse.newInstance(foo)
}

Jsonを送信する

受け取り側でJsonをprotoにmappingするためにProtobufHttpMessageConverterをBeanに設定します。

// 受け取り側にBeanを追加
@Configuration
class ProtobufConverterConfig {
    @Bean
    fun protobufHttpMessageConverter(): ProtobufHttpMessageConverter {
        val printer = JsonFormat.printer().omittingInsignificantWhitespace()
        val parser = JsonFormat.parser().ignoringUnknownFields()

        return ProtobufJsonFormatHttpMessageConverter(parser, printer)
    }
}

ignore fieldが含まれる場合は無視するように設定しています。 後は普通にJsonを送信するだけで適切にMappingされます。

suspend fun requestByJson(fooJson: String): FooResponse {

    val client = WebClient.builder()
        .baseUrl("http://localhost:8081")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
        .build()

    return client
        .post()
        .uri("foo/proto")
        .body(BodyInserters.fromObject(fooJson))
        .exchange()
        .flatMap {
            it.bodyToMono(FooResponse::class.java)
        }
        .awaitFirst()
}

まとめ

protobufをHTTP送信する方法でした。通常はx-protobufでやり取りするのがシンプルで良いと思います。