Google API と文字コード

id:otn:20140901 の「MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 」の続き。
ふと、Rubyデバッグモードで実行してみると、

Exception `ArgumentError' at D:/Ruby/lib/ruby/gems/2.0.0/gems/multi_json-1.10.1/lib/multi_json/adapter.rb:30
 - invalid byte sequence in Windows-31J

のエラーが出ている。

      def blank?(input)
        input.nil? || /\A\s*\z/ === input
      rescue ArgumentError # invalid byte sequence in UTF-8
        false
      end

と、レスキューされているので実際にはエラーにはならないし、意味上も問題ないが、気になる。これは、inputのEncodingがWindows-31Jになっているが実際にはUTF-8の値が入っていると言うことだろう。JSON周りと言うことは、Google API周辺で出ているはずで、実際プログラムを削ってみると、Google APIでリスト取得する段階でエラーになっていることがわかる。

Encoding.default_external = Encoding::UTF_8

を入れてみると、このエラーは出ない。ということは、Webから受け取ったデータのEncodingがデフォルトのままなので、Windows環境ではWindows-31Jと見なされてしまうというのが原因。default_external を変更できないケースもあるだろうから、ライブラリ側で何とかして欲しいところ。


ソースを見てみると、faradayライブラリが原因のよう。

require "faraday"
a = Faraday.new
u = %w|
  http://www.google.com/
  http://www.google.com/search?q=AAAAA
  http://www.google.co.jp/
|
u.each do |x|
  w = a.get(x)
  puts "#{w.status} #{w.body.encoding} #{w.headers['Content-Type']} #{x}"
end

==> 302 UTF-8 text/html; charset=UTF-8 http://www.google.com/
==> 200 ASCII-8BIT text/html; charset=ISO-8859-1 http://www.google.com/search?q=AAAAA
==> 200 ASCII-8BIT text/html; charset=Shift_JIS http://www.google.co.jp/

となる。他のURLも試してみると、200 だと ASCII-8BIT で、30x だと UTF-8 になるようだ。
charsetは見てない。faraday のソースを grep してもcharsetを気にしているらしい記述はない。


それではと思って、net/http を直接使って同様のプログラムを書いてみると、encoding は同様になる。
ということは、faraday は encoding については素通しで、そもそも、net/http で charset を見ずに ASCII-8BIT にしている。


しかし、これがどうして google-api-client を通すと Windows-31J になるのか?? default_external を変えると変わるし。
google-api-client のソースを見てもそれらしい記述はない。


とりあえずパッチを当てておく。

--- api_client.rb.bak   2014-08-11 23:35:44 +0900
+++ api_client.rb       2014-09-24 22:46:13 +0900
@@ -599,6 +599,7 @@

         case result.status
           when 200...300
+            result.body.force_encoding(Encoding::UTF_8)
             result
           when 301, 302, 303, 307
             request = generate_request(request.to_hash.merge({
2014-09-25追記

原因判明。
レスポンスが "Content-Encoding: gzip" の時に、zlib を使って伸張しているが、その際に default_external になってしまっているようだ。
伸張する前の encodig は ASCII-8BIT だ。
ということで、パッチを当てるなら、api_client/gzip.rb に当てるべき。

https://github.com/google/google-api-ruby-client/issues/160#issuecomment-67225979