masato-ka's diary

日々思ったこととか、やったことの備忘録。

フィジカルコンピューティングクラスタとかDjangoクラスタのみんなもWebSocketデビューするといいよ!

今回はPythonのWEBアプリケーションフレームワークであるDjangoでWebSocket通信を行う方法を紹介します。

はじめに

事の発端ですが、Arduinoなどのマイコンボードに接続したセンサの値をサーバーに集約してごにょごにょしたいな〜と以前から考えていました。
 こういった場合、個人ユースでよく用いられるのが、CGIにPOSTやGETでデータを送りつける方法だと思います。しかし、わずか数バイトのデータを送るにヘッダや何やらくっつけたりしていたら、本来送りたい情報よりも付属のデータの方が多くなってしまいます。また、逆にサーバからマイコンボードなどにデータを送りたい場合、マイコン側からポーリングするといった方法を取らなくてはいけません。AjaxやCometの様な手法でも従来のWEBサーバの技術を応用して上記のようなことを実現しようとすると同様の問題にぶち当たると思います。もちろん一からソケットプログラムを書いてプロトコルを決めて・・・としても良いのですが手間だしあまり汎用性が無いように感じます。

 そこで、今回思い立ったのが最近密かなブーム、、、、になってるどうかは判りませんが、従来のAjaxやCometに変わる技術として注目されているWebSocketを実現させようと思いました。
 AjaxやCometとWebSocketとの違いや、そもそもWebSocketとの違いなどの話は他の詳しいサイトに譲りますが、簡単に言うと、AjaxやCometと違いWebSocketは最初にWebサーバに接続した際にセッションを接続させたままにしておき、TCPソケットのようにreadとwriteでサーバ/クライアントの双方向でデータのやりとりを行う事ができます。AjaxやCometは1リクエストごとにセッションを張り直し、さらにクライアント側が起因となる通信士かできませんでした(サーバ起因でクライアントに接続できない。)

問題のWebSocketですが、2012年の1月2011年の12月にRFCの最終草案がまとまりました。この草案が決まるまでに幾つかの仕様が存在しており、今回はRFCとはちょっと違った仕様のWebSocketとなります。*1

今回の構成

今回はMac OS X snow Lepperd上にPython2.6, Django, django-websocketをインストールしてDjangoフレームワークでWebSocketを実現します。
選定の理由は自分の主要言語がPythonで使えるWebフレームワークがDjangoだったからです。DjangoはそのままではWebSocketを扱うことができません。そこで、django-websocketという拡張モジュールをインストールします。この拡張モジュールですが、最終リリースが2010年の7月だったりバージョンが0.3.0だったりで怪しさが漂ってますがRFCの草案もまとまったことだしそのうちメンテされると信じてます。django-pythonのpipのページです。
それでは早速django-websocketが動くところまで設定しましょう。

環境構築

Mac PortでDjangoをインストール!

MacPortに関係する環境変数などは割愛させていただきます。最近はHomebrewなんかが人気ですが、そちらでもDjangoが入れば問題ないかと。
Python2.6とDjangoをさくっとインストールしてしまって下さい。あとpipが入っていない場合は併せてpipもインストールして下さい。

django-websocketのインストール。

django-websocketをMac環境にインストールする場合、PythonCPAN的なpipを使ってインストールします。
django-websocketのインストール。
>||$pip install django-websocket|

permission denieとかで怒られるときはsudoでも先頭に付けときましょう。

ここまでで、WebSocketもしゃべれるDjangoな環境ができあがりました。

Djangoの設定

WebSocket用のプロジェクトをDjangoで作成しましょう。今回はプロジェクト名をwebsocketにします。Djangonoの設定がうまくいっていれば以下のコマンドでプロジェクトが作成できるはずです。
>||$django-admin.py startproject websocket|

permission deniedとか怒られるときはdjango-admin.pyのパーミッション確認して、実行権(x)が付いているか確認して下さい。
上記コマンドが正常に実行できたらwebsocketディレクトリに移動してsettings.pyを以下の様に編集します。

1.MIDDLEWARE_CLASSESにdjango_websocket.middleware.WebSocketMiddlewareを追加
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django_websocket.middleware.WebSocketMiddleware'
)
2.INSTALLED_APPSにdjango_websocketを追加
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_websocket'
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)
3.WEBSOCKET_ACCEPT_ALL= Trueを追加

適当なところに追加して下さい。

WEBSOCKET_ACCEPT_ALL= True

以上です。これでDjangoで作成したプロジェクトがWebSocketを受信できるようになります。

Djangoのアプリケーションを作成する。

websocketディレクトリ内で以下のコマンドを使いDjangoのアプリケーションを作成します。
>||$python manager.py startup echo|

そうするとアプリケーションechoのディレクトリが作成されるのですかさずechoディレクトリに移動して下さい。
適当なエディタでview.pyを開いて以下のコードを記述して下さい。
view.py

from django.http import HttpResponse
from django_websocket import accept_websocket

def modify_message(message):
    return message.lower()

@accept_websocket
def websocket(request):
    if not request.is_websocket():
        message = request.GET['message']
        message = modify_message(message)
        return HttpResponse(message)
    else:
        for message in request.websocket:
            message = modify_message(message)
            request.websocket.send(message)

django-websocketのページに載っているサンプルコードと同じものです。通常のGETリクエストが来た場合はHttpレスポンスを返しますが、websocketが来た場合は通信を確立してwebsocketでデータを返しています。django-websocketはこのようにDjangoのview.pyに記述されたメソッドをラップして、requestオブジェクトにwebsocketのメソッドを拡張させて利用します。非常に簡単にWebSocket対応サーバを構築することが可能です。

view.pyにURLをマッピングそしてサーバーを起動!

websocketディレクトリ内のurls.pyを開いて以下の様に記述しましょう。

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'websocket.views.home', name='home'),
    # url(r'^websocket/', include('websocket.foo.urls')),
    url(r'^websocket/','websocket.echo.views.websocket')
    # Uncomment the admin/doc line below to enable admin documentation:
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    # url(r'^admin/', include(admin.site.urls)),
)

上記設定を行い保存したら以下のコマンドを実行してServerを起動して下さい。
>||$python manage.py runserver --multithreaded|

こうすると開発用サーバーが起動します。
http://localhost:8000/websocket/?message=TESTにアクセスして下さい。
画面に"test"と表示されれば成功です。

あとはJavaScriptで適当なWebSocketクライアントを作りWebSocketを試してみて下さい。
実装方法は他のページに沢山載ってます。私は以下のページを参考にさせていただきました。


WebSocketによるクライアント=サーバー通信


注意点はSafari5.0.1を使うことです。
django-websocketが実装しているWebSocketの実装はdraft-hixie-thewebsocketprotocol-76またはdraft-ietf-hybi-thewebsocketprotocol-00と呼ばれるもののどちらかですおなじものらしいです。*2Wikipedia様のWebSocketのページにはこの2つのプロトコルに対応したブラウザとしてSafari5.0.1でした。他にもCrhome6とかありますが。。。ちなみに最新版のCrhome16とFireFox10で試しましたが動作しませんでした。*3

あと、DjangoのフロントとしてApacheを使う場合はmod_wisgiではなくmod_pythonを使わないと行けないようです。このあたりもそもそもWebSocketを扱えるWebサーバの構成が限られているようです。

最後に、今回自分の環境で作ったDjangoプロジェクトをbitbucketに公開しました。


bibucket


よかったら使って下さい。。。
追記:コードを修正して、2つのブラウザからアクセスした場合、ブラウザの更新がもう片方に反映されるようになりました。よりWebSocketの動きが判りやすくなったと思います。

まとめ

    1. WebSocketを使うとコネクションを張ったままにできる。
      1. WebSocketを使うとサーバーからのデータの送信が実現できる。
      2. データ送るときの付加情報が少なくて済む。
      3. 組み込み機器に使うと便利そう。(とおいらが思ってる)
    2. Djangoの拡張django-websocket
      1. viewのメソッドをラップするだけで簡単にWebSocket対応アプリを作れる。
      2. ただしRFC 6455とは違う古い実装のWebSocketである。
      3. 今後のメンテナンスに期待!

う〜ん使いやすいし自分でdjango-websocketを修正するってのも有りですね。
次回はArduinoとかmbedとかからwebsocketに接続する方法でもやりますかね〜。
あとはApacheとかNginxとかをフロントに置いて実行かな。NginxもWebSocketそのままじゃあつかえないっぽいし。。。以下次回に続く。。。

*1:RFC 6455とは互換性がありません。。。たぶん。

*2:ご指摘ありがとうございます。

*3: 2012/2/5追記:その後django-websocketのコードを観たところ、protocolversion 75と76で処理を分けていたので0.3.0では75,76に対応しているようです。