web-dev-qa-db-ja.com

Websocket上のバイト配列としての任意のprotobufメッセージ-実際のメッセージタイプを事前に決定する方法

Protobufは素晴らしくてダンディですが、自己記述を念頭に置いて作成されたものではありません。これで、明確に定義されたプロトコルを使用していて、たとえば次のように置き換えたい場合は、まったく問題ありません。 SOAPメッセージまたは単にRESTful JSON APIなどを置き換えたい。

しかし、私はそれをWebSocketで使用し、別のURLで何度も閉じて再度開くのではなく、WebSocketのポイントを無効にします-開いたままにして、ワイヤー経由で異なるメッセージを送信します- バイト配列

今、私の問題は単純ですが、少し複雑です。クライアントからサーバーに(またはその逆に)任意のメッセージを送信したいのですが、受信者は受信したメッセージの種類とその解釈方法を簡単に判断する必要があります。

擬似コードでは、これは次のようになります。

クライアント

Message m = new Auth().withUserName("Sorona").withPassword("TotallyNotMyActualPassword")

ws.send(m)

サーバー

Map[Type, Handler]

receiveMessage(Message m) {
   handlers.get(m.determineType()).handle(m)
}

ハンドラー

trait Handler[T] {
  def handle()
}

だから私が本当に欲しいのは2つのことです:

1)メッセージサイズを(劇的に)増やすことなく、Anyメッセージの実際のタイプを判別できます。

2)堅牢で拡張可能であること

新しいメッセージタイプの場合は、単にハンドラーを追加して完了します。

維持する必要のあるハードコードされた列挙型、採用する必要のあるスイッチケース構造などはありません。

単純にOOPこれはかなり簡単ですが、Protobufsでは私はちょっと立ち往生しています。

ヒントはありますか?

1
Sorona

私がこれまでに思いついたもの:

message Root
{
    string type = 1;
    google.protobuf.Any content = 2; 
}

message Dog
{
   string name = 1;
   string fav_food = 2;
}

message Cat
{
   string name = 1;
   uint32 colors = 2; 
}

次に、どこか(共有ファイル、データベースなど)で次のようなマップを作成します。

val mapper: Map[String, Class] = Map(
  "Dog" -> Dog,
  "Cat" -> Cat
)

送信者で私はします:

val nero = Dog().withName("Nero").withFavoriteFood("Raw meat")
val msg = Root().withType("Dog").withMessage(Any.pack(nero))

および受信側:

val r = Root.parseFrom(msg)
val a = r.message.get.unpack(mapper(r.type))

それが基本的な計画です。次に、types(「Dog」や「Cat」など)がハードコードされている独自のビルダーを紹介し、クラス/メッセージタイプとハンドラーの間に別のマッパーを追加します。

しかし、私がそこまで来てそれが機能するかどうかは推測します(現在、Scala.jsを使用しているため機能しません。Any.packは現在サポートされていないようです)まったく別の話になります;)

0
Sorona

ここではさまざまなアプローチを考えることができます。

  • 自己記述メッセージ(提案のように)
  • メッセージ以外のメッセージの説明(メタデータ)
  • rpcフレームワーク(複雑なプロジェクトに推奨)

メタデータの説明

自己記述型のプロパティを各メッセージに入れたくない場合は、それらを外部で定義し、たとえばメッセージの間に送信することができます。例えば

stream: ... "now comes a dog", { dog }, "now comes a cat", { cat }, ...

「犬がやってくる」をどのようにエンコードするか、そしてそれをメッセージにどのようにマッピングするかはあなた次第です。識別子の構造によっては、自己記述型のメッセージよりも複雑になる場合があります(たとえば、任意の長さの文字列を使用する場合は、その番号を送信する必要もあります)。

自己記述メッセージ

カスタムプロトコルは次のようになります

stream: ..., { self-describing dog }, { self-describing cat }, ...  

rPCフレームワークの使用

gRPC のようなrpcフレームワークを使用してみることもできます。これにより、より洗練されたデザインが得られます。ここでは、さまざまなメソッドとパラメータを使用してサービスインターフェイスを定義できます。通信が生成され、メソッドの実装を提供するだけで済みます。ただし、WebSocketが完全にサポートされているかどうかはわかりません

0
jannikb