Block Rockin’ Codes

back with another one of those block rockin' codes

これからの Web の話をしよう。 (次世代 Web セッション @ CROSS2014)

Update

2014-01-19 振り返り

CROSS2014 が無事終了しました。

以下ログです。ビデオは前のセッションとかぶっているので 35 分くらいからが本セッションです。


去年に引き続き、今年もなかなか密度の高い内容になったんじゃないかと思います。

プロトコル編では、普段なかなか表に出てこない方を中心にお呼びして、2013 年から今日までの流れを踏まえつつ、今起こっているプロトコル的な変化を浮き彫りにし、今後の HTTP2.0 の展望や QUIC の持つ意義などを話し合いました。
象徴的なのは「TCP/IP の破壊」などという想定外に振り切った本音に見て取れるかと思います。こうした発言が出たあたり、非常にやった甲斐を感じました。


アーキテクチャ編は、去年より幅広いポジションの方々に出て頂き、それぞれの視点から現実的な話をして頂きました。去年の CROSS で「リアルタイム」として語っていた内容が、「ゲーム」というより具体的なものに繋がり、新しい技術の適応先の一例として興味深い話も出ました。確かに今回は出て頂いた方の携わる分野的な偏りもあったと思いますが、それでも現在の Web について語る上で、ゲームを「完全には無視できない」というのが自分としては盲点だったところかと思います。


全体を通しては、「TLS 前提の Web」という風潮の是非に対する、プロトコル編 / アーキテクチャ編双方のスタンスに割と明確な差異が見いだせました。プロトコルを考えるロール、それを用いてサービスを運用するロール、双方の意見が聞けたのがこのセッション構成をとった 1 つのメリットとして見いだせたのかなと思っています。


反省点は色々ありますが、少なくとも [twitter:muddydixon] さんに「もうやるな」と言われない限りは続けていきたいなと思っています。
(あと、 1 年に 1 回だと少し間が長いし、もっと色々な方とも議論もしてみたいので、 CROSS 以外にも場を作ったり、場が無ければ Podcast にしてみても面白いかなぁ、などと思っています。)


あまり俺が色々書くより、セッションの最後でも話した、参加者による「自分の考える次世代 Web」ブログにそこは譲って(出てくるといいなぁw)、自分のまとめとしてはここまでで。

最後に、出て頂いた登壇者の皆さん。参加して下さったみなさん。そして CROSS スタッフのみなさん。本当にありがとうございました & お疲れ様でした!

Jxck



Intro

2014/1/17(Fri) に開催される CROSS2014 にて、
CROSS2013 に引き続き「次世代 Web セッション」というセッションを担当させていただくことになりました。

今日はこのセッションの意図と概要について書こうと思います。
セッションで扱わない内容などについても言及するので、参加される方はこのエントリを README(趣意書) だと思って参加前に目を通して頂ければと思います。


去年のログ: これからの Web の話をしよう。 (次世代 Web セッション @ CROSS2013)

テーマ

このセッションのメインテーマは去年と同じです。

「次世代 Web セッション」ではいま起こっている Web の変化について、

  • 「何が起こっているのか」
  • 「どこに向かっているのか」

を議論することを目的としています。


また、その議論の主軸を絞るために 2h の枠を

の 2 つのセッションに分けて議論します。

登壇者

今年も豪華な登壇者の方々に集まって頂くことができました。
(公式サイト用プロフィールを引用)

プロトコル編」

@さん
Aria2/Spdylay/nghttp2/Wslay の作者 https://github.com/tatsuhiro-t
@ さん
Chromium Project にて、主に WebSocket, XHR の実装及び関連仕様の標準化を担当。それ以前は、現職にて Load balancer の開発、大学にて FPGA を用いた TCP/IPv6/10GbE の高速化研究など。
@ さん
2009年頃から主にIETFを中心とした標準化活動に参加。現在、通信プロトコル、認証・認可等のセキュリティ領域、プログラミング言語処理系等を主な活動対象としている。


アーキテクチャ編」

@ さん
2011年よりカヤックに入社。自社サービス、ソーシャルゲームのサーバインフラ、運用、監視を担当。
@ さん
2006年はてな入社、2010年より2代目CTO。技術的なことであれば、なんでも好物。はてなのサービスと技術の進化を加速させていきます。著書に「サーバ・インフラを支える技術」「大規模サービス技術入門」など
@ さん
ネットウォッチが高じてプログラマに。とにかくJavaScriptを高速化したい。ネイティブに負けたくない。CoffeeScript等のAltjsが好きで、最近だとLLVMEmscriptenに興味がある。

モチベーション

Web は絶えず変化しています。前回の CROSS2013 からも 1 年経って変わったところがいくつもあります。
この変化をキチンと把握し続けるのはなかなか難しいことです。

例えば「SPDY とは?」といった部分的話は割りとしやすいので、初心者向けの話は多くのイベントでされています。
(自分もよくお話させて頂いています。)

一方で、そうした初心者向けの話は、もう聞き飽きている人も多くいます。
しかし、初心者向けセッションの「その先」を扱うのは実は難しいのが現実です。


次世代 Web セッションは「その先」に挑戦したいと思っています。


「どこに向かっているのか」に答えを持っている人はいません。しかし、僕ら技術者はそこにコミットすることはできると思います。

「どこに向かっているのか」を考えるには、まず今「なにが起こっているか」をきちんと把握する事も必要です。


そこで自分は

  • 「何が起こっているのか」
  • 「どこに向かっているのか」

について、きちんと議論する場を作りたいと思い、その議論ができるであろう方々に声をかけさせて頂きました。

お断り

このセッションは、答えを出すセッションではありません。
答えが出せる、出ているような話は、わざわざここで議論する気はありません。


自分たちが「次世代の Web」を考える上で必要な知見を、常にそれを考えて戦っている方々との議論の中から見出そうというのがこのセッションの目的です。


参加するだけで答えが教えてもらえるという期待を持つ方は、このセッションには参加しないことをおすすめします。

扱わない内容

1 時間は、ちょっと真面目に議論すればあっという間に過ぎてしまう時間です。
その限られた時間の中で本来のテーマに集中するために、切り捨てる内容について書きます。

これは、自分が今回「あまり積極的に扱う必要がない」だろうと考えているものです。 といってもさじ加減なので、実際は登壇者の方々に委ねます。
しかし、参加者の方には「前提」として考えていただきたいと思っています。

初心者向け内容

例えば「SPDY とは何か?」「REST とは何か?」「IETF とは何か?」etc といった話には時間を割きません。
ある程度の知識は前提として、それを踏まえて先について話すことを重視します。
知識を得て Catch Up するためではなく、議論を通して Move Up するセッションにしたいという思いがあります。

過去の事

今回、テーマにはあえて「過去」を入れていません。 懐古するためのセッションとは考えていません。

  • 「何が起こっているのか」(現在)
  • 「どこに向かっているのか」(未来)

もちろん、古きを知ることは重要です。
過去の事例が引き合いに出ることもあると思いますが、過去を改めてから未来を語るには時間がたりません。
過去は基本的には共有された前提として考えて、「その先」にフォーカスして議論したいと考えています。

登壇者紹介

こうしたセッションで、自己紹介で大きく時間を浪費するセッションをよく見ます。
今回の登壇者のみなさんは著名な方が多く、このセッションを聞きに来る方は知っている人が多いと思います。
彼らの自己紹介に時間を割く気はありません、みなさん自己紹介を聞きに来るわけでもないでしょう。
なので、冒頭に自分がみなさんの名前を紹介する程度とさせて頂きます。


各登壇者の紹介は、このエントリや公式サイトのセッション概要から見ることもできますので。予め見ておいて頂けると良いと思います。

進め方

流れを決めすぎると予定調和になってしまうので、打ち合わせの段階でおおまかな方向性とキーワードのみを決めておき、
当日はそれについて基本的にはフリースタイルで議論を進めていきたいと思っています。


あまりズレて行くなら、途中で切るなどはしすまが、その場で起こる議論をなによりも大事にしたいです。

雰囲気については、去年の映像をご参照下さい。

まとめ

最後にもう一度。
このセッションは、 Web について

  • 「何が起こっているのか」
  • 「どこに向かっているのか」

に 1 時間フォーカスし続け、「その話はもう知ってるよ」とか、「何度も聞いたよ」みたいな話を無くして、少しでも議論の密度を高めたいです。
そのために参加者には「前提」を求める部分が多くなる点はありますが、限りある時間を有効に使うためにご理解ください。
これだけの方が揃ったセッションなので、変わりゆく Web の今後を考える上で、少しでも熱く有益な議論ができる場を作れればと考えています。


今年も去年同様、「最後は自分の頭で考えろ」(by @) で行きたいと思います。
このセッションで得たものをベースに、あなたが考える「次世代 Web」についてのエントリが読めることを、楽しみにしています。

Jxck

これからの Web の話をしよう。 (次世代 Web セッション @ CROSS2013)

update

2013/1/13
終了したので録画やログを追加

intro

2013/1/18(Fri) に開催される CROSS2013 にて、「次世代 Web セッション」というセッションを担当させていただくことになりました。


次世代 Web セッション


今日はこのセッションの内容と、このセッションで自分がやりたいと思っていることについて書こうと思います。

Theme

このセッションは「次世代 Web セッション」というタイトルとし、主にいま起こっている Web の変化(リアルタイム Web, HTTP2.0 の登場 など)について、

  • 「何が起こっているのか」
  • 「どこに向かっているのか」


を、


の 2 つのセッションに分けて議論したいと思っています。
(枠自体は 「次世代 Web セッション」の 1 つですが、内容的には 2 つのセッションからなります)

Motivation

HTTP, CGI, Ajax, HTML5 etc と、 Web には今までもいろいろな変化がありました。そして今また、 Web が大きく変わりつつあると感じています。


この変化をキチンと把握するのはなかなか難しいです。


例えば「WebSocket とは?」といった部分的話は割りとしやすいので、初心者向けの話は多くのイベントでされています。 一方で、そうした初心者向けの話は、もう聞き飽きている人も多くいます。 しかし、初心者向けセッションの「その先」を扱うのは実は結構難しいのが現実です。


今回の CROSS では各 1 時間という時間をつかって、初心者向けセッションで語られない「その先」に挑戦したいと思っています。


「Web がどうなるか」に答えを持っている人はいません。しかし、僕ら技術者はそこにコミットすることはできると思います。


「どこに向かっているのか」を考えるには、まず今「なにが起こっているか」をきちんと把握する事も必要です。


そこで自分は

  • 「何が起こっているのか」
  • 「どこに向かっているのか」


について、きちんと議論する場を作りたいと思い、それの議論ができるであろう方々に、声をかけさせて頂きました。

プロトコル編」(14:00-15:00)

今起こっている変化の主流には、プロトコルレベルで Web を見直すという取り組みが大きく関わっていると考えます。


そこでは様々なステークホルダが参加しているので、議論される問題は Web の現状を浮き彫りにし、非常に興味深いものがあります。


プロトコル編では、主に Web のプロトコル自体の変化(WebSocket, SPDY, WebRTC, HTTP2.0 etc)について、低レイヤで何が起こり、どこに向かっているかを議論したいと考えています。

登壇者
  • 小松さん(@komasshu)
  • 大津さん(@jovi0608)
  • 清水さん(@kazubu)

アーキテクチャ編」(15:15-16:15)

プロトコルが変わると、アーキテクチャが前提としていた制約が変わります。 すると、自ずとアーキテクチャも見直す必要が出てきます。


また、プロトコル以外にも、サービスに求められる要件の変化によっても、アーキテクチャは変化しています。


アーキテクチャ編」では、そうしたプロトコルの変化、時代の要求の変化により、Web のアーキテクチャがどうなっていくだろうかという点について、議論したいと考えています。
キーワードとしては REST や MVC、リアルタイム、リアクティブ、バイナリベース/テキストベースなどを考えています。 ただ、キーワードはあくまで議論の呼び水であり、議論の主軸は「何が起こり、どこに向かっているのか」です。

登壇者
  • 山本さん (@yohei)
  • 江島さん (@kenn)
  • 佐々木さん (@yssk22)

扱わない内容

今回は、各セッションが 1 時間しかありません。 これは、ちょっと真面目に議論すればあっという間に過ぎてしまうと思います。
その限られた時間の中でとにかくテーマに集中するために、いくつか切り捨てる内容について書きたいと思います。


これは、自分が今回「あまり積極的に扱う必要がない」だろうと考えているものです。 といってもさじ加減なので、実際は登壇者の方々に委ねます。
しかし、参加者の方には「前提」として考えていただきたいと思っています。

初心者向け内容

難しいところですが、例えば「WebSocket とは何か?」「REST とは何か?」「IETF とは何か?」etc といった話は要らないと思っています。
知識を得て Catch Up するためではなく、議論を通して Keep Up するセッションにしたいという思いがあります。

過去の事

今回、テーマにはあえて「過去」を入れていません。 懐古するためのセッションとは考えていません。

  • 「何が起こっているのか」(現在)
  • 「どこに向かっているのか」(未来)


もちろん、古きを知ることは重要です。 Web の歴史、HTTP の歴史 etc 引き合いに出るでしょう。
しかし、過去を改めてから未来を語るには時間がたりません。過去は基本的には共有された前提として考えて、それらを踏まえた「その先」を中心に議論したいと考えています。

登壇者紹介

特に自己紹介で大きく時間を使ってしまうセッションが多いです。
今回の登壇者のみなさんは著名な方が多く、このセッションを聞きに来る方は知っている人が多いと思うので、冒頭に自分がみなさんの名前を紹介する程度とさせて頂きます。


各登壇者の紹介は、 セッション概要 から見ることもできますので。予め見ておいて頂けると良いと思います。

進め方

流れを決めすぎると予定調和になってしまうので、打ち合わせの段階でおおまかな方向性とキーワードのみを決めておき、
当日はそれについて基本的にはフリースタイルで議論を進めていきたいと思っています。


あまりズレて行くなら、途中で切るなどはしまが、その場で起こる議論をなによりも大事にしたいです。

最後に

今のWeb について

  • 「何が起こっているのか」
  • 「どこに向かっているのか」


に 1 時間フォーカスし続けたいです。 「その話はもう知ってるよ」とか、「何度も聞いたよ」みたいな話を無くして、少しでも議論の密度を高めたいです。
そのために参加者には「前提」を求める部分が多くなる点はありますが、限りある時間を有効に使うためにご理解ください。
これだけの方が揃ったセッションなので、変わりゆく Web の今後を考える上で、少しでも熱く有益な議論ができる場を作れればと考えています。



CROSS には、このセッション以外にも興味深いセッションが多数あります。
現在エンジニアや、それに関わることを何かしらをしていれば、気になるセッションがきっと一つはあると思います。
なにとぞよろしくお願い致します。

本番終了後ログ

無事本番を終えました。
自分としては、上記に望んでいたセッションができたので満足しています。


以下が録画です。


(前半) プロトコル編


(後半) アーキテクチャ編


スライド

最後の yohei さんの一言「あとは自分の頭で考えろ」が何よりのまとめです。
そして、それを考えるに足るだけのエッセンスはほぼ出てきました、
来ていただいたみなさんにとっても、これからの Web を考えるきっかけになったなら、よかったと思います。


今回のセッションを作るにあたっては、(最終的には登壇もして頂いたんですがw) t_wada さんに色々な面でアドバイスを頂きました。
こうしたセッションを作るには自分はまだまだ力不足だったので、非常に心強かったです。


そして、このセッションのために非常に豪華な登壇者の方に集まって頂きました。
みなさん忙しい中自分のわがままに付き合って頂いて、本当に感謝しております。


最後に muddy さんを始め運営のみなさん、このような機会を頂き本当にありがとうございました。


Jxck

Web+DBPress Vol.71 で WebSocket 特集を書かせて頂きました。

intro

タイトルの通り、 WEB+DB PRESS Vol.71 で WebSocket の特集を書かせて頂きました。


WEB+DB PRESS Vol.71

WEB+DB PRESS Vol.71


この特集は、 @ さんと分担して書かせて頂いてます。
分担はこんな感じ。

第1章:WebSocket入門 Jxck
HTTPの限界とリアルタイムWeb時代の到来
第2章:WebSocketプログラミングの基本 Jxck
Socket.IOのインストールとAPIの使い方
第3章:チャットアプリケーションの作成 mizchi
Socket.IOでメッセージ処理
第4章:シンプルな多人数同期型アプリケーションの作成 mizchi
Socket.IOの特性を最大限に活かす
第5章:WebSocketのこれから Jxck
関連仕様とHTTP2.0の動向


おおまかに、自分が WebSocket プロトコル自体の話と Socket.IO の導入。
mizchi さんが Socket.IO を用いた本格的なアプリ実装解説。
といった感じの分担で書かせて頂きました。

担当章について

1 章では、初心者向けに、なぜ WebSocket が出てきたのか、
HTTP とは何が違うのかなどを解説しています。


2 章では、それをふまえて Socket.IO のモチベーションと、
その最小限の使い方を解説しています。 Socket.IO@1.0 についても軽く触れています。


3 章では、「もう WebSocket の初心者向けエントリは飽きた。」
という方も満足できるような内容を意識して、
WebSocket の Extension や SubProtocol。
そして、 SPDY から HTTP2.0 への流れなども簡単に触れています。


全体として、進化の早い WebSocket の現状と最新動向をまとめてありますので、
興味のある方は、是非読んでみて頂けると幸いです。


書く側としても、色々調べ直しながら書いて行く中で、
自分の知識をアップデートしたり、理解不足を補う事ができました。


また、レビュアーの方々に色々指摘を頂けたのも、
自分としてはとても勉強になりました。

謝辞

本誌には書けなかったですが、
今回も、色々な方にお世話になって書き上げることができました。


そんな中でも、自分の原稿のレビューをして下さったみなさま。
一緒に書かせて頂いた mizchi さん。
タイトなスケジュールの中担当頂いた、技評のみなさん。


本当にありがとうございました。

Chrome Socket API で WebSocket

intro

Chrome Canary で Socket API が listen() をサポートしたってことで、
ブラウザ上にサーバを立てるという矛盾した行為が、
にわかに流行っているようです。

HTTP Server on Chrome Socket API

で、以下のようにするとサーバが立てられて。
クライアントからアクセスすると HTTP が配信されたりします。


Chrome Socket API Server


しかし、 何故か accept() が一回しかリクエストを処理できず、
現状一回処理したら立ち上げ直す(= Chrom 再起動?) しかないので、
仕方なく accept() をその都度行わないといけない状況です。
(多分そのうち改善されるんだろうと思います。)

WebSocket Server on Chrome Socket API

ということで、動くか確かめて見たくなったので、
ちょっとやってみました。
ソースはこちらです。

Jxck/ChromeWebSocketServer · GitHub


これを Chrome の Canary でたちあげて、
コンソールから下記を叩くと、
メッセージがやり取りできました。

var ws = new WebSocket('ws://localhost:3000');
ws.onopen = function() {
  ws.send('hoge');
  ws.onmessage = function(e) {
    console.log('receive', e.data);
  }
}

面倒なんで 4byte 送ったら 4byte 返ってくるだけです。


WebSocket サーバの実装とプロトコル解説 - Block Rockin’ Codes


で書いたコードを移植した感じです。

まとめ

素振りです。特に意味は無いです。
個人的には TypedArray の API より Node の Buffer の方がわかりやすいなぁ。

WebSocket サーバの実装とプロトコル解説

intro

なんだかんだ WebSocket を使ってるのに、 WebSocket サーバを自分で書いたことが無かったので、RFC も落ち着いてきたここらで、仕様を読みながら実装してみようと思いました。


"WebSocket サーバ 実装" とかでググると、 Socket.IO とか pywebsocket で WebSocket アプリ作って、「WebSocket サーバを実装」みたいなタイトルになってることが多いみたいですが、
(ApachePHP で HelloWorld して、「HTTP サーバ実装しました」とは言わないよね。)


この記事では、 WebSocket プロトコルをしゃべるサーバ自体を実装します。
といっても、全部やるのはちょっと大変だったので、基本的なテキストメッセージのやりとりの部分だけやって、エコーサーバができるところまでやりました。


完成版のソースは以下です。本文を読みながら、合わせて見ていただけると良いと思います。

WebSocket Server Sample Impliment · GitHub

仕様

今回実装するのは、IETF RFC6455 に準拠したサーバと思ったのですが、全部やるのは大変だったので、サーバとクライアントでそれぞれ単純な
テキストをやり取りできるところまでにします。

RFC 6455 - The WebSocket Protocol

ちなみに、 API の方は W3C になります。

The WebSocket API


具体的には C->S, S->C それぞれ 'test' という文字列をやり取りします。

// Client
var ws = new WebSocket("ws://localhost:3000/", ["test", "chat"]);
ws.onopen = function() {
  ws.send("test");
  ws.onmessage = function(message) {
    console.log(message.data); // test
  };
}

使ったのは以下

(FireFox でも動いたみたい)

HTTP Server

まず、今回は HTTP サーバで配信した HTML に含まれる JS から、同じサーバの上にある WebSocket サーバにコネクションを要求、確立する
感じにします。


そのため、まずは Node.js で HTTP サーバを立て、HTML を配信します。

var clientScript = function () {
  var ws = new WebSocket("ws://localhost:3000/", ["test", "chat"]);
  // var ws = new WebSocket("ws://localhost:3000/", "test");
  ws.onopen = function() {
    console.log(ws);
    ws.send("test");
    ws.onmessage = function(message) {
      console.log(message.data);
    };
  }
}

var server = http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = '<html><head><title>wsserver</title>' +
  '<script type="text/javascript">' +
    '(' + clientScript + ')();' +
  '</script>' +
  '</head>' +
  '<body>hello world</body>' +
  '<html>';
  res.end(html);
});

シンプルな HTML の中に、生の WebSocket を使う JS を埋めています。


ブラウザで WebSocket オブジェクトを new すると、引数の WebSocket サーバに接続しにいきます。


この時、ブラウザは HTTP1.1 で upgrade リクエストを投げます。
そこで WebSocket を指定することで、通信プロトコルを WebSocket に upgrade する要求をができるので、サーバはこのリクエストを受け取り、ヘッダを解析します。

HTTP Upgrade Header

upgrade リクエストのヘッダは以下のようなものです。
1.2. Protocol Overview


また、 HTTP のヘッダ周りはこちらを見ると参考になると思います。
studyinghttp.net - 

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Host
接続先の HOST です。
Upgrade
HTTP1.1 を別のプロトコルへ切り替える要求です。
Connection
「HTTP/1.1 では、"Upgrade が HTTP/1.1 メッセージに存在する時は常に、upgrade というキーワードを Connection ヘッダフィールド (section 14.10) の中に与えなければならない" とされている事に注意せよ。」だそうです([http://www.studyinghttp.net/security#SwitchingProtocolFromHTTP:title=link)
Sec-Websocket-Key
クライアントの認証や、セキュリティのためにクライアントで生成されるキーです。
Origin
接続を要求するオリジンを示します。
Sec-WebSocket-Protocol
サブプロトコルのリストです。
Sec-WebSocket-Version
プロトコルのバージョンです。最新は13。


で、これらの解析なんですが、実は Node.js の場合は先程立てた HTTP サーバが勝手に解析してくれます。(なんというチートw)


サーバに Upgrade リクエストが来ると、Server オブジェクトで upgrade イベントが発生し、コールバックの第一引数に解析されたヘッダがオブジェクトとして渡されます。

server.on('upgrade', function(req, socket, head) {
  console.log(req);
  //  { host: 'localhost:3000',
  //  upgrade: 'websocket',
  //  connection: 'Upgrade',
  //  origin: 'http://localhost:3000',
  //  'sec-websocket-protocol': 'test, chat',
  //  'sec-websocket-extensions': 'x-webkit-deflate-frame',
  //  'sec-websocket-key': 'NblXHeIwGDpoQ2GFAGzwzw==',
  //  'sec-websocket-version': '13' }
});


現在、 Chrome では Sec-WebSocket-Extensions ヘッダも送られています。
このへんかな

が、これはあくまで拡張なので、今回は無視します。


gist に貼ったソースでは、このあと、わかってる範囲で古いヘッダでの条件分岐などを書いていますが、最新のプロトコルのヘッダだけに対応するならこの時点で半分は終わりですw

HTTP Upgrade Response Header

リクエストからレスポンスを生成します。
レスポンスヘッダはこのようになります。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

レスポンスは HTTP1.1 Switching Protocol を返すことで、upgrade が受け入れられたことを示します。


この時、 Sec-WebSocket-Accept フィールドは、リクエストにあった、 Sec-WebSocket-Key の値から以下のように算出された値を使用します。

  1. Sec-WebSocket-Key(key) の末尾の空白を覗いた値を準備
  2. key に固定値 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" を連結
  3. sha1 を取得
  4. base64 に変換

です。


Node.js では標準の Crypto モジュールを使います。

key = require('crypto')
       .createHash('sha1')
       .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
       .digest('base64');

Sec-WebSocket-Protocol は、クライアントから提示されたサブプロトコルからどれか(もしくは無し)を選んで返します。
そもそもサブプロトコルはオプションで利用できるアプリケーションレイアのプロトコルなので、今回はネゴシエーションしてるけど、使いません。

Data Frame(C->S)

ヘッダを返したら、 WebSocket 通信が確立します。


まずは、クライアントからメッセージを送ってみましょう。

ws.send('test');

送られるメッセージは 4byte の ASCII 文字列です。


この文字列はバイナリ形式のデータフレームでサーバに送られます。

Node.js の場合は、メッセージが届くと 'data' イベントが Socket オブジェクトで発生し、コールバックに Buffer オブジェクトでこのデータが渡ります。


Buffer オブジェクトは、 Node.js でオクテットストリーム(要するにバイナリデータを 8bit ごとの配列っぽく)扱うためのオブジェクトです。


このフレームの形式は以下のようになっています。


RFC 6455 - The WebSocket Protocol

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+


では、 Buffer の中身をコツコツ解析しながら、各値を解説していきます。

FIN

最後のパケットなら 1, 続くなら 0
今回は 1 ('test' というデータは一回で送れるから)

var firstByte = receivedData[0];
/**
 * fin
 * axxx xxxx first byte
 * 1000 0000 mask with 0x80 >>> 7
 * ---------
 * 1         is final frame
 * 0         is continue after this frame
 */
var fin = (firstByte & 0x80) >>> 7;
RSV1-3

extention を使わないなら0
今回は拡張を使わないので無視。

opcode

Payload Data の説明
Payload Data とはヘッダ等じゃない実データ(ここでは 'test')

  • %x0:continuation frame
  • %x1:text frame
  • %x2:binary frame
  • %x3-7:reserved for further
  • %x8:connection close
  • %x9:ping
  • %xA:pong
  • %xB-F:reserved for further

今回は 0x1

/**
 * opcode
 * xxxx aaaa first byte
 * 0000 1111 mask with 0x0f
 * ---------
 * 0000 0001 is text frame
 */
var opcode = firstByte & 0x0f;
MASK

1 ならPayload Data がマスクされている。されていなければ 0。

Payload はブラウザが送るときは "必ずマスクする"
サーバが送るときは "絶対にマスクしない"
という決まりがある。

今はブラウザからだから 1

var secondByte = receivedData[1];

/**
 * mask
 * axxx xxxx second byte
 * 1000 0000 mask with 0x80
 * ---------
 * 1000 0000 is masked
 * 0000 0000 is not masked
 */
var mask = (secondByte & 0x80) >>> 7;
if (mask === 0) {
  assert.fail('browse should always mask the payload data');
}
Payload Length

Payload Data の長さ
最初の 7 bit を読んだとき

  • 0-125:そのままそれが Payload の長さ
  • 126:それより長いから、後続の 16bit が UInt16 として Payload の長さを表す
  • 127:それよりも長いから、後続の 64bit が UInt64 として Payload の長さを表す

ここでは、処理するデータは 'test' と決め打ちにして、 7bit だけ読む実装にした。
だから、 Pyload Length = 4 になる。

/**
 * Payload Length
 * xaaa aaaa second byte
 * 0111 1111 mask with 0x7f
 * ---------
 * 0000 0100 4(4)
 * 0111 1110 126(next UInt16)
 * 0111 1111 127(next UInt64)
 */
var payloadLength = (secondByte & 0x7f);
if (payloadLength === 0x7e) {
  assert.fail('next 16bit is length but not supported');
}
if (payloadLength === 0x7f) {
  assert.fail('next 64bit is length but not supported');
}
Masking Key

Payload をマスクしているキーデータ


Payload Length の後に続く、 32bit のデータがこれになる。
MASK=1 だった場合は必ず付与される、後で Payload を複合するのに使う。

/**
 * masking key
 * 3rd to 6th byte
 * (total 32bit)
 */
var maskingKey = receivedData.readUInt32BE(2);
Payload Data

実データの部分、実際は Extention Data + Application Data


以降、末尾までのデータが Payload Data になっている。
しかし、実際これは Extention Data + Application Data となっている。


Extention Data とは、ネゴシエーションの過程で、 Extention を使うと決めた
場合に付与されるデータだから、今回は使わない。(というか Payload に含まれない)


Application Data は今回で言う 'test' のこと。
だから、 Payload Data == Application Data と考えていい。


Payload Length が 4byte とわかっているため、
32bit を Big Endian で読んであげればいい(TCP=BigEndian だからだと思ってるけどあってるのかな?)

var applicationData = receivedData.readUInt32BE(6);


読みだしたデータを、先ほどの Masking Key で unmask する。
(Masking Key との XOR をとってあげればいい)

var unmasked = applicationData ^ maskingKey;


この値を UTF-8エンコードしてあげればいいんだけど、
今の時点では Buffer じゃないから、一旦バッファにしてから、
Buffer.toString() をした。 (もっといい方法あるかも)
Buffer Node.js v0.8.26 Manual & Documentation

var unmaskedBuf = new Buffer(4);
unmaskedBuf.writeInt32BE(unmasked, 0);

var encoded = unmaskedBuf.toString();
console.log(encoded)' // test


これで無事、クライアントが投げた値が取り出せました。

Data Frame(S->C)

次はクライアントにデータ 'test' を送ります。
といっても、さっきの逆をやればいいだけです。


大きな違いは、

「サーバからクライアントに送る場合は、マスクしない」

という点です。


各値は以下のようになります。

  • FIN:1
  • OPCODE: 1
  • MASK: 0
  • Payload Length: 4
  • Payload: 'test'

変換を手を抜いて、普通に書き込んだらこんな感じ。
一旦 Buffer オブジェクトに貯めて、 socket.end() に渡すと、クライアントに送ってくれます。
String からは charCodeAt() が使えます。

/**
 * Sending data to client
 * data must not mask
 */
var sendData = new Buffer(6);

// FIN:1, opcode:1
// 0x81 = 10000001
sendData[0] = 0x81;
// MASK:0, len:4
// 0x4 = 100
sendData[1] = 0x4;

// payload data
// send data "test"
sendData[2] = 'test'.charCodeAt(0);
sendData[3] = 'test'.charCodeAt(1);
sendData[4] = 'test'.charCodeAt(2);
sendData[5] = 'test'.charCodeAt(3);

// send to client
socket.end(sendData);

うまくいけば、クライアントでは onmessage イベントが発生し、data フィールドに送ったデータが格納されます。

ws.onmessage = function(message) {
  console.log(message.data); // test
};

まとめ

WebSocket の仕様自体は、全部実装しようとするとちょっと大変だけど、今はまだ読もうと思えば読める程度の量だと思います。


より大きな問題は、ここまでに生まれては消えたプロトコルの実装を持った、古いブラウザとの互換性でしょう。そこを考えなければ、頑張れば実装できなくはないかも。


しかし、現在の RFC にしても、まだまだ仕様が変わる可能性は、無くはないし、途中で出てきた Extention や Subprotocol, また別で進んでる multiplexing などによって、今後もう少し変わる可能性があります。
WebSocket をアプリで使う場合は、おとなしくメンテナンスされている実装を使うのが吉ですが、こうして自分でちょっとでも実装すると得られるものも多いのでお勧めです。


JS はもともとバイナリを処理する文化が無かったため、Node.js は独自に Buffer モジュールを作っていますが、JavaScript には TypedArray という仕様が最近出てきているので、いずれ Node もそっちを使うことになると思います。



もしかしたら、仕様の読み違いなどあるかもしれません、指摘、質問などフィードバック歓迎です。

SPDY と WebSocket の基礎と SPDY の Push

最近 SPDY対WebSockets? などという記事が出てきたりして、

SPDY と WebSocket が色々ごちゃごちゃになって語られているのかもなぁ、と思います。


SPDY では Akamai の中の人の

Guy's Pod » Blog Archive » Not as SPDY as You Thought

や、それに対するフォロー記事

Followup to “Not as SPDY as You Thought” « Mike's Lookout

なんかも、 ちょっと注目されたりしました。


これらの記事には WebSocket や SPDY の性質やモチベーションを正しく理解するために、
知っておくと良い知識へのキーワードが散りばめられていると思ったので、
そこら辺について、つらつら書いてみようかと思います。

SPDY は「遅い」のか?

Guy's Pod » Blog Archive » Not as SPDY as You Thought では Guy 氏がベンチをとっています。


ベンチは

  • Alexa の top 500 サイト(アダルト含む)を、 HTTP/HTTPS/SPDY に切り替えて比較した。
  • サーバは Cotend でプロキシして、クライアントは Chrome の WebPageTest を使用。
  • 4 つの速度の違うネット環境で 5 回づつテスト。
  • 時間とともに Alexa の結果はかわるので、昼に 2 回、夜 1 回実施して、合計で 9 万回ページロードを試行したらしい。


ここまでキチンと行われているので、結果の信頼性は高いだろうとのこと。

結果は

SPDY は HTTPS より 4.5% 速く、 HTTP より 3.4% 遅かった。


原因としては


であると分析しています。



これに対する補足記事として、数日後に以下の記事があがりました。


Followup to “Not as SPDY as You Thought” « Mike's Lookout


よく、こうしたベンチをめぐる論争があるために、この記事を最初のベンチへのアンチ記事のように思われるかもしれませんが、
よく良く読むとそんなことは一切無くて、 Mike 氏は Guy 氏 の記事の価値(ベンチの信頼性)を評価した上で、こう付け加えています。

Guy 氏 のテストは、 SPDY ページロードではなく、 "部分的な" SPDY ページロードしかテストできてない。


ここでは、 Guy 氏が挙げた 1 つ目の理由の方に着目しています。


まとめると、 Guy 氏 のテストでは、ドメインが違うと SPDY の通信は多重化できないから、 3rd Party Domain が多いサイトでは、
SPDY のメリットが得られにくいと指摘しています。
そして、Guy 氏のテストしたサイトには、 3rd Pary Domain の中に、「自分がコントロールできるドメインが混ざっている」ことを Mike 氏 は問題にしていて、
つまり、 google.com の持ち主なら、 images.google.com を触ることができるんだから、
全てのコンテンツを google.com から取るようにしたら、 SPDY の効果がより大きく出るはずだというもの。


Guy 氏 のテストから cnn, ebay, yahoo の結果だけをサンプリングして、その中で使われているドメインを調べてみても、
「SPDY で読もうと思えば読めるはずだけど、上記理由からそうなってないリソース」が結構多いことを指摘している。
(これが、 Mike 氏 の blog 真ん中の表)


だから、 Mike 氏 は「Guy のテストは素晴らしいけど、これはサイトを SPDY に最適化することで、より効果が得られる可能性を指摘している」 といったまとめをしています。

High Performance WebSite のアップデート

要するに、SPDY によって高速化のための手段は増えたものの、
ただ使うだけではだめで、相応の最適化も必要であるということ。


これは WebSocket についても同じです。


そして、こうした技術をどういった場面に適応するのかは、きちんと見定めないといけないし、
単純に置き換えただけで、なんでもうまくいくというものでもないだろうということです。


SPDY は、確かに既存のインフラやアプリとの親和性/互換性を大事にしているけど、
それでも、それなりに「最適化」をしないと、期待するパフォーマンスは引き出せないことを Guy 氏 の記事は示唆しているととれるのではないでしょうか。


こうした部分を使いこなせれば、今まで行なっていた様々な最適化/高速化手法に、
新たな、強力な選択肢が増えることになります。


SPDY と WebSocket は "vs" で繋げるか?

SPDY と WebSocket については、かなり用途というか、考えるべきレイヤが違うものだと思っています。
このブログでは Socket.IO によりつつも、 WebSocket については何度か何度か書いてきたので、
WebSocket の説明はそこそこに、 SPDY について書きたいと思います。

SPDY のモチベーション

SPDY は Google が「HTTP が遅いから、速くしたい」というモチベーションのもと、
「自分たちで作って、自分たちで使い始めた」プロトコルというような感じで始まりました。


具体的に、例えばあるページが複数のリソース (img, css, js) などをリンクして成り立っている場合を考えると、


HTTP では

  • まず index.html を読む。
  • 中にある <img>, <link>, <script> に書かれたリソースを取得する。この時、それぞれのリクエストは前のレスポンスが返らないと投げられない。(*1)
  • 全てのリクエスト、レスポンスには HTTP ヘッダが付き、それも含めてテキストが基本


SPDY では

  • index.html を読む。(*2)
  • 必要なリソースを、同一のコネクション上で多重化し一気にやり取りできる。
  • ヘッダは圧縮されたバイナリなので、生テキストよりはサイズが小さい。


といった形で、SPDY の方が従来の HTTP より効率の良い(*3)通信を実現することを目的としています。


*1: なので、セッションを複数張って並行にダウンロードしたりする。
*2: SPDY の Push を使うと index.html を読む時点で、必要なリソースも読める。後述。
*3: 一方で、暗号化のコストや、中間キャッシュが効かないなどはある。

SPDY で Push

そんな中で SPDY も WebSocket でよく言うサーバサイド Push が可能だと言う点が、話をもう一つややこしくしているのかもしれません。
これは、同じサーバサイド Push である、 WebSocket と、 似ているようですが、若干用途が違います。


SPDY の Push は、動作イメージとしては、

「リクエストが想定されているリソースを、先にレスポンスしておく」


といった感じの挙動ができます。
(これはあくまでも、ひとつの使い方だと思いますが。)


しかし、これで従来できなかったような高速化が
できるようになる可能性があります。


従来のリソース読み込み

例えば、最初のリクエストで index.html を読み込んで、
ブラウザがそれを解析し始めたとき、以下の様な外部リソースの
埋め込みがあったとします。

<link rel="foo.css">
<script src="bar.js">
<img src="buz.png">


すると、ブラウザは(AMD とかで無い限り)、これらを順番に取得していきます。
取得の際は、単一のセッションではレスポンスが返るまで、リクエストが投げられません。


そこで、ブラウザは複数(2-6くらい) のセッションを同時に張って、
平行にリソースの取得を行うなどしていました。これはサーバやネットワークに大きな負荷になります。


しかし、 spdy は単一のセッションでリクエストを多重化することができるため、
それだけでも、大量の画像の読み込みなどでは、セッションの面で見る通信の効率は
良くなります。


さらに spdy は、サーバ push を利用することで、
ブラウザが index.html を読み込んで、リクエストを投げる前に、
外部リソースを配信することができます。
これは、見方を変えれば「キャッシュの送りつけ」という感じです。


先ほどの例では、ブラウザは index.html を読んで初めて
"foo.css", "bar.js", "buz.png" も必要だということがわかり、
そこで初めてリクエストを投げてリソースを取得します。


しかし、コンテンツを送る側であるサーバは、 index.html が必要とするリソースをあらかじめ知ることができる
(場合が多い) はずなので、
index.html とともに、 ブラウザがリクエストしてくる前に
"foo.css", "bar.js", "buz.png" を「送りつけてしまう」のです。


送りつけられたこの三つは、ブラウザが index.html を読んでいる間に
キャッシュに入り、ブラウザがリクエストを発行する段階では
キャッシュヒットしてすぐにリソースが使用出来るという寸法です。

「俺は index.html を読んでリクエストを発行しようとしたら、
そのリソースは既に"キャッシュの中に入ってた"。。
何を言ってるかわからねーと思うが(ry
cdn とか css-sprite とかそんなちゃちなもんじゃあ 断じてねぇ
もっと恐ろしいものの片鱗を味わったぜ。。」

node-spdy で push

node-spdy でサーバ push をやるのは、そこまで難しくないです。


indutny/node-spdy · GitHub を npm でインストールしたら、
下記のように通常の https の createserver と同じインタフェースでサーバを立てるだけです。

push については、

  • req.issdy
  • res.push()

を用います。


主要部分はこんな感じ。

var path = '/images/icon0.png',
    header = { 'content-type': 'image/png' },
    file = fs.readFileSync(__dirname + '/public' + path);

app.get('/', function(req, res) {
  var title = 'HTTP :(';
  if (res.isSpdy) {
    title = 'SPDY :)';

    res.push(path, header, function(err, stream) {
      if (err) return console.error(err);
      stream.on('error', function(){});
      stream.end(file);
    });
  }

  res.render('index', {
    title: title
  });
});


上記の全体は、こちら。 spdy-push/simple.js at master · Jxck/spdy-push · GitHub


サーバを起動して、 https://localhost:3000 にアクセスします。
(HTTPS であることに注意!! いや、俺は別にそんなところで 15 分もハマったりしてないですよ、やだなぁホントww)


Chrome でみると、 Push したリソースは Cache から読まれていることがわかります。
(もちろん Disable Cache しておきましょう。)



SPDY が動いていることは、 SPDY-indicator を使うと URL エリアに印が出ます。
(ちなみに作ってるのは、 Socket.IO の作者 Guillermo です)

ベンチマーク

では、多数のリソースファイル(img, css, js)を読み込むだけの、
単純な html を返して、Push した場合としない場合で、全体の読み込みにかかる時間を調べてみようと思います。


ベンチを取ったこれの全体はこちらです。(spdy.js が本体)


https://github.com/Jxck/spdy-push


リソースは、img が自分の icon、 css, js については、一般的なものとして、
jquery.js と bootstrap.css の非圧縮のものを採用しました。
それぞれとりあえず 10 個づつ読み込んでいます。


chrome を用いて、同じリソースを読み込む速度を
Push した場合と、しなかった場合で比べてみました。


三回測定した結果は

push なし
2.09, 2.14, 2.02:
push あり
1.69, 1.69, 1.57:


これだけでも結構早くなってますね。


ただ、同じスクリプトを HTTP で読んでみたところ、

HTTP
1.28, 1.42, 1.30:


と、こちらの方が速い結果になりました。
これは Guy 氏の言っていたような結果と近いかもしれません。


この場合は、並行するセッションが多いから、そっちの方が速いのかもしれません。
10個は少ないかなとも思ったけど、現実的な値が欲しかったのでそうしました。
HTML が読み込むリソースがもっと多いと差が出るのかもしれません。


SPDY と WebSocket の Push の違い


SPDY の Push を見てきましたが、では WebSocket の Push とは何が違うかを考えてみます。


まず SPDY で Push をしましたが、これはあくまで SPDY の通信なので、
通信にはヘッダ情報などがつきます。


また、実際はリクエストより先にレスポンスを返すという感じなので、
SPDY サーバのコンテキスト上でやり取りします。


一方 WebSocket は、メッセージにヘッダ情報などはつかないため、
メッセージの容量は非常に小さく済みます。


そして、例えば HTTP サーバなどとは独立した形で通信が可能なので、
非常に柔軟な扱いができます。


頑張れば、 SPDY だけでチャットなども出来るかもしれませんが、
そうしたガリガリプログラムで制御するような用途なら WebSocket の方が楽です。
(あ、でも Push が来たことブラウザで取れるのかな?)


WebSocket はそもそもインタフェース自体が、 W3C などを中心に、
アプリケーションを実装しやすいように仕様策定しているので、
当たり前と言えば当たり前ですが。


ここまで見ていただいてわかったように、 SPDY での Push は WebSocket の Push と若干違います。
使い分けるとすれば

spdy
コンテンツのプッシュ、サイトの高速化が主な目的。
WebSocket
アプリに必要な、インタラクティブな表現を可能にするのが主な目的。


という形で使い分ける感じで落ち着くんじゃないかと思います。

まとめ

両方とも、次世代の Web を担うことを期待されたプロトコルだし、
出てきた時期も、 Push できるなど似たような性質を持っている点で、
一緒くたに語られることが増えそうではありますが、
何度も言うように、2つが担う点は別だし、考えないといけないことも、
使うべき場面も違うということは抑えておいたほうがいいと思います。



また、 infoQ では、WebSocket にヘッダが無いことが、
セキュリティやインフラへ大きな影響を及ぼすことを指摘しているリンクがあります。


これは確かにそうで、ヘッダが無いこと以前に、upgrade が通らず LoadBalanser で使えない とか、
パーソナルファイアーウォールでつながらない とか、
いう事例が起こったりします。


その点、 TLS の拡張である Next Protocol Negotiation を用いて昇格している点
SPDY は既存のインフラとの親和性は高いかもしれないけど、
暗号化されている分中間キャッシュが効かない、ドメインが違うと多重化できないなどもあります。



どっちも、「取り入れて終わり」とか「とにかく使えばいい」ってほど簡単でも無い点は変わりません。


いずれも出たばかりのプロトコルですが、
WebSocket は対応ブラウザも増えてきたし、
SPDY は次期 HTTP2.0 のベースとしても採用されそうな動きもあるので、
それぞれの特性をキチンと掴んだ上で、
両方をうまく取り入れるのが、今後求められるのかなと思います。

dotCloud で WebSocket

dotCloud が WebSocket に対応したという発表がありました。

dotCloud Announces Native Support for WebSockets | dotCloud Blog

dotCloud は多くの言語やミドルウエアに対応していて、 CLI からアプリがデプロイできる PaaS サービスといった感じです。
(Heroku なんかをイメージしてもらうとわかりやすいかも。)

早速、 Node.js + Socket.IO でどんなもんか試してみました。
アカウントは自分は結構前にとったんですが、今ならすぐ取得できるっぽいです。

CLI のインストール

専用の CLI があるのでそれをインストールするところから始めます。
http://docs.dotcloud.com/firststeps/install/ にある通り、 Python の pip で入れます。

Jxck$ sudo easy_install pip && sudo pip install dotcloud

インストールしたら、 dotcloud コマンドが使えるようになります。

最初に dotcloud コマンドを実行したら、 API key とやらを聞かれるので入力します。
API key は、メッセージの通り http://www.dotcloud.com/accounts/settings で確認できます。

Jxck$ dotcloud
Warning: .dotcloud/dotcloud.conf does not exist.
Enter your api key (You can find it at http://www.dotcloud.com/accounts/settings): xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
error: usage: dotcloud [-h]
                
                {info,status,scale,run,logs,versions,url,setup,list,stats,var,restart,alias,rollback,push,destroy,create,ssh,history}
                ...

エラってね?(汗


うーん、とりあえず -h したら、 steup がそれっぽいので、もう一度やってみる。

Jxck$ dotcloud setup
Enter your api key (You can find it at http://www.dotcloud.com/accounts/settings): xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

できたっぽい。 ~/.dotcloud/dotcloud.conf を見てみる。

Jxck$ cat .dotcloud/dotcloud.conf
{
    "url": "https://api.dotcloud.com/",
    "apikey": "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

こんなのができてる。

アプリ作成

dotCloud アプリを作成してデプロイします。
ここでは Node を使います。 Node を使ったアプリについては、

dotCloud - Node.js Service が参考になります。


まずは create コマンド。

Jxck$ dotcloud create wstest

(これは、ローカルに何かファイルを作ったりではないみたい)


好きなディレクトリを作って、 build file を作ります。名前は dotcloud.yml

Jxck$ mkdir dotcloud
Jxck$ cd dotcloud

dotcloud.yml はこんな感じ

www:
  type: nodejs
  approot: wstest

approot に実際にアプリがあるディレクトリを書きます。
ディレクトリ内には、実際のアプリと package.json, supervisord.conf などを配置します。
だからこの場合、こういう構成になります。

Jxck$ tree dotcloud
dotcloud
├── dotcloud.yml
└── wssample
    ├── package.json
    ├── server.js
    └── supervisor.conf

1 directory, 4 files


とりあえず普通の http サーバを作って動かしてみます。


サーバは server.js に実装します。しかし dotCloud ではエントリファイルは supervisor.conf で指定するため、
server.js である必要はないんですが、まあ慣習です。

また、 http サーバをデプロイする場合は、必ず 8080 ポートをリッスンする必要があるそうです。
(もちろん外からは 80 でアクセスできるので、リバース Proxy があるんでしょう)

server.js
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8080);
supervisor.conf

ここで、実装したサーバの起動方法を指定します。
npm start に対応してないのがちょっと残念ですが、
同じ方法で別の言語も動くのでまあ、しょうがないですね。
ここで指定するから、エントリファイルは server.js でなくても実際は大丈夫です。

[program:node]
command = node server.js
directory = /home/dotcloud/current

デプロイ

dotcloud コマンドでできます。
見た感じ rsync で飛ばしてるみたいですね。

$ dotcloud push wstest dotcloud/

.....

Deployment finished. Your application is available at the following URLs
www: http://wstest-jxck.dotcloud.com/

コマンドラインに出力されたアドレスでアクセスできます。


WebSocket

ではいよいよ、 WebSocket を使って試してみます。
ここでは Socket.IO を用いて、使用する通信を WebSocket のみにするため、
transport オプションに ['websocket'] を指定します。


静的ファイルの配信は express を用いました。(この程度なら express でも十分だった。)

package.json

依存するパッケージは package.json に書いておけば、デプロイ後に入れてくれます。

最初に npm init でプロジェクトを作り始めるとはかどります。

{
  "author": "Jxck",
  "name": "wssample",
  "version": "0.0.0",
  "repository": {
    "url": ""
  },
  "engines": {
    "node": "*"
  },
  "dependencies": {
    "express": "*",
    "socket.io": "*"
  },
  "devDependencies": {}
}
サーバ

サーバはこんな感じ。
ポイントは io.set() で、 transport を指定しています。
これで socket.io は websocket の接続確立に失敗してもフォールバックをしません。

var express = require('express')
  , io = require('socket.io')
  ;

var app = module.exports = express.createServer();

app.configure(function(){
  app.use(express.static(__dirname + '/public'));
});

io = io.listen(app);
io.configure('production', function() {
  io.set('transports', ['websocket']);
});

io.sockets.on('connection', function (socket) {
  console.log('connected');
  socket.on('msg send', function (msg) {
    socket.emit('msg push', msg);
    socket.broadcast.emit('msg push', msg);
  });
  socket.on('disconnect', function() {
    console.log('disconnected');
  });
});

app.listen(8080);
クライアント

現在どの通信方法で接続を確立しているかは、

以下の場合、 socket.socket.transport の中を見るとわかります。

var socket = io.connect();

socket.on('connect', function() {
  console.log('connected');
  console.log('transport is', socket.socket.transport);
  socket.emit('msg send', 'data');
  socket.on('msg push', function (msg) {
    console.log(msg);
  });
});

まとめ

今回のサンプルは以下にデプロイしています。

http://wstest-jxck.dotcloud.com/


ソースは以下。

https://github.com/Jxck/dotcloud_ws_test


実行結果は以下。

WebSocket で接続できていることが確認できます。


ということで、公表されたとおり WebSocket が通っているようですね。
WebSocket が使えない PaaS (heroku とか) はまだまだ多いので、
これはひとつ大きなアドバンテージになるかもしれません。


気になるところは

  • 速度
    • どのくらいでるのか。これはサーバとの物理的距離も関係。
  • 何使ってるの?
    • おそらく dotCloud のサーバ群には Proxy などがフロントに立っているんだろうと予想できるが、何を使っているのか。
    • ちなみにここに Nginx を使っている場合は、デフォルトだとできない、のでパッチなどが必要。(だから heroku は ws できないとされてる)


この辺も余裕があったら調べてみたいところですね。

あ、今更だけど、これはエイプリルネタじゃないです(汗
この記事、本当は 3 月中に書きたかった。。(月 1 でブログが目標)