あれとアレは混ぜるな危険

日々精進をしているふり

Future Technology Daysのフォロー

いやいやどうもどうも。健康診断で貧血気味なことが発覚したはるたまです。
10月14日に開催されたFuture Technology Days Windows Azure セミナー with David Chappell 〜クラウドビジネスに踏み出す日〜の中で、マイクロソフト エバンジェリストの砂金(いさご)さんのセッション中にすこしお話しさせていただきました。来場いただいた皆様、おつきあいいただいてありがとうございます。
このときに紹介したWindows AzureのWorker Roleで作るTwitter Botに関してフォローしたいと思います。

まず最初に

Azureアプリケーションの開発に必要な物に関しては、砂金さんがFuture Technology Daysで使ったスライドを公開していますので、そちらを参照してください。
【資料&ソース公開】twitter botをAzureのWorkerロールで動かしてみよう:Azureの鼓動:ITmedia オルタナティブ・ブログ
そして、完成形のソースはここで配布しています。配布場所はもちろんSkyDrive!!
ここで公開しています
このソースのライセンスについては、NYSL(煮るなり焼くなり好きにしろライセンス)で公開させていただきます。好きにやっちゃってください。
Cloud Serviceプロジェクトで作成されていますので、Visual StudioだけでなくWindows Azure Tools for Microsoft Visual StudioWindows Azure Software Development Kitがインストールされてないとソリューションが開けないので注意が必要です。

TwitterのWebAPIの仕様に関しては、ここで入手できます。
Page not found | Twitter Developers
当然のことながら全部英語ですが、日本語に翻訳されているステキな方がいらっしゃるので、導入としてはこちらを参照した方が良いでしょう。
[観] Twitter API 仕様書 (勝手に日本語訳シリーズ)
今回はこの中のupdateメソッドを使って、Twitterにメッセージを投稿します。

Twitterにメッセージを送信する本体

まずはTwitterPost.csの中にTwitterにメッセージを投稿するメソッドが用意されていますので、こちらから解説していきましょう。

    static class TwitterPost
    {
        /// <summary>
        /// twitterにリクエストを投げる本体
        /// </summary>
        /// <param name="message">投稿するメッセージ</param>
        public static void PostMessage(string message)
        {
            //「100 Continue」をサポートしないので、これを設定しないと動かない。
            ServicePointManager.Expect100Continue = false;

            //WebClientを作成
            WebClient client = new WebClient();
            client.Encoding = Encoding.UTF8;
            client.Headers["content-type"] = "application/x-www-form-urlencoded";
            client.Credentials = new NetworkCredential("アカウント名", "パスワード");

            //メッセージを送信
            client.UploadString("http://twitter.com/statuses/update.xml", "POST", "status=" + message);
        }
    }
接続時の動作の設定

一番最初の

ServicePointManager.Expect100Continue = false;

これはTwitterのサーバ側の仕様に関する都合で、これを設定しないと認証に失敗してしまいます。今回の趣旨からは少し外れてしまう話になってしまうので、具体的な動作に関してはMSDNを参照してください。
ServicePointManager.Expect100Continue プロパティ (System.Net)

WebClientを作成

次に、メッセージを送信するためのWebClientを作成します。

            //WebClientを作成
            WebClient client = new WebClient();
            client.Encoding = Encoding.UTF8;
            client.Headers["content-type"] = "application/x-www-form-urlencoded";
            client.Credentials = new NetworkCredential("アカウント名", "パスワード");

TwitterのWebAPIの仕様として、送信するメッセージはUTF-8で送信することが定められているので、Encodingに「UTF-8」を設定します。また、TwitterのupdateメソッドはHTTPのPOSTメソッドで送信しなければいけないと決められているので、content-typeヘッダに「application/x-www-form-urlencoded」を設定し、メッセージ文字列をフォームデータとして送信することを宣言しています。
最後にメッセージを送信するアカウントとパスワードをCredentialsに設定しています。この方法ではベーシック認証(MSDN上の表記では基本認証)を使ってユーザーの認証を行います。現在はこの方法で問題ありませんが、将来的にOAuthという認証方法に移行することがアナウンスされています(いつになるかはよく分かりませんが、多分ずっと先)。まあ、今回はお手軽なベーシック認証で行きましょう。

メッセージを送信

最後に、これまでにいろいろ設定したWebClientを使って、Twitterに対してリクエストを送信します。

            //メッセージを送信
            client.UploadString("http://twitter.com/statuses/update.xml", "POST", "status=" + message);

最初の引数は、メッセージを送るURLを指定します。これは仕様書に書いてあるとおりですね。URLの最後に「.xml」と「.json」のいずれかを選択すると、レスポンスで返ってくる形式が変化します。今回はレスポンスの内容を使わないので、とりあえず「.xml」を指定しています。
2番目の引数はHTTPメソッドを指定します。仕様的にPOSTでないといけないので、仕様通り「POST」を指定しましょう。
3番目の引数は、実際に送信する文字列を指定します。メッセージの内容は「status」という名前で送信しなければならないので「status=[PostMessageメソッドの引数でもらった文字列]」という形式の文字列が送信されなければいけません。

WorkerRole本体から呼び出す

メッセージを送信するメソッドができたので、これをWorkerRoleから呼び出します。WorkerRoleの実体はRoleEntryPointを継承したクラスになりますが、Visual StudioでCloud ServiceプロジェクトテンプレートでWorkerRoleプロジェクトを作成したときに「WorkerRole.cs」という名前で作成されます。
このファイルのStartメソッドがWorkerRoleの作業そのものになりますので、ここを修正していきます。

        public override void Start()
        {
            // This is a sample worker implementation. Replace with your logic.
            RoleManager.WriteToLog("Information", "Worker Process entry point called");

            while (true)
            {
                try
                {
                    //DateTimeで今の時間を持ってくると、Azureに乗せたときに日本の時間は取れてこない。
                    //ロケールも違うから、ToStringのフォーマットも違ってる。
                    string message = DateTime.Now.ToString() + " なう。";
                    TwitterPost.PostMessage(message);
                }
                catch (Exception e)
                {
                    RoleManager.WriteToLog("Error", e.Message);
                }

                RoleManager.WriteToLog("Information", "Working");

                Thread.Sleep(1 * 60 * 1000);
            }
        }

try-catchでサーバに接続できなかったときの例外を処理していたり、Thread.Sleepの間隔を長く変更したりしていますが、基本的には無限ループの中でPostMessageメソッドをたたいているだけです。
あとはこれをVisual Studioからデバッグ実行すれば、ローカルのAzureファブリックでデバッグ実行してくれます。もちろんデバッグ実行なので、ブレークポイントを置けばそこで処理が止まって、変数の中身を見ることができます。

実はコメントにも書いてあるとおり、このコードだとローカルのファブリックで実行した場合と、本物のAzure上にデプロイした場合で、投稿される時間が変わります。
ローカルのファブリックだと、DateTime.Nowで取れる時間は日本時間でToStringした結果も日本の形式です。しかし、Azure上のファブリックでは時間は世界標準時間、ToStringの結果は(おそらく)USの物になって返ってきます。
つまりロケールを意識してコーディングを行わないと、想定している形式で出力がされないという事態が発生してしまいます。このあたりがクラウドっぽいですね。

おしまいに

Windows Azureに関しては、まだドキュメントが出そろってない感じがありますが、こんなお手軽な所から始めてみるのも良いんじゃないでしょうか。とりあえず、正式サービスが始まるまで1ヶ月くらいありますので、この機会に是非どうでしょう?