ページ

2011年1月26日

MS Exchangeからスケジュールを抜き出すんだぜ

はい、MSラブなんですが、まだ、誰にもわかってもらえません。仕方ないので、昨日の続きで、MS Exchangeから指定したカレンダーの予定をAPIを使って抜き出します。こちらは、昨日の仕組みよりはちゃんとドキュメントになっているものが多いです。いやいや、単に、Exchange Sevice BindingとExchange Serviceの関連性のドキュメントがないだけかもしれません。それでは大好きなC++++じゃなくって、C#を使って早速はじめます。

まずは、初期化コード。昨日は、ExchangeServiceBindingを使いましたが、今回はExchangeServiceを使います。ややこしや。
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
 // service.AutodiscoverUrl("メールアドレス");
service.Url = new Uri("https://サーバ名/EWS/Exchange.asmx");
                
service.Credentials = new WebCredentials("メールアドレス", "パスワード");

メールアドレスを指定すればAutoDiscoverUrlでExchangeサーバを勝手に見つけてきてくれるはずなんですが、会社ではちゃんと動いてくれません。なので、service.Urlでサーバを直接しています。そのあとに、service.Credentialsを指定します。ただ、このタイミングでログインするわけではなくって、APIを通してサーバにアクセスしたときにユーザIDとパスワードがバリデーションされます。まあ、バックエンドはSoapなので、リクエストごとにXMLの中にユーザIDとパスワードが乗るだけですが。

では次に、今月分のスケジュールの情報をコンソールに出力します。
private void PrintSchedule(ExchangeService service, FolderID fid) {
    DateTime dtStart = DateTime.Today;
    dtStart = dtStart.AddDays(-dtStart.Day+1);
    DateTime dtEnd = dtStart.AddMonths(1);

    CalendarFolder calFolder = CalendarFolder.Bind(service, fid);
    CalendarView calView = new CalendarView(dtStart, dtEnd);
    FindItemsResults findResults = calFolder.FindAppointments(calView);
    foreach (Appointment appointment in findResults.Items) {
        Console.WriteLine("========================================");
        Console.WriteLine(appointment.ICalUid);
        Console.WriteLine(appointment.Id);
        Console.WriteLine(appointment.Subject);
        Console.WriteLine("Start: " + appointment.Start);
        Console.WriteLine("End  : " + appointment.End);
        Console.WriteLine("Mod  : " + appointment.LastModifiedTime);
    }
}

ほとんどがお作法なので、難しいところは何もないです。全期間を指定してフェッチすれば繰り返しの予定は、繰り返しとして取得できます。期間を指定すれば、繰り返しは展開されて取得できます。繰り返しの予定のハンドリングは大変なので、展開してくれているほうがうれしいです。
FolderIDはカレンダーのIDですが、昨日の方法で取得したID(文字列)を指定します。FolderID fid = "昨日の文字列EwsID";でFolderIDになります。

では、実行すると

========================================
e040000008200E00074C5B7101A82E008000000002AE35E2725B6CB01000000000000000010000
0000D3A097CABC86A4C86FB3B5F9E463744-
AAMkADI0Zjk2MGQ0LWEyZWYtNGViNC04NGU1LWYyOTgyNmI3YjMwYQBGAAAAAABhWrJE2t3vTIVm7PHG
9TnUBwCtvtb1WlXZTJEDqdu9EE7RAAAMYnVIAACtvtb1WlXZTJEDqdu9EE7RAAAMYplxAAA=
じろう
Start: 2011/01/17 18:30:00
End  : 2011/01/17 19:30:00
Mod  : 2011/01/17 18:01:40
最初のIDがical用のIDです。その下がEwsIDというやつです。なが~~~

でわでわ。

2011年1月25日

MS ExchangeのメールボックスのカレンダーのIDをAPIで取得する

MS大好きなんですが、周りの人にはそのことが理解してもらえず、つくづく日本人にとっては愛情表現は難しいな、と思う今日この頃です。MSラブということで、MS Exchangeでのお仕事が来ましたが、そのときのメモです。今回はMS Exchangeで指定したユーザのメールボックスのカレンダーIDを取得するという、お題です。言語はもちろんC#です。そうです。C++++ですね。

MS ExchangeをAPIで操作するにはいくつか方法があります。Exchange 2000以降はCOM APIが用意されています。Exchange 2007 SP1以降はCOM APIに加えてEWSというSoapベースの仕組みが用意されています。EWSは直接SOAPをさわることもできますが、ライブラリにもなっているので、より簡便にあつかえます。ただし、この二つの仕組みでは指定したユーザのメールボックスのカレンダーのIDは取得できそうにありません。

ややこしいことにExchangeはもうひとつ、SoapベースのAPIを用意しています。Visual C#でWeb参照でhttps://${servername}/ews/Services.wsdlを指定すると利用できるようになります。Visual Studio素敵です。

まずは、初期化コードです。

ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;

ExchangeServiceBinding esb = new ExchangeServiceBinding();
esb.Url = @"https://サーバ名/EWS/Exchange.asmx";
esb.Credentials = new NetworkCredential("ログインID", "パスワード");
esb.RequestServerVersionValue = new RequestServerVersion();
esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1;
最初のServicePointManagerはサーバにちゃんとしたSSL証明書をもっていないので、証明書の検証をスキップしています。esbのオブジェクトを作った後は、適当なユーザでログインします。サーバのバージョンを指定しないと、のちのちの作業で悲しいことになるので、指定します。これで初期化は終了。

それでは実際のIDを取得します。コードはこんな感じ。
private String GetCalendarId(ExchangeServiceBinding esb, String mail) {
 FindFolderType fit = new FindFolderType();
 fit.FolderShape = new FolderResponseShapeType { BaseShape = DefaultShapeNamesType.AllProperties };
 fit.ParentFolderIds = new DistinguishedFolderIdType[] {
     new DistinguishedFolderIdType {
         Mailbox = new EmailAddressType{ EmailAddress=mail},
         Id = DistinguishedFolderIdNameType.msgfolderroot
     }
 };
 fit.Traversal = FolderQueryTraversalType.Shallow;
 FindFolderResponseType firt = esb.FindFolder(fit);
 FindFolderResponseMessageType firmt = (FindFolderResponseMessageType)firt.ResponseMessages.Items[0];
 foreach (BaseFolderType ft in firmt.RootFolder.Folders) {
     if (ft.FolderClass != null) {
         String fts = ft.FolderClass.ToString();
         if (ft.FolderClass.ToString().Equals("IPF.Appointment")) {
             String fid = ft.FolderId.Id;
             return fid;
         }
     }
 }
     
 return null;
}

エラー処理はちょっとサボっています。フォルダのタイプを見分ける仕組みはFolderClassの名前を使いましたが、別のタイプのフォルダを取得する場合はFolderClass名がかぶることもあるので、別のフィールド値(?)を使わないといけません。まあ、実際に動かせば何とかなりそうですね。これで、EwsIDという形のIDが取得できます。Exchangeのややこしいところは、IDはいろいろ種類があって、EwsLegacyIDとか、OwsIDとか、それ以外にも数種類あります。
ややこしや~。

でわでわ

2011年1月18日

AMQPのお勉強 その2

前回の続きです。


AMQPの構成要素

  • メッセージブローカー:AMQPクライアントが接続するサーバ(サーバはクラスタ構成にできるが仕様書は規定しない)
    • 交換(Exchange)とキューで構成
  • ユーザ: IDとパスワードでブローカーに認証して接続するもの。
  • コネクション: TCP/IPなどを使っての物理的な接続。各コネクションはユーザと結びつけられる。
  • チャネル: 論理的なコネクション。
    • ステートフル
    • 各コネクションでの並列実行する時は、スレッドごとに別のチャネルで操作


  • メッセージを送受信するエンティティはチャネル上に宣言
    • 一度宣言したエンティティを別のプロパティに変更しようとするとエラー
    • プロパティの変更は一度削除してから
  • エンティティは名前付き(オプション)
    • 名前はエンティティとブローカの中でユニーク
    • 利用可能な名前付きエンティティのリストを取得できない
    • エンティティ名についての知識は、そのエンティティ上でできる操作も定義している
      • アプリケーションはそれを知っているはず
      • 名前はUTF-8で1から255文字(バイト?)
      • 数字か文字か、アンダースコアーで始まること


交換(Exchange)

  • メッセージの送信先を制御するエンティティ
  • 名前とプロパティ付き
  • プロパティ
    • durable: ブローカーを再起動しても永続化されている
    • auto-delete: キューがなくなったら破棄される。
    • passive: よくわからん。すでにあればそれを使うけど、なければエラーにする?

キュー

  • メッセージの受信先エンティティ
  • 名前とプロパティ付き
  • クライアントがキューを購読すると、
    • ブローカーがキューにあるメッセージをクライアントに配信
    • クライアントがキューにあるメッセージをポーリング
    • キューはFIFOで配信
  • プロパティ
    • alternate-exchange: メッセージがクライアントに拒否されたり、キューが削除されたときに、メッセージをexchaneに戻す。
    • passive: よくわからん。キューが存在していればそれをつかって、なければエラー?
    • durable: ブローカーを再起度すいてもキューが永続化されている
    • exclusive: 特定のキューに対してクライアントは一つだけ
    • auto-delete: クライアントがいなければキューを自動削除
      • キューがauto-deleteの時は、exchageの時もauto-delete
  • AMQP1.0でキューがexchangeと統合されるらしい?

メッセージ

  • exchangeに発行して購読しているクライアントに配信される実体
  • ヘッダーとコンテントで構成
  • ヘッダープロパティ
    • routing-key: exchnageのタイプに応じて使用する
    • immediate: クライアントが一つも購読していないキューが一つでもあれば、転送できない
    • delivery-mode: メッセージを永続化して確実に配送させる
    • priority: 0から9でメッセージ配信の優先度を指定
    • expiration: メッセージの配送をあきらめるまでの時間(ミリ秒)

バインディング

  • exchangeからキューにメッセージが流れる方法を定義したもの
  • exchangeで使われるルーティングアルゴリズムの指定
  • でも1.0ではexchangeとキューが一つになるので気を付けてね
  • 条件に応じて配信するキューを制御しようとしていた
    • でも条件が多くなりすぎて、フローが急激に複雑になりすぎた
ルーティングパターン

  • direct: メッセージのルーティングキーとバインディングのキーが同一の場合
  • fanout: キーなくても常にマッチ
  • topic: メッセージのルーティングキープロパティとバインディングキーがマッチする場合
  • words: ワードはドットで区切られた文字列。
    • *: 一単語にマッチ
    • #: 0からN単語にマッチ。

AMQPの製品

  • OpenAMQ
  • StormMQ
  • RabbitMQ
  • Apache Qpid (ApacheプロジェクトはMQ関係の製品を二つも持っているのか・・・)

AMQPの敵

  • Storm: テキストベースのpubsubプロトコル
  • RestMS:AMQPの接続形態をHTTPベースにした感じ
  • XMPP: Extensible Messaging and Presence Protocol。JabberとかのIMで。XMLは・・・
    • AMQPとXMPPとの比較はそのうち(とっても似ているらしい)


2011年1月17日

AMQPっとは? その1

会社の中でAMQPを知らない人も多かったので、まとめ。

AMQPとは

  • Advanced Message Queue Protocol
  • メッセージングシステムのオープンな標準仕様

歴史
  • 2004年中頃から2006年中頃後にJPMorgan ChaseとiMatixがC/C++とJavaで開発
  • プロトコルを仕様書として作成
  • RedHat, Cisco, TWIST, IONAとiMatrixがワーキンググループ作成
  • 2006年6月0.8
  • 2006年12月0.9
  • 2008年11月0.9.1
  • 2010年5月1.0ドラフト
    • いろいろ問題があったので1.0は交換とバインディングのコンセプトを変えるらしい

特徴
  • メッセージ指向
  • キューイング
  • ルーティング
    • ポイント間
    • pubsub
  • 信頼性
  • セキュリティ
  • 複数言語間、ベンダー間での相互運用
    • APIは定義しない
    • プロトコル(データフォーマット)を指定

メッセージパターン
  • pubsub: 配信条件に応じて複数の送信先にメッセージ配信
  • round-robin: タスクを複数のクライアントに配信
    • 例: タスクをround-robinで分散処理させて、pubsubでタスクの結果をフィードバック

仕様書の定義
  • クライアントとサーバ(ブローカー)間のバイナリプロトコル
  • 実装すべきサーバのキューイングモデルとサービス概要

ブローカーの機能
  • メッセージキューイング (メッセージの保存)
  • メッセージ交換
    • 1対1
    • 1対N
    • 1対Nの中の1(タスクの分散とか)

バインディング
  • メッセージのルーティングルール

メッセージの構成
  • ルーティング、アプリケーションが使用するプロパティ
  • コンテンツ(バイナリ)

メッセージのルーティング
  • Basic.Publish, Basic.Deleverコマンドでブローカとクライアント間をメッセージが流れる
    • コマンドの実行は非同期
  • Basic-Get/Get-Okコマンドは同期処理

例外
  • ブローカに例外メッセージの処理方法を指定。
(つづく)

2011年1月14日

初めてのRabbitMQその1

メッセージングキューがなぜか続きます。今回はSpringSourceに捨てられたプロダクト、RabbitMQです。昨日のZeroMQとは違うんです。ちゃんとした(?)AMQPなんです。AMQPの標準に準拠した高可用性、高スケーラビリティ、ポータブルなメッセージングシステムらしいです。ただ、このうたい文句の検証は大変なのでやりませんん。小人さん、お願いします。

僕の手元にはMacしかないので、Mac上で遊んでみます。まずは、RabbitMQのインストールはhomebrewから(MacPortsやめました)。brew install rabbitmqです。インストールが終わるまではお茶を飲みながらゆっくり待ちましょう。それから、Python用のモジュールをインストールします。amqpなのでamqplibです。pip install amqplibで終わりです、

それではrabbitmqを起動します。rabbitmq-serverで起動します。運用環境ではRabbitMQのユーザとパスワードを設定してguestでのログインはできなくするのがお作法らしいですが、テスト用なので余分な作業はしません。これでしばらく待てばRabbitMQが起動します。うさぎ年らしくていいですね。(意味不明)

まあ今回も、一斉配信ですね。amqplibのdemoのコードをスリムにしただけです。
import amqplib.client_0_8 as amqp

def callback(msg):
    for key, val in msg.properties.items():
        print '%s: %s' % (key, str(val))
    for key, val in msg.delivery_info.items():
        print '> %s: %s' % (key, str(val))

    print ''
    print msg.body
    print '-------'
    msg.channel.basic_ack(msg.delivery_tag)

    #
    # Cancel this callback
    #
    if msg.body == 'quit':
        msg.channel.basic_cancel(msg.consumer_tag)


def main():
    conn = amqp.Connection()
    ch = conn.channel()
    ch.access_request('/data', active=True, read=True)

    ch.exchange_declare('myfan', 'fanout', auto_delete=True)
    qname, _, _ = ch.queue_declare()
    ch.queue_bind(qname, 'myfan')
    ch.basic_consume(qname, callback=callback)

    #
    # Loop as long as the channel has callbacks registered
    #
    while ch.callbacks:
        ch.wait()

    ch.close()
    conn.close()

if __name__ == '__main__':
    main()

この人を起動して待ち受けます。送信側は

import sys
import amqplib.client_0_8 as amqp

def main():
    msg_body = ' '.join(sys.argv[1:])

    conn = amqp.Connection()
    ch = conn.channel()
    ch.access_request('/data', active=True, write=True)

    ch.exchange_declare('myfan', 'fanout', auto_delete=True)

    msg = amqp.Message(msg_body, content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'})

    ch.basic_publish(msg, 'myfan')

    ch.close()
    conn.close()

if __name__ == '__main__':
    main()

短いです。コマンドラインの引数に送信するメッセージを書いて起動します。
ZeroMQが相手ノードを指定して自分でルーティングルートを制御するのに対して、チャネルのキーに対して制御しているみたいです。
うーん、RabbitMQはZeroMQと違ってサーバが必要になりますが、コードは書きやすそう。少なくともノードの制御を気にいしなくていいのがいい。

amqp.Connectionで接続先のサーバを指定できますが(デフォルトはlocalhost)、サーバが落ちていたり、接続中にサーバが落ちたらどうやって別のサーバにつなぎ替えるんだろう?自分でやるのかな・・・

2011年1月13日

はじめてのZeroMQ その1

MQ関係は「遅い!機能過多!」というイメージがあって敬遠していました。最近、もう一度調べ直しています。その中でZeroMQと言う変なのがいるらしい、と言うことで、しばらくはZeroMQ関係のお話です。ざっとドキュメントを読んだ感想は、MQと言うより、ネットワークをもう少し抽象化して、ユニキャスト、ブロードキャストを簡易に使えるようにしている、と言うものです。そのため、APIもとても薄くローレベルなものです。僕はこれくらいのものが好きだったりします。
トップページには次のように特徴を書いています。


  • 並列フレームワークとして振る舞うソケットライブラリ
  • プロセス間, IPC, TCP, マルチキャストを横断的にメッセージ伝搬
  • fanout, pubsub, pipeline, request-replyによるN対N接続
  • クラスタ製品やスーパーコンピューティングとして使える十分なスピード
  • スケーラブルなマルチコアでメッセージ送受信アプリ用の非同期IO
  • コミュニティはでかくて活発だよ
  • C, C++, Java, .NET, Pythonなど20以上の言語バインディングあるよ
  • Linux, Windows, OS XなどたいていのOSで動くよ
  • LGPLだから商用利用無問題

と書いています。とっても速いし、ローベルなことは大体できるということでしょうか。そうそう、それから、MQ用の専用のサーバがいらないのもメリットの一つです。

サンプルコードを見ながら、いろいろ試してみます。全部は一度には大変なので、pubsub関係から。pubsubはpublisherが接続しているsubscriberに一斉にデータを配信するものです。
publisherのコードは次。言語はPythonですが、C/C++ライブラリのラッパーなので、どの言語でも大差ないでしょう。
import time
import zmq

def main(count):
    ctx = zmq.Context()
    sock = ctx.socket(zmq.PUB)
    sock.bind("tcp://*:5555")
    print "Waiting for clinets..."
    time.sleep(0.1)
    print "Starting broadcast..."
    for i in range(count):
        sock.send_pyobj("HELLO %d" % i)
    
    print "Wait for a while"
    time.sleep(1)
    print "Done"

if __name__ == "__main__":
    import sys

    main(int(sys.argv[1]))

最後のsleepは別スレッドでメッセージの送受信を行うので、それを待っているもの。
subscriber側は
import time
import zmq

def main(count):
    ctx = zmq.Context()
    sock = ctx.socket(zmq.SUB)
    sock.connect("tcp://localhost:5555")
    sock.setsockopt(zmq.SUBSCRIBE, "")
    print "recieving broadcast..."
    for i in range(count):
        val = sock.recv_pyobj()
        print val
    print "done"
        
if __name__ == "__main__":
    import sys

    main(int(sys.argv[1]))

zmq.SUBで作ったsocketオブジェクトにし対してsock.bindするとエラーで怒られました。Subscriberはpublisherに接続するしか生きる意味がないらしいです。

さて、これでpublisher側を起動してからsubscriberを起動するのが普通の流れですが、ZeroMQってsubscriberを起動してからpublisherを起動しても動作します。定期的にpublisherに接続を試みているようです。publisherのWait for clients...の後のtime.sleepの時間ですが、0.1秒だとsubscriberを先に起動しておいてもメッセージの送受信ができますが、0.01秒だと送受信できませんでした。publisherの存在チェックが0.1秒間隔ぐらいなんでしょうか?

多段階のルーティングはできないかやってみましたが、無理そうです。どうしてもやりたければ自分でルーティングしないといけません。

ノードの管理は自前でやらないといけないのは、ちょっと大変だな〜、という感じがします。

でわでわ

追記
======

これはよくまとまっているので、その2はなし。

2011年1月12日

golangのWebsocketクライアント

Pythonの人たちには黙っていましたが、僕はgoが好きです。ごめんなさい、ごめんなさい。さて、PythonでWebsocketのクライアントを先週書きました。正確には年末にちょこちょこって書いて、年明けにちょびっと修正しただけです。まあ、そんなことはいいのですが、僕がWebsocketのチャットサーバのサンプルを書いたのがgoでした。そのときは、多分クライアントの実装はなかったと思うのですが、あれから一年たったのでクライアントも実装されているに違いないと信じて見てみました。ありました。ほら
API自体は僕の書いたものと同じで、Read/Writeの低レベルなものです。上のページにはサンプルコードが載っています。こんな感じです(そのままだと動かないので修正しています)。ChatのサーバはPythonで以前書いたものです。
package main

import (
 "websocket"
    "bytes"
 "fmt"
)

func main() {
  ws, err := websocket.Dial("ws://localhost:5000/chat", "", "http://localhost:5000/");
  if err != nil {
  panic("Dial: " + err.String())
 }
 if _, err := ws.Write([]byte("hello, world!\n")); err != nil {
  panic("Write: " + err.String())
 }
 var msg = make([]byte, 512);
 if n, err := ws.Read(msg); err != nil {
  panic("Read: " + err.String())
 } else {
  var b = bytes.NewBuffer(msg[0:n])
  fmt.Printf(b.String())
 }
}

goだと、クライアントはDialなんですね。なかなか素敵です。Dialの第2引数がプロトコルを指定するみたいなんですが、何なんでしょう?URLでプロトコルは分かると思います。第3引数はoriginです。僕の書いたPythonのクライアントはoriginはurlから決めていました。originは外部から与えないといけないのかな?wssの場合はhttpsでwsの場合はhttpでいいような気がするし(自分で書いたものはプロトコル切り替えていない・・・)、originとurlのホスト名とかがずれることがあるのか、よく分かりません。そもそも、originが何のために必要なのかもよく分かっていないのですが・・・。
Websocketは何気によく分からないことが多いです。

2011年1月6日

Python用のWebSocketのクライアント作った

数日前にpypiには登録していたのですが、もうちょっと機能を追加してPython用のWebSocketのクライアントを作りました。githubにいます。pypiはこっち。pip install websocket-clientでインストールできます。プロトコルが変わるとかいろいろありそうですが、まあ、WebSocketはいろいろ変遷してきたので、今更驚かされません。

作った理由は、うーん、何でしょう?何となく書いてみました、という感じです。WebSocketのサーバのテストとか、Pythonのアプリで接続を維持したまま、Webサーバとお話するとか・・・。この前作った、WebSocketのデータを複数サーバでリレーさせるときの通信手段とか・・・。Webサーバでクライアント・サーバのころのような通信ができるようになるというのは、ブラウザを抜きにしてもすごいことだと思うんですね。

今、サポートしているのは、hybi00(hixie76)です。ちょっと古い実装だとhixie75しかサポートしていないサーバもいるらしいので、そういう人はばっさり捨てましょう。hybi00はSafariの今の最新版、iOS4.2のMobileSafari、Chromeがサポートしています。

使い方は、githubのページを見てください。まあ、こんな感じ

from websocket import create_connection
ws = create_connection("ws://localhost:5000/echo")
print "Sending 'Hello, World'..."
ws.send("Hello, World")
print "Sent"
print "Reeiving..."
result =  ws.recv()
print "Received '%s'" % result
ws.close()

基本的にsocketモジュールっぽくしようとして、その後、妥協しています。websocketのオブジェクトを作って、あとは、socketのようにsend/recvをやります。
で、それから、JavaScriptのWebSocketのようにコールバックベースでも書けます。
import websocket
import thread
import time

def on_message(ws, message):
    print message

def on_error(ws, error):
    print error

def on_close(ws):
    print "### closed ###"

def on_open(ws):
    def run(*args):
        for i in range(3):
            time.sleep(1)
            ws.send("Hello %d" % i)
        time.sleep(1)
        ws.close()
        print "thread terminating..."
    thread.start_new_thread(run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("ws://localhost:5000/chat",
                                on_message = on_message,
                                on_error = on_error,
                                on_close = on_close)
    ws.on_open = on_open
    
    ws.run_forever()

どっちかいいかはよくわかりません。ちなみに、僕はgeventでmonkey patchをあてて強調スレッドで動かしてます。最初は久しぶりにTwistedを使おうかと思ったのですが、やめました。
それから、英語のドキュメントを読みながら書いたので、読み間違えているところがあったらごめんなさい。まあ、日本語でも読み間違えることはあるので、ごめんなさい。

実装してみて、やっぱりWebSocketのHandshakeでkeyを3つも作って、サーバのレスポンスが問題ないかバリデーションする意味がさっぱり分からない。
WebSocketのハンドシェークでキーを3つ作って、一回のGETでサーバに送ります。キーを受け取ったサーバは、その3つのキーからmd5のハッシュ値を計算してクライアント返します。クライアントは、送ったキーのmd5のハッシュ値とサーバから受け取ったハッシュ値が同じ値かどうかチェックします。それだけです。ハッシュ値はちょっとめんどいけど、キーから計算できるものです。ハッシュ値を使うところはそこだけなので、何をやりたいのかさっぱり分かりません。sec-webscoket-xxxのsecってsecureだと思うんだけど、何もsecureじゃないです。
その後のデータの送受信も暗号化されるわけではないので、めんどーな処理という以外にメリットが感じられません。うーん、これの計算でクライアントを作る気が一瞬なくなったので、そういうメリットはあるかな?

でわでわ。