AndroidアプリからRailsサーバ(devise)に対してユーザ登録とログインが必要になるので、Androidの非同期通信を試してみます。
調べてみるとAndroid Asynchronous Http Clientというのが、わりと簡単に使えるらしいです。
[ad#336×280]
参考サイト
Android Asynchronous Http Clientを使ってみた
Android Asynchronous Http Clientの簡単なテスト
新しいプロジェクトを作成
プロジェクト名をHttpClientTest。
Target SDKとCompile WithをAPI16。
あとは適当。
Android Asynchronous Http Clientからandroid-async-http-1.4.2.jarをダウンロードして、libsに投げ込んでConfigure Build Pathからjarを追加。
MainActivity.javaに
import com.loopj.android.http.*;
onCreateのところに
AsyncHttpClient client = new AsyncHttpClient(); client.get("http://www.google.com", new AsyncHttpResponseHandler() { @Override public void onSuccess(String response) { System.out.println(response); } });
AndroidManifest.xmlに
<uses-permission android:name="android.permission.INTERNET" />
これで一旦テストしてみる。
LogCatに出てくるメッセージ
<!doctype html><html itemscope="itemscope" itemtype="http://schema.org/WebPage"> <head><meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能を活用して、お探しの情報を見つけてください。" name="description"> <meta content="noodp" name="robots"><meta itemprop="image" content="/images/google_favicon_128.png"><title>Google</title><script>(function(){...
とうことで、Googleのページをgetできました。簡単なテスト成功!
Android Asynchronous Http Clientでユーザ登録
RailsのDeviseを使ってると仮定して、
ユーザ登録はこんな感じになります。(一応動きますが、修正箇所があります)
AsyncHttpClient client = new AsyncHttpClient(); client.get("https://example.com/users/sign_up.json", new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject json) { try { String token = json.getString("token"); RequestParams params = new RequestParams(); params.put("user[username]", "hogeuser"); params.put("user[password]", "hogepass"); params.put("user[password_confirmation]", "hogepass"); params.put("authenticity_token", token); AsyncHttpClient client2 = new AsyncHttpClient(); client2.addHeader("Accept", "application/json"); client2.post("https://example.com/users", params, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject json2) { System.out.println(json2); } }); } catch (JSONException e) { System.out.println("error"); } } });
RailsのDeviseでのユーザー登録で引っかかるのが、authenticity_token (CSRF token)です。
CSRF tokenを送らないと、
WARNING: Can’t verify CSRF token authenticity
とかいう気持ち悪い警告がログに出てきます。
iOSの場合、Rails側のapplication_controllerのbefore_filterのところに、
response.headers[‘X-Authenticity-Token’] = form_authenticity_token
とか何とか入れてあげると、headerと一緒にtokenが送られてきます。
iOSのプログラムで
[[request responseHeaders] objectForKey:@”X-Authenticity-Token”];
とか入れてあげると、tokenをゲットすることが出来ます。
が、Android Asynchronous Http Clientの場合、
このheaderが取れない取れない。。
request responseHeadersみたいなのがないみたい。。
いろいろ試してのheaderからは簡単に取れそうもないので、
getのリターン時のjsonでtokenをもらうことにしました。
class Users::RegistrationsController < Devise::RegistrationsController を少し変更。
[html autolinks="false"]
def new
respond_to do |format|
format.json {
render :json => { :success => true, :token => form_authenticity_token }
}
format.html{
resource = build_resource({})
respond_with resource
}
end
end
[/html]
https://example.com/users/sign_up.json
をgetリクエストすると、
String token = json.getString(“token”)でtokenをもらえます。
それをユーザー登録するときのpostと一緒に送って上げる。。
ユーザ登録はできたけど、なぜかRailsのlogに下記の警告が出てくる・・・。
WARNING: Can’t verify CSRF token authenticity
Android Asynchronous Http Clientでログイン
WARNINGが出てくるけど、とにかくデータベース的にはユーザが登録されているので次に進んでしまいます。
で、ここでひどく悩みました。
Androidアプリ側のコード
まずログインしているかどうか調べて、
ログインしていない場合、handleFailureMessageで捕まえて、
トークンをもらって、ログインして、ユーザ情報を更新する。
というプログラム。
でもユーザ情報が更新できません。
AsyncHttpClient client = new AsyncHttpClient(); client.get("https://example.com/users/loginCheck.json", new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject json) { System.out.println("onSuccess"); System.out.println(json); } @Override public void handleFailureMessage(Throwable e, String responseBody) { System.out.println("handleFailureMessage"); System.out.println(e); AsyncHttpClient tokenClient = new AsyncHttpClient(); tokenClient.get("https://example.com/users/sign_up.json", new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject json) { try { final String token = json.getString("token"); RequestParams params = new RequestParams(); params.put("user[username]", "hogeuser"); params.put("user[password]", "hogepass"); params.put("authenticity_token", token); params.put("user[remember_me]", "1"); AsyncHttpClient loginClient = new AsyncHttpClient(); loginClient.addHeader("Accept", "application/json"); loginClient.post("https://example.com/users/sign_in.json", params, new JsonHttpResponseHandler() { public void onSuccess(JSONObject json) { System.out.println("login"); System.out.println(json); AsyncHttpClient profileClient = new AsyncHttpClient(); RequestParams params2 = new RequestParams(); params2.put("profile", "I am the God!!!"); params2.put("authenticity_token", token); profileClient.addHeader("Accept", "application/json"); client.post("https://example.com/users/update_profile.json", params2, new JsonHttpResponseHandler() { public void onSuccess(JSONObject json) { System.out.println("update_profile"); System.out.println(json); } }); } }); } catch (JSONException e) { System.out.println("error"); } } }); } });
Railsのログを見ていると、
ログインしたときも
WARNING: Can’t verify CSRF token authenticity
というログが出ています。でもログインはしている感じです。
でも、その後すぐにユーザ情報を更新しようとすると、
認証に失敗して更新できないというログが出てきて、更新に失敗します。
ちなみに、ユーザ情報はログインユーザのみが変えられます。
CSRF tokenがおかしいのは確かなんですが、
なんでおかしいのかわかりません。
iOSアプリの場合、同じような手続きで問題なく更新してました。。
で、かなり時間を使っていろいろ試してみると・・、原因が判明!
全ての原因は
AsyncHttpClient client = new AsyncHttpClient();
AsyncHttpClient loginClient = new AsyncHttpClient();
AsyncHttpClient profileClient = new AsyncHttpClient();
このコードが原因でした。
clientはどうでもいいんですが、
loginClientがtokenをゲットして、そのtokenをprofileClientが使おうとすると、
人が違う!ということでRails側がtokenを拒否していました。
その結果、無効なtokenを使ってきたので、
WARNING: Can’t verify CSRF token authenticity
というメッセージがログに出て、ログインしても直ぐにログアウトされてしまっているようです。
profileClientは作成せずに、loginClientの作成時にfinalを付けます。
final AsyncHttpClient loginClient = new AsyncHttpClient();
そして引き続きloginClientでユーザ情報を更新するとtokenを受け付けて更新に成功しました。
今回の教訓
AsyncHttpClient client = new AsyncHttpClient();
AsyncHttpClient client2 = new AsyncHttpClient();
clientとclient2は別人なので、同じtokenは使えません。
コメントを残す