こんにちは!kossyです!
今回はRack::MockRequestのenv_forメソッドとRack::Requestクラスを使って、Rack::Requestのbodyのrack.inputをいじっていて気づいたことがあったので、
備忘録としてブログに残してみたいと思います。
Rack::MockRequestを使ってenvを生成する
rack gemにはデフォルトで Rack::MockRequest というクラスが定義されています。
Rack::MockRequestのenv_forメソッドを使えばenvオブジェクトを生成することができるので、まずはこちらを試してみます。
# rails c $ env = Rack::MockRequest.env_for("/", {}) => {"rack.version"=>[1, 3], "rack.input"=>#<StringIO:0x000055e41ae646b0>, "rack.errors"=>#<StringIO:0x000055e41ae64750>, "rack.multithread"=>true, "rack.multiprocess"=>true, "rack.run_once"=>false, "REQUEST_METHOD"=>"GET", "SERVER_NAME"=>"example.org", "SERVER_PORT"=>"80", "QUERY_STRING"=>"", "PATH_INFO"=>"/", "rack.url_scheme"=>"http", "HTTPS"=>"off", "SCRIPT_NAME"=>"", "CONTENT_LENGTH"=>"0"}
envのrack.inputに日本語の文字列を渡してみます。
$ env["rack.input"].string => "" $ env.merge!(::Rack::RACK_INPUT => StringIO.new("その辺にいるWebエンジニアの備忘録")) $ env["rack.input"].string => "その辺にいるWebエンジニアの備忘録"
env["rack.input"]に日本語の文字列を入れることができました。
Rack::Requestのbodyの日本語文字列をreadで読み出してみる
次に、Rack::Requestインスタンスを生成して、先ほど渡した日本語文字列をreadメソッドで読み出してみます。
$ request = Rack::Request.new(env) # envで文字列を参照できる $ request.env['rack.input'].string => "その辺にいるWebエンジニアの備忘録" # bodyはStringIOクラスのインスタンスが格納されている $ request.body => #<StringIO:0x000055e41e96a8b8> # 4096はバイト数 $ data = request.body.read(4096) => "\xE3\x81\x9D\xE3\x81\xAE\xE8\xBE\xBA\xE3\x81\xAB\xE3\x81\x84\xE3\x82\x8BWeb\xE3\x82\xA8\xE3\x83\xB3\xE3\x82\xB8\xE3\x83\x8B\xE3\x82\xA2\xE3\x81\xAE\xE5\x82\x99\xE5\xBF\x98\xE9\x8C\xB2"
非ASCII文字はエスケープされていますね。この文字列をJSON.generateしようとすると以下のエラーが発生します。
$ JSON.generate(data) => Encoding::UndefinedConversionError: "\xE3" from ASCII-8BIT to UTF-8
同様の問題がsentry-rubyというGemで発生していたようです。
修正されたPRでは、非ASCII文字をUTF-8でエンコーディングしていました。
$ encoded_data = data.force_encoding(Encoding::UTF_8) => "その辺にいるWebエンジニアの備忘録"
rack.inputに非ASCII文字を渡してreadでバイト指定して読み出す時は気をつけるようにしましょう。。。