Fluentdのhttpインプットプラグインを使ってみた&=はまってみた
必要になったので、Fluentdのhttpインプットプラグインを試してみました。
httpインプットプラグインはWebAPI経由で簡単にデータを入力することが出来るとても便利なプラグインです。
使用可能なフォーマットとしてはjsonやmsgpack形式のバイナリデータの送信が可能なようです。
使ってみた
とりあえず公式ドキュメント(http Input Plugin | Fluentd)に習って使ってみます。
設定ファイル
$ vim test.conf # HTTPインプット <source> type http port 8888 bind 0.0.0.0 </source> # 標準出力へアウトプット <match **> type stdout </match>
Fluentdの起動
$ fluentd -c test.conf
curlでJSONデータを送信
リソース情報にFluentdで使用するタグを指定します。
また、POSTする際のパラメータでデータタイプを指定しています。
$ curl -X POST -d 'json={"action":"login","user":2}' http://localhost:8888/test.http.json
Fluentd側
# データが送られてきました 2013-06-24 11:57:15 +0900 test.http.json: {"action":"login","user":2}
msgpack形式のデータを送信
次はmsgpack形式のデータをhttpインプットプラグインに送信してみたいと思います。
例えばこんなクライアントを作って...
require 'net/http' require 'msgpack' host, port = 'localhost', 8888 http = Net::HTTP.new(host, port) data = {"action"=>"locagin","user"=>2}.to_msgpack http.post('/test.http.msgpack', "msgpack=#{data}")
Fluentd側
# データが送られてきました 2013-06-24 12:41:52 +0900 test.http.msgpack: {"action":"locagin","user":2}
msgpack形式のデータも受け取ることが出来ました!
はまってみた
完全にクライアントの作りの問題なのですが、
msgpack形式のデータをPOSTした時のエンコードではまってみました。
先ほど作成したクライアントのdataに値を追加してリクエストを送ってみます。
... data = {"action"=>"locagin","user"=>2,"hoge"=>[38,61]}.to_msgpack ...
Fluentd側
# データが送られてきま、せん!
netcat
netcatでプロキシを立ててリクエストを確認してみました。
$ nc -l 8888 POST /test.http.msgpack HTTP/1.1 Accept: */* User-Agent: Ruby Connection: close Host: localhost:8888 Content-Length: 43 Content-Type: application/x-www-form-urlencoded msgpack=��action�locagin�user�hoge��fuga&=
原因としては、Content-Type が application/x-www-form-urlencoded ですので、
POSTする時はパラメータとデータ部分を=で連結し、パラメータ同士を連結する場合は&で連結するようになっています。
そして送信するデータの中にASCIIコードの&(38)と=(61)を含めた場合に区切る場所を間違って解釈してしまいデータが受け取れなかったようです。
これは、クライアント側で適切にエンコードしたデータを送信しないと受け取れない文字列が出てきそうです。
実際にFluentdのテストコードはどのようにしているのか確認したところ、set_form_dataメソッドで適切にエンコードしていました。
# set_form_dataメソッドはURIモジュールのencode_www_formメソッドを呼び出しています。
テストコード(fluentd/test/plugin/in_http.rb at master · fluent/fluentd · GitHub)
# テストコード抜粋 def test_msgpack d = create_driver time = Time.parse("2011-01-02 13:14:15 UTC").to_i d.expect_emit "tag1", time, {"a"=>1} d.expect_emit "tag2", time, {"a"=>2} d.run do d.expected_emits.each {|tag,time,record| res = post("/#{tag}", {"msgpack"=>record.to_msgpack, "time"=>time.to_s}) assert_equal "200", res.code } end end def post(path, params) http = Net::HTTP.new("127.0.0.1", 9911) req = Net::HTTP::Post.new(path, {}) req.set_form_data(params) http.request(req) end
データを適切にエンコードして試してみる
データを適切にエンコードしてもう一度試してみました。
require 'net/http' require 'msgpack' host, port = 'localhost', 8888 http = Net::HTTP.new(host, port) data = {"action"=>"locagin","user"=>2,"hoge"=>["fuga",38,61]}.to_msgpack # エンコード data = URI.encode_www_form({"msgpack" => data}) http.post('/test.http.msgpack', data)
Fluentd側
# データが送られてきました 2013-06-24 13:16:14 +0900 test.http.msgpack: {"action":"locagin","user":2,"hoge":["fuga",38,61]}
リクエストを確認する
適切にエンコードされています!
# Content-Lengthがjsonより長くなってしまいます。。
$ nc -l 8888 POST /test.http.msgpack HTTP/1.1 Accept: */* User-Agent: Ruby Connection: close Host: localhost:8888 Content-Length: 63 Content-Type: application/x-www-form-urlencoded msgpack=%83%A6action%A7locagin%A4user%02%A4hoge%93%A4fuga%26%3D
おわりに
今回のエンコードの問題ですが、httpインプットプラグイン側のContent-Typeの判別条件に
application/x-msgpack を追加してdataだけを送るのはどうだろう?と思っています。
繰り返しますが、HTTPインプットプラグインは非常に便利なプラグインです!
このプラグインのおかげでサービス開発の際のコストが激減しており、
大変お世話になっております。 # Fluentd本当にありがとう。