MessagePackを解析するためのライブラリ/ツール msgpack-microscope
概要
Golangで、MessagePackの解析用ライブラリとそれを使用したサンプルツールを作成しました。
ビルド済みバイナリはReleases から取得できます。
MessagePackとは
JSONのようなデータ構造を、バイナリ形式で表現したデータ形式です。 大抵の場合、JSONよりもデータサイズを減らすことができるようですが、バイナリ列のために可読性が下がっています。 公式サイトの例ですと、
{"compact":true,"schema":0}
という27byteのJSONを、
82 a7 63 6f 6d 70 61 63 74 c3 a6 73 63 68 65 6d 61 00
という18byteのバイナリに変換する例が示されています。
特徴
基本的には、インスパイア元のmsgpack-inspectとほぼ同じですが、下記が異なっています。 *1
- HTTP POST経由のデータを解析することができる
- Timestamp extension type をサポート
- Fluentd の EventTime Ext Format をサポート*2
- 破損データ*3に少し耐性がある
サンプルツール msgpack2json
その名のとおり、MessagePackをJSONに変換するためのサンプルツール。 出力フォーマットは、同じ機能のツールであるmsgpack-inspectの形式をほぼ流用しています。
下記のいずれかからMessagePackのバイト列を受け取り、標準出力に解析結果を出力します。
- 標準入力(他コマンドからパイプで繋ぐケース)
- ファイル
- HTTP POST
それぞれの動作サンプルについて以下に書きます。なお、いずれのケースについても下記JSON相当のMessagePackを解析した結果になります。
{"compact":true,"schema":0}
標準入力から読む
パイプでリダイレクトするケースを想定した使い方です。
$ printf "\x82\xa7compact\xc3\xa6schema\x00" | ./msgpack2json
出力結果は下記。
{"format":"fixmap", "header":"0x82", "length":2, "raw":"0x82a7636f6d70616374c3a6736368656d6100", "value": [ {"key": {"format":"fixstr", "header":"0xa7", "raw":"0xa7636f6d70616374", "value":"compact"}, "value": {"format":"true", "header":"0xc3", "raw":"0xc3", "value":true} }, {"key": {"format":"fixstr", "header":"0xa6", "raw":"0xa6736368656d61", "value":"schema"}, "value": {"format":"positive fixint", "header":"0x00", "raw":"0x00", "value":0} } ] }
ファイルを読む
バイナリファイルを読むケースです。
$ printf "\x82\xa7compact\xc3\xa6schema\x00" > b.msgp $ ./msgpack2json b.msgp
出力結果は標準入力と同様なので割愛。
HTTP POST
簡単なhttp server機能も備えています。-sオプションでserver動作をし、デフォルトで8080ポートへのPOSTを待ちます。 なお、ポート番号については、-p オプションで変更することができます。
$ printf "\x82\xa7compact\xc3\xa6schema\x00" > b.msgp $ ./msgpack2json -s &
たとえば、curlで下記のように投げることで出力結果を得ることができます。
$ curl -sS localhost:8080 -X POST --data-binary "@b.msgp"
Fluent-bitのout_httpプラグインなど、HTTP POST形式でMessagePackを投げるソフトウェアのデバッグなどにも使うことができます。
その他オプションについて
- -e : Fluentd の EventTime Ext Format を解釈可能とする。
Fluentdでは、Ext Typeを使用して、独自のタイムスタンプ形式を定義しています。このオプションを使うことで、そのタイムスタンプ形式を解釈できるようになります。
$ printf "\xd7\x00\x5c\xda\x05\x00\x00\x00\x00\x00"| ./msgpack2json -e
{"format":"event time", "header":"0xd7", "type":0, "raw":"0xd7005cda050000000000", "value":"2019-05-14 09:00:00 +0900 JST"}
- -r: プレーンなJSON形式で出力する
解析用でない、シンプルな形式で出力します。
$ printf "\x82\xa7compact\xc3\xa6schema\x00"|./msgpack2json -r
{"compact":true,"schema":0}
ライブラリ
基本的には、Decode関数にMessagePackの[]byteを与えることで、MPObjectに変換し、そのMPObjectの中身をゴニョゴニョして解析する形になります。
type MPObject struct { FirstByte byte /* どのフォーマットかを示すbyte */ FormatName string /* フォーマット名称 */ ExtType int8 /* Ext フォーマットの場合、どのTypeかを示す。それ以外のフォーマットの場合は0。*/ Length uint32 /* length を備えるフォーマットの場合のみ。*/ DataStr string /* 各フォーマットのデータを文字列にしたもの */ Raw []byte /* FirstByteからデータ本体までのbyteスライス。ArrayやMapの場合は、その要素も含む。 */ Child []*MPObject /* ArrayやMap フォーマットの場合、その各要素が保存される。*/ }
import
import "github.com/nokute78/msgpack-microscope/pkg/msgpack"
サンプルコード
簡単なサンプルコードは下記です。
package main import ( "bytes" "fmt" "github.com/nokute78/msgpack-microscope/pkg/msgpack" "os" ) func showMsgPack(obj *msgpack.MPObject) { switch { case msgpack.IsMap(obj.FirstByte) || msgpack.IsArray(obj.FirstByte): for _, v := range obj.Child { showMsgPack(v) } default: fmt.Fprintf(os.Stdout, "%s\n", obj) } } func main() { /* {"compact":true,"schema":0} in JSON */ msgp := []byte{0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00} ret, err := msgpack.Decode(bytes.NewBuffer(msgp)) if err != nil { os.Exit(-1) } showMsgPack(ret) }
出力結果は、下記のようになります。
fixstr(0xa7): val="compact" true(0xc3): val=true fixstr(0xa6): val="schema" positive fixint(0x00): val=0
独自のExt Typeを定義する
MessagePackには、Ext Typeを用いることでユーザーが独自フォーマットを定義することができます。 例としては、FluentdのEventTimeがあります。
本ライブラリでは、下記のstruct埋め、RegisterExt関数に食わせることで、ユーザー定義のフォーマットを追加することができます。
type ExtFormat struct { FirstByte byte /* どのフォーマットかを示すbyte。0xd4-0xd8, 0xc7-0xc9のいずれか。 */ ExtType int8 /* 種別を示す。正の整数。負の値は予約されているため、通常は使わない。*/ TypeName string /* Type 名称 */ DecodeFunc func([]byte) string /* []]byte列を変換するための関数 */ }
FluentdのEventTimeの場合の例は下記のようになります。
func extEventTimeV1(b []byte) string { if len(b) != 8 { return "" } var sec int32 if binary.Read(bytes.NewReader(b[:4]), binary.BigEndian, &sec) != nil { return "" } var nsec int32 if binary.Read(bytes.NewReader(b[4:]), binary.BigEndian, &nsec) != nil { return "" } return fmt.Sprintf("%v", time.Unix(int64(sec), int64(nsec))) } // RegisterFluentdEventTime registers Fluentd ext timestamp format. // https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1 */ func RegisterFluentdEventTime() { msgpack.RegisterExt(&msgpack.ExtFormat{FirstByte: msgpack.FixExt8Format, ExtType: 0, TypeName: "event time", DecodeFunc: extEventTimeV1}) msgpack.RegisterExt(&msgpack.ExtFormat{FirstByte: msgpack.Ext8Format, ExtType: 0, TypeName: "event time", DecodeFunc: extEventTimeV1}) }