ReactiveOAuth ver.0.4 - Twitpic(OAuth Echo)対応
- 2011-06-23
ver.0.4になりました。少し前に0.3.0.1をこっそり出していたので、それを含めて0.3からの差分は、「対象Rxのバージョンが現在最新の1.0.10605(Stable)」に、というのと「Realmが含まれていると認証が正しく生成出来なかったバグの修正」と、「TwitpicClientサンプルの追加」になります。バグのほうは本当にすみません……。Twitterでしかテストしてない&TwitterはRealm使わないため、全然気づいていなくて。ダメですねホント。
OAuth Echo
TwitpicはOAuth Echoという仕組みでTwitterと連携した認証をして、画像を投稿できます。詳しくはUsing OAuth Echo | dev.twitter.comやTwitPic Developers - API Documentation - API v2 » uploadにありますが、よくわかりませんね!Twitpicに画像を投稿、というわけでTwitpicのAPIにアクセスするわけですが、その際のヘッダにTwitterに認証するためのOAuthのヘッダを付けておくと、Twitpic側がTwitterに問い合せて認証を行う。という仕組みです、大雑把に言って。
ただのOAuthとはちょっと違うので、今までのReactiveOAuthのOAuthClientクラスは使えない。けれど、認証用ヘッダの生成は同じように作る。というわけで、ここはReactiveOAuthにひっそり用意されているOAuthBaseクラスを継承して、Twitpic専用のTwitpicClientクラスを作りましょう。
が、作るのもまた少し面倒なので Sample/TwitpicClient/TwitpicClient.cs に作成したのを置いておきました。ファイルごとコピペってご自由にお使いください。.NET 4 Client Profile, Silverlight 4, Windows Phone 7の全てに対応しています。
Windows Phone 7でのカメラ撮影+投稿のサンプル
TwitpicClient.cs の解説は後でやりますが、その前に利用例を。WP7でカメラ撮影+投稿をしてみます。CameraCaptureTaskの利用法に関しては CameraCaptureTaskを使ってカメラで静止画撮影を行う – CH3COOH(酢酸)の実験室 を参考にさせて頂きました。TwitterのAccessTokenの取得に関しては、ここでは解説しませんので neue cc - ReactiveOAuth - Windows Phone 7対応のOAuthライブラリ を参照ください。
// CameraCaptureTaskのCompletedイベント
void camera_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
// 撮影画像(Stream)をバイト配列に格納
var stream = e.ChosenPhoto;
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
// key, secret, tokenは別に設定・取得しておいてね
new TwitpicClient(ConsumerKey, ConsumerSecret, accessToken)
.UploadPicture(e.OriginalFileName, "from WP7!", buffer)
.ObserveOnDispatcher()
.Catch((WebException ex) =>
{
MessageBox.Show(new StreamReader(ex.Response.GetResponseStream()).ReadToEnd());
return Observable.Empty<string>();
})
.Subscribe(s => MessageBox.Show(s), ex => MessageBox.Show(ex.ToString()));
}
}
new TwitpicClient(キー, シークレット, アクセストークン).UploadPicture(ファイル名, メッセージ, 画像) といった風に使います。戻り値はIObservable<string>で結果(投稿後のURLとか)が返ってくるので、あとは好きなように。投稿に失敗した場合は、WebExceptionが投げられるので、それを捉えてエラーメッセージを読み取ると開発には楽になれそうです。
TwitpicClient.cs
以下ソース。Sample/TwitpicClient/TwitpicClient.cs と同じですが、自由にコピペって使ってください。大事なことなので2回言いました。このコード自体はTwitpicに特化してありますが、認証部分のヘッダを少しと画像アップロードを変更する部分を弄れば、他のOAuth Echoサービスにも対応させることができると思います。
using System;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
#if WINDOWS_PHONE
using Microsoft.Phone.Reactive;
#else
using System.Reactive.Linq;
#endif
namespace Codeplex.OAuth
{
public class TwitpicClient : OAuthBase
{
const string ApiKey = ""; // set your apikey
readonly AccessToken accessToken;
public TwitpicClient(string consumerKey, string consumerSecret, AccessToken accessToken)
: base(consumerKey, consumerSecret)
{
this.accessToken = accessToken;
}
private WebRequest CreateRequest(string url)
{
const string ServiceProvider = "https://api.twitter.com/1/account/verify_credentials.json";
const string Realm = "http://api.twitter.com/";
var req = WebRequest.Create(url);
// generate oauth signature and parameters
var parameters = ConstructBasicParameters(ServiceProvider, MethodType.Get, accessToken);
// make auth header string
var authHeader = BuildAuthorizationHeader(new[] { new Parameter("Realm", Realm) }.Concat(parameters));
// set authenticate headers
req.Headers["X-Verify-Credentials-Authorization"] = authHeader;
req.Headers["X-Auth-Service-Provider"] = ServiceProvider;
return req;
}
public IObservable<string> UploadPicture(string filename, string message, byte[] file)
{
var req = CreateRequest("http://api.twitpic.com/2/upload.xml"); // choose xml or json
req.Method = "POST";
var boundaryKey = Guid.NewGuid().ToString();
var boundary = "--" + boundaryKey;
req.ContentType = "multipart/form-data; boundary=" + boundaryKey;
return Observable.Defer(() =>
Observable.FromAsyncPattern<Stream>(req.BeginGetRequestStream, req.EndGetRequestStream)())
.Do(stream =>
{
using (stream)
using (var sw = new StreamWriter(stream, new UTF8Encoding(false)))
{
sw.WriteLine(boundary);
sw.WriteLine("Content-Disposition: form-data; name=\"key\"");
sw.WriteLine();
sw.WriteLine(ApiKey);
sw.WriteLine(boundary);
sw.WriteLine("Content-Disposition: form-data; name=\"message\"");
sw.WriteLine();
sw.WriteLine(message);
sw.WriteLine(boundary);
sw.WriteLine("Content-Disposition: form-data; name=\"media\"; filename=\"" + filename + "\"");
sw.WriteLine("Content-Type: application/octet-stream");
sw.WriteLine("Content-Transfer-Encoding: binary");
sw.WriteLine();
sw.Flush();
stream.Write(file, 0, file.Length);
stream.Flush();
sw.WriteLine();
sw.WriteLine("--" + boundaryKey + "--");
sw.Flush();
}
})
.SelectMany(_ => Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)())
.Select(res =>
{
using (res)
using (var stream = res.GetResponseStream())
using (var sr = new StreamReader(stream, Encoding.UTF8))
{
return sr.ReadToEnd();
}
});
}
}
}
認証ヘッダ作成はConstructBasicParametersとBuildAuthorizationHeaderというprotectedメソッドで行います。わけわかんないよね…気持ち悪いよね…。使いにくいメソッドです、すみません、私もそう思います。そういうものだと思って、見ないふりしてもらえれば幸いです。
コードの大半を占めているのは画像を投稿するためのmultipart/form-dataのもので、これはもうOAuth Echo関係ない話、で、面倒ぃ。特にWP7での非同期だと涙が出る。POSTはBeginGetRequestStreamとBeginGetResponseの二つの非同期メソッドをセットで使う必要があるため、コードがごちゃごちゃするのです。
しかしReactive Extensionsを使えばあら不思議!でもないですが、ネストがなくなって完全に平らなので、結構普通に読めるのではないでしょうか?(ストリーム書き込みのコード量が多いのは、これは同期でやっても同じ話なので)。例外処理も利用例のところで見たように、Catchメソッドをくっつけるだけ。実に色々とスッキリします。
Rxがあれば非同期POSTも怖くない。
やっていることは単純で、FromAsyncPatternでBegin-Endを変換。StreamへのWriteは後続への射影はなく、対象(Stream)に対しての副作用(書き込み)のみなのでDo、RequestStream->Responseへの切り替えはSelectMany、Responseから結果のStringへの変換はSelect、と、お決まりの定型メソッドに置き換えていっただけです。この辺はパターンみたいなものなので、これやるにはこのメソッドね、というのを覚えてしまえばそれでお終いです。
Stream読み書きは非同期にしないの?
StreamにもBeginReadとかBeginWriteとかありますものね。しかし、しません(キリッ。理由は死ぬほど面倒だからです。やってみると分かりますが想像以上に大変で、おまけに何とか実現するためにはRxでのチェーンを大量に重ねる必要がありオーバーヘッドがバカにならない……。なので、わざわざやるメリットも全くありません。
一応、ReactiveOAuthのOAuthClientは、そこも非同期でやってますが、わざわざ頑張った意味があったかは、かなり微妙なところ。実装は Internal/AsynchronousExtensions.cs にあるので参照ください。それと、この AsynchronousExtensions.cs はReactive Extensionsで非同期処理を簡単にで言った「拡張メソッドのすゝめ」を実践したものでもあります。WebRequestはプリミティブすぎて扱い難いので、Rxに特化したうえで簡単に扱えるようにDownloadStringやUploadValueなどといったメソッドを拡張してあります。便利だと思いますので、こちらも TwitpicClient.cs と同様に、ファイルごと自由にコピペって使ってやってください。
まとめ
ReactiveOAuthを公開する目的に、「これが入り口になってRxの世界を知ってもらえると嬉しい」というのもあったのですが、WP7開発で利用してもらったりと、その目的は少しは達成出来たかもで、良かった良かった。ちょっと練りたりなかったり、未だにバグがあったり(本当にごめんなさい!)と至らない点も多いですが、今後も改善していきますのでよろしくお願いします。