AsyncOAuth ver.0.7.0 - HttpClient正式版対応とバグ修正

HttpClientがRTMを迎えた、と思ったら、次バージョンのベータが出た、と、展開早くて追いつけないよ~な感じですが、とりあえず正式版のほうを要求する形で、AsyncOAuthも今回からBetaじゃなくなりました。

AsyncOAuthについてはAsyncOAuth - C#用の全プラットフォーム対応の非同期OAuthライブラリを、HttpClientについてはHttpClient詳解、或いはAsyncOAuthのアップデートについてを参照ください。

今回のアップデートなのですが、PCL版HttpClientのRTM対応という他に、バグ修正が二点ほどあります。バグ的には、結構痛いところですね……。

OrderByと文字列について

バグ修正その一として、OAuthの認証シグネチャを作るのにパラメータを並び替える必用があるのですが、その並び順が特定条件の時に狂っていました。狂っている結果、正しく認証できないので、実行が必ず失敗します。特定条件というのは、パラメータ名が大文字始まりと小文字始まりが混在するときです。例えばhogeとHugaとか。本当はHuga-hogeにならなければならないのに、hoge-Hugaの順序になってしまっていたのでした。

なぜそうなったか、というと、OrderByをデフォルトで使っていたからです。

// charの配列をOrderByで並び替えると、ASCIIコード順 = 65:'A', 97:'a'
foreach (var item in new[] { 'a', 'A' }.OrderBy(x => x))
{
    Console.WriteLine((int)item + ":" + (char)item);
}

// stringの配列をOrderByで並び替えると、良い感じ順 = "a", "A"
foreach (var item in new[] { "a", "A" }.OrderBy(x => x))
{
    Console.WriteLine(item);
}

良い感じ順!というのはなにかというと、StringComparison/Comparer.CurrentCultureの順番です。ほぅ……。ありがたいような迷惑のような。で、今回はASCIIコードにきっちり従って欲しいので、

// 実際のコード。
// ところでrealmのところは!x.Key.Equals("realm", StringComparison.OrdinalIgnoreCase) って書くべき、次のバージョンで直します
var stringParameter = parameters
    .Where(x => x.Key.ToLower() != "realm")
    .Concat(queryParams)
    .OrderBy(p => p.Key, StringComparer.Ordinal)
    .ThenBy(p => p.Value, StringComparer.Ordinal)
    .Select(p => p.Key.UrlEncode() + "=" + p.Value.UrlEncode())
    .ToString("&");

といったように、StringComparer.OrdinalをOrderBy/ThenByに渡してあげることで解決しました。メデタシメデタシ。文字列の比較とか、言われてみれば基本中の基本っっっ!なのですけれど、完全に失念してました。テストでも、パラメータ小文字ばっかだったりして問題が中々表面化しないんですね、言い訳ですけれど……。しかし、幾つかのライブラリ見てみると、ほとんどがこれの対応できてなかったので私だけじゃないもん!みたいな、うう、情けないのでやめておきましょう。OAuthBase.csもissueには上がってましたがマージされてないんで、同様の問題抱えてるのですねえ。さすがにDotNetOpenAuthはきっちりできていました。

UriクラスとQueryとエスケープについて

バグ修正その2。GETにパラメータをくっつける、つまりクエリストリングとして並べた時に、日本語などURLエンコードが必要な物が混ざっている時に必ず認証に失敗しました。エンコード周りということで、お察しの通り二重エンコードが原因です。

これに関しては、私のUriクラスへの認識が甘々だったのがマズかったです。ぶっちけ、ただのstringを包んだだけの面倒臭い代物とか思ってたりとかしたりとか……。いや、さすがにそこまでではないんですが、まぁしかし甘々でした。こっちは↑のに比べても本当に初歩ミスすぎて穴掘って埋まりたいですぅー。相当恥ずかしい。

// クエリストリングつけたものを投げるとして
var uri = new Uri("http://google.co.jp/serach?q=つくば");

// QueryはURLエンコードされてる => ?q=%E3%81%A4%E3%81%8F%E3%81%B0
Console.WriteLine(uri.Query);

// URLエンコードされてるのを渡したとして
uri = new Uri("http://google.co.jp/serach?q=%E3%81%A4%E3%81%8F%E3%81%B0");

// ToString結果はURLデコードされたものがでてくる => http://google.co.jp/serach?q=つくば
Console.WriteLine(uri.ToString());

というわけで、Queryは常にURLエンコードされるし、ToStringは常にURLデコードされてる。この辺の認識がフワフワッとしてると、エンコードやデコード結果が非常にアレになっちゃうんですね……。

// 元データが欲しい時はOriginalString
Console.WriteLine(uri.OriginalString);

// エンコードされないで取るにはGetComponentsで指定してあげる
// ちなみにQueryは (UriComponents.Query | UriComponents.KeepDelimiter, UriFormat.Escaped) と等しい
var unescaped = uri.GetComponents(UriComponents.Query, UriFormat.Unescaped);
Console.WriteLine(unescaped); // q=つくば

GetComponents大事大事。

Next

今回からBetaを取ったということで、1.0に上げようかなあ、とか思ったんですが、↑のように手痛いバグが残っていたことが発覚したりなので、まだもう少し様子見といった感。さすがにもう大丈夫なはず!といくら思っても見つかっていくわけで、枯れてるって本当に大事ですねえ。AsyncOAuthも枯れたライブラリになるよう、頑張ります。今回のバグとかもユーザーが伝えてくれるお陰なので足を向けて寝られません。

私自身が使ってるのか?というと、答えは、使っています!こないだに.NET最先端技術によるハイパフォーマンスウェブアプリケーションというセッションを行ない、そこで言及したように、現在、某ソーシャルゲームをC#で再構築中です。で、GREEにせよMobageにせよ、ソーシャルゲームってOpenSocialの仕組みに乗っかっているのですが、その認証がOAuthなのです。というわけで、めっちゃくちゃヘヴィーに使っています。(認証の形態がちょっと違うので幾つかメソッドが足されているのと、Shift-JIS対応とかのためのカスタムバージョンだったりはしますが…… その辺はAsyncOAuth本体にも載せるか検討中)。実戦投下まであとちょっとってところですが、それできっちり動ききれば、十分に枯れた、といえるのではないかと思っています。その時までは0.xで。でも現状でもかなり大丈夫だとは思いますので、是非是非使ってください。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2023

Twitter:@neuecc GitHub:neuecc

Archive