ようへい

2012年7月6日金曜日

[GAE/J] Twitter4Jで A JSONArray text must start with ... が発生した場合

Google App EngineでTwitterのBotを運用しているのですが、とある処理でエラーが発生していました。
スタックトレースは以下。
twitter4j.TwitterRuntimeException: A JSONArray text must start with '[' at 2 [character 3 line 1]Relevant discussions can be on the Internet at:
 http://www.google.co.jp/search?q=610d24cf or
 http://www.google.co.jp/search?q=03587c2f
TwitterException{exceptionCode=[610d24cf-03587c2f 2efc4644-56189413], statusCode=-1, retryAfter=-1, rateLimitStatus=null, featureSpecificRateLimitStatus=null, version=2.2.5}
 at twitter4j.internal.json.LazyIDs.getTarget(LazyIDs.java:52)
 at twitter4j.internal.json.LazyIDs.getIDs(LazyIDs.java:59)
 at com.example.Hoge.doGet(Hoge.java:175)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
 at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
 at com.google.apphosting.utils.servlet.ParseBlobUploadFilter.doFilter(ParseBlobUploadFilter.java:102)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
 at com.google.apphosting.runtime.jetty.SaveSessionFilter.doFilter(SaveSessionFilter.java:35)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
 at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
 at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
 at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
 at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
 at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
 at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
 at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:249)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
 at org.mortbay.jetty.Server.handle(Server.java:326)
 at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
 at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
 at com.google.apphosting.runtime.jetty.RpcRequestParser.parseAvailable(RpcRequestParser.java:76)
 at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
 at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:135)
 at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.run(JavaRuntime.java:477)
 at com.google.tracing.TraceContext$TraceContextRunnable.runInContext(TraceContext.java:449)
 at com.google.tracing.TraceContext$TraceContextRunnable$1.run(TraceContext.java:455)
 at com.google.tracing.TraceContext.runInContext(TraceContext.java:695)
 at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContextNoUnref(TraceContext.java:333)
 at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContext(TraceContext.java:325)
 at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:453)
 at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:251)
 at java.lang.Thread.run(Thread.java:679)
どうやらTwitterのAPIから返されるJSONデータのパースに失敗しているようです。
Twitter側がおかしなデータを返すことはないと思うので、原因はTwitter4J、GAEにあると考えました。
また、ローカルでは実行できることから、GAEのライブラリではなく、GAEの環境に問題があると思われました。
Google 先生にエラーメッセージを聞いても明確な答えが返ってきません。
そして、JSONの結果をパースする際に影響するような設定がTwitter4Jに無いか調査しました。
TFJ-131 use GZipInputStream to handle gzipped response ? 9888b96 ? yusuke/twitter4j ? GitHub
https://github.com/yusuke/twitter4j/commit/9888b96f5eb0de60f189bf6088976119d145b512 個人的に、Accept-Encoding:gzip を指定して、壊れたデータが返ってきたり、gzipデコードがちゃんと行われなくて、レスポンスデータが受け取れなかったりという事があったので、これが気になった。
どうやら、レスポンスデータの受け取りをgzip圧縮する機能はTwitter4J 2.0.2にて実装されたらしい。
この機能は無効にできないのかなぁ。無効にして試したいんだけど・・・。

ソースの調査

Twitter4Jのページを見ても詳しい情報が無いので、ソースを覗いてみることにしました。
    public ConfigurationBuilder setGZIPEnabled(boolean gzipEnabled) {
        checkNotBuilt();
        configurationBean.setGZIPEnabled(gzipEnabled);
        return this;
    }
        if (notNull(props, prefix, HTTP_GZIP)) {
            setGZIPEnabled(getBoolean(props, prefix, HTTP_GZIP));
        }
    public static final String HTTP_GZIP = "http.gzip";
なるほど。
twitter4j.propertiesで有効/無効を制御できるようです。

いざDeploy

以下のように設定してみました。
twitter4j.http.gzip=false
これでDeployしてみると、見事エラーは無くなりました

衝撃の事実

って、githubのTwitter4Jのリポジトリ見たら、以下のコミットログがあった。
FIX: bug when the content-encoding: gzip header not interpreted ? 2b12d03 ? yusuke/twitter4j ? GitHub
https://github.com/yusuke/twitter4j/commit/2b12d03d409fedc97022c7271c5d96180c44e309
github側では既知の障害だったようで、修正済みっぽいです。
試してみる
という事でTwitter4Jから該当の修正が取り込まれているであろう最新スナップショットビルドの2.2.6を取得し、試してみました。
gzip圧縮機能はデフォルト(有効)に戻して、いざDeploy!
ダメでした・・・orz
twitter4j.TwitterRuntimeException: A JSONArray text must start with '[' at 2 [character 3 line 1]Relevant discussions can be on the Internet at:
 http://www.google.co.jp/search?q=610d24cf or
 http://www.google.co.jp/search?q=03587c2f
TwitterException{exceptionCode=[610d24cf-03587c2f 2efc4644-56189413], statusCode=-1, retryAfter=-1, rateLimitStatus=null, featureSpecificRateLimitStatus=null, version=2.2.5}
 at twitter4j.internal.json.LazyIDs.getTarget(LazyIDs.java:52)
 at twitter4j.internal.json.LazyIDs.getIDs(LazyIDs.java:59)
 at com.example.Hoge.doGet(Hoge.java:169)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
 at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
 at com.google.apphosting.utils.servlet.ParseBlobUploadFilter.doFilter(ParseBlobUploadFilter.java:102)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
 at com.google.apphosting.runtime.jetty.SaveSessionFilter.doFilter(SaveSessionFilter.java:35)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
 at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
 at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
 at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
 at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
 at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
 at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
 at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:249)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
 at org.mortbay.jetty.Server.handle(Server.java:326)
 at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
 at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
 at com.google.apphosting.runtime.jetty.RpcRequestParser.parseAvailable(RpcRequestParser.java:76)
 at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
 at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:135)
 at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.run(JavaRuntime.java:477)
 at com.google.tracing.TraceContext$TraceContextRunnable.runInContext(TraceContext.java:449)
 at com.google.tracing.TraceContext$TraceContextRunnable$1.run(TraceContext.java:455)
 at com.google.tracing.TraceContext.runInContext(TraceContext.java:695)
 at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContextNoUnref(TraceContext.java:333)
 at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContext(TraceContext.java:325)
 at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:453)
 at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:251)
 at java.lang.Thread.run(Thread.java:679)
次期の正式版リリースまで待ってみましょう。
それまではgzip無効で。
関連記事

0 件のコメント:

コメントを投稿