株式会社ライブキャストロゴ 株式会社ライブキャスト

2011年11月3日 追記
ご注意!
Google Translate API v1が2011年12月1日にサービスが終了いたします。添付のサンプルソースでは、Google Translate API v1を利用していますので、翻訳が正常に機能しない場合がございます。ご承知おきください。

Android Market公開を目指してAndroidアプリを開発する!(翻訳機能実装編)の続きです。

今回は、翻訳結果をTweetする機能を実装していきたいと思います。

先日、TranslatAIRを対応させましたが、認証にはXAuthを採用していますので、今回もそうすることにします。
TranslatAIRのTweet機能をXAuth対応する(準備編)
TranslatAIRのTweet機能をXAuth対応する(実装編)

まず、新たにモデルクラスを2つ作りました。
XAuthに使う情報を集約したクラスとTweetする際に必要になる情報を集約したクラスです。

●XAuthModelクラス

package jp.flashcast.translator.android.model;

public class XAuthModel {
	public static final String consumer_key = "xxxxxxxxxxxxxxxxxxxxxx";
	public static final String consumer_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
	private String oauth_token;
	private String oauth_token_secret;

	public XAuthModel() {
		oauth_token = "";
		oauth_token_secret = "";
	}

	public void setOAuthToken(String oauth_token) {
		this.oauth_token = oauth_token;
	}

	public String getOAuthToken() {
		return oauth_token;
	}

	public void setOAuthTokenSecret(String oauth_token_secret) {
		this.oauth_token_secret = oauth_token_secret;
	}

	public String getOAuthTokenSecret() {
		return oauth_token_secret;
	}
}

※ 4、5行目は適宜変更してください。

●TweetModelクラス

package jp.flashcast.translator.android.model;

public class TweetModel {
	private String account;
	private String password;
	private String status;
	private boolean success;

	public TweetModel() {
		account = "xxxxxxxxxx";
		password = "xxxxxxxxxx";
		status = "";
	}
	public void setAccount(String account) {
		this.account = account;
	}

	public String getAccount() {
		return account;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getPassword() {
		return password;
	}

	public void setStatus(String status) {
		this.status = status;
	}

	public String getStatus() {
		return status;
	}

	public void setSuccess(boolean success) {
		this.success = success;
	}

	public boolean isSuccess() {
		return success;
	}
}

※ 10、11行目は適宜変更してください。

次に、XAuthで認証して、翻訳結果をTweetする部分です。

こちらも、前回のgoogle翻訳API呼び出しと同じように、Threadクラスを継承した別クラスとして実装しました。ソースをすべてご紹介すると、ずいぶん長くなってしまいますので割愛しますが、要注意事項をご紹介したいと思います。

XAuthの部分がある程度できた時点でテストしてみたのですが、なぜかHttpステータス417が返ってきました。

Expectation Failed 417

今まで、417って、ほとんど目にしたことがありませんでした。

417 Expectation Failed
期待するヘッダに失敗。その拡張はレスポンスできず、またはプロキシサーバが次に到達するサーバがレスポンスできないと判断している。
具体例として、Expect:ヘッダに100-continue以外の変なものを入れた場合や、そもそもサーバが100 Continueが扱えない場合に返す。

HTTPステータスコード – Wikipediaより引用。

なるほどなるほど~

Expect:100-continue

ってなんだろう?

Expect リクエストヘッダフィールドは、特定のサーバの振る舞いがクライアントによって要求されている事を示すために使われる。

リクエスト中のExpect フィールドにある期待値{expectation values} を理解できない、あるいはそれに従えないサーバは、適切なエラーステータスを返さなければならない。サーバは、その期待{expectations} に一つでも添えない場合は、417 (Expectation Failed) ステータスを返さなければならないが、そのリクエストが他に問題を持つような場合などは、他の 4xx ステータスを返してもよい。

このヘッダフィールドは、将来の拡張によって許される拡張された構文によって定義される。もし、サーバがサポートしていない expectation-extension を含んだ Expect フィールドを持つリクエストを受けた時は、417 (Expectation Failed) ステータスを返さなければならない。

どうやらHttpヘッダーのExpectフィールドには、クラアントが希望する拡張構文が指定できるようです。
※ 「100-continue」は現時点で定められている唯一の拡張構文。

100 (Continue) ステータス (section 10.1.1 参照) は、オリジンサーバがクライアントがリクエストボディを送る前に (リクエストヘッダに基づいた) リクエストを受け入れようとする場合に、リクエストボディを伴ったリクエストメッセージを送る事をクライアントに決めさせるという目的を持つ。いくつかのケースでは、サーバがボディを見る事も無くメッセージを受けつけていない場合にクライアントがボディを送る事は、不適切でひどく効率が悪くなる事がある。

[Studying HTTP] HTTP Status Codeより引用。

そういうこと!

以下のような、やり取りがされるのだと解釈しました。

●Expect:100-continueが扱え、かつ、リクエストヘッダーに問題がない場合
クライアント:「これからこんな感じのデータ送るけど大丈夫?とりあえず、ヘッダーだけ送るので見てみて。」
※ Expect:100-continueを指定。ヘッダーのみを送信。

サーバ:「これなら大丈夫だよ。本文も送って。」

クライアント:「送りまーす。」

●Expect:100-continueは扱えるが、リクエストヘッダーに問題がある場合
クライアント:「これからこんな感じのデータ送るけど大丈夫?とりあえず、ヘッダーだけ送るので見てみて。」
※ Expect:100-continueを指定。ヘッダーのみを送信。

サーバ:「これ無理。処理できないよ。」

クライアント:「了解。じゃあ、本文送るのやめるね。」

●Expect:100-continueが扱えない場合
クライアント:「これからこんな感じのデータ送るけど大丈夫?とりあえず、ヘッダーだけ送るので見てみて。」
※ Expect:100-continueを指定。ヘッダーのみを送信。

サーバ:「いやいや、ヘッダーだけ送られても判断できないよ。」
※ このときHttpステータス417を返す。

クライアント:「あ、そうなんだ。ごめん。」

どうやら、twitterでは、Expect:100-continueを扱えないようです。

では、このエラーを回避するにはどうしたらよいか?
Expect:100-continueをHttpヘッダーに指定しなければ良いのです。

今回、twitterのAPIを呼び出すところでは、

	private HttpClient client;

	public TweetThread(Handler handler, Runnable runnable, TweetModel tmodel, XAuthModel xmodel) {
		this.handler = handler;
		this.runnable = runnable;
		this.tmodel = tmodel;
		this.xmodel = xmodel;
		this.client = new DefaultHttpClient();
	}

のように、「HttpClient」クラスを利用しています。
この場合、デフォルトで指定されるようです。

		client.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);

こうすることで、HttpヘッダーにExpect:100-continueを指定しないようにすることができます。これで、エラーも発生しなくなりました。

未実装だった、Tweetボタンクリック時の処理を実装します。

        btnTweet.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				if (!gmodel.getTranslated().equals("")) {
					tweeting = true;

					if (tmodel == null) {
						tmodel = new TweetModel();
					}
					if (xmodel == null) {
						xmodel = new XAuthModel();
					}
					tmodel.setStatus(gmodel.getTranslated());

					Tweet();
				}
			}

        });

これで、翻訳結果をtweetすることができるようになりました!

tweeted

サンプルソース

2010年7月8日追記
申し訳ありません。上記サンプルソースに一部バグがありました。
twitterの投稿で、単語であれば問題ないのですが、英文のようにスペースが混在している文章を投稿しようとするとエラーになる、というものでした。対応版をアップしましたので、あらためてこちらからどうぞ。