へぼいいいわけ このページをアンテナに追加 RSSフィード Twitter

2010年12月22日

WebSocketを使用したWebアプリ「unkartop」を作った

unkartop

※WebSocketが動くブラウザで見ないと何も起きません。Chromeがオススメ


なにこれ

unkarで表示された2chスレッドをリアルタイムで集計して表示します。簡単に言うとapachetopのパクリ。


作った理由

デスクトップの片隅に端末を開いて、apachetopサーバを監視し続けると出来る男の気分になれるので、毎日のようにダサい端末の黒地に白字が流れるのを見ていたら、そもそも端末でやる必要がない事に気がついたため作ってみました。

あと、unkarへのアクセス可視化することで、Googleトレンドのように使える事を若干ながら期待しています。


動作イメージ

f:id:heiwaboke:20101222215658p:image

最近のブラウザは公式の着せ替え機能が充実しているため、かなりオシャレになりました。


詳細

このunkartopですが、今流行のWebSocketプロトコルを利用しています。

WebSocketを使用するに当たり、Go言語でWebSocket用サーバを構築しています。ソースコードは下に晒しておきます。

やっていることは単純で、unkar本体(php)で2chスレッドが表示された際に、スレッドURLやタイトルを格納した連想配列JSONに変換してredisサーバに格納し、それを一秒ごとに以下のGo言語で書かれたWebSocketサーバが読みだし、適当にまとめてブラウザに送り出しています。

クライアント側は取得したデータを集計して、jQueryを使ってテーブルに当てはめているだけです。


鯖ソース(Go言語)

// うんかーTOPコマンドもどき
package main

import (
	"fmt"
	"json"
	"redis"
	"os"
	"strconv"
	"http"
	"runtime"
	"time"
	"websocket"
)

type Error string
func (this Error) String() string { return string(this) }

const (
	RedisAddr	string = "127.0.0.1:1984"
	WebSocketAddr	string = ":12345"
)

var listen_chan map[*websocket.Conn]chan os.Error

func main() {
	runtime.GOMAXPROCS(4)
	listen_chan = make(map[*websocket.Conn]chan os.Error)
	go timeCallback()
	http.Handle("/top", websocket.Handler(webHandler))
	err := http.ListenAndServe(WebSocketAddr, nil)
	if err != nil {
		panic(err.String())
	}
}

func timeCallback() {
	// redis接続の用意
	client := new(redis.Client)
	client.Addr = RedisAddr
	// 1秒毎
	tick := time.Tick(1000 * 1000 * 1000 * 1)
	for {
		<- tick
		data, err := getData(client)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s\n", err.String())
			continue
		}
		for socket, errchan := range listen_chan {
			go writeData(data, socket, errchan)
		}
	}
}

func getData(client *redis.Client) ([]byte, os.Error) {
	bval, err := client.Get("unkocount")
	if err != nil { return nil, err }
	client.Set("unkocount", []byte("0"))
	size, _ := strconv.Atoi(string(bval))
	if size > 10000 {
		size = 10000
	} else if size < 1 {
		return nil, Error("hogehoge")
	}
	dbvals, rrerr := client.Lrange("unko", 0, size)
	if rrerr != nil { return nil, rrerr }

	// 〜省略〜

	data, jeerr := json.Marshal(dbvals)
	if jeerr != nil { return nil, jeerr }
	return data, nil
}

func writeData(data []byte, con *websocket.Conn, errchan chan os.Error) {
	_, werr := con.Write(data)
	if werr != nil {
		errchan <- werr
	}
}

func webHandler(ws *websocket.Conn) {
	errchan := make(chan os.Error)
	listen_chan[ws] = errchan
	err := <- errchan
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err.String())
	}
	close(errchan)
	listen_chan[ws] = nil, false
}

※データのまとめ処理部分は省略しています。その都合でそのままだとコンパイルできないかも。

※redisのライブラリを使わせてもらっています。https://github.com/hoisie/redis.go


感想みたいなもの

Go言語でWebSocketを使用した日本語のサンプルが少ないように感じました。妙にnode.jsのサンプルっぽいものが多かったような気がします。よく見ていませんが。

今更ながら、jQueryって便利ですね。今回初めて使用したのですが、cssセレクタとやらが簡単に使用できすぎて他のライブラリが使えなくなりそうです。