ぬけラボ

φ(..)メモメモ

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=&#65533;&#65533;action&#65533;locagin&#65533;user&#65533;hoge&#65533;&#65533;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本当にありがとう。