kumama go言語とかgolangとかGAEとかネットサービスとかその他色々・・・

25Dec/09Off

WebSocket:golang chat

メーリークリスマス!

と、言う事で(笑)WebSocketのchatを色々と実装。
って言っても切り貼りでベースは、WebSocketでChatを作ってみた

http://kumama.org/chat/
で実際に動かしています。
メッセージをDBに書いてたりWSサポートしてないブラウザでも一応掲示板として動いたりといった実装を追加。
# 止めました

HTML5の規格ではあるもののWebSocketは、
現状Chrome β(release 4.0.249.0から)でしかサポートされていないので、
βを入れるかChromiumのdaily buildを突っ込まないとWebSocketのお試しには成りません。
ブラウザを2つ立ち上げて片方で書き込んだメッセージがもう片方に届くさまを見てください。

メッセンジャーとかも作れそうな感じ有ってFlashとかAdobe Air使うよりは全然まともな気がします。

コードは以下に。

template使ったりsqlite使ったり色々やってみるとgo言語の理解が進む感じ。

書いていて「をを!」って思った事は小ネタ集として明日にでもまた書きます。
あー、sqliteは微妙に下記の方がいい感じだったので前に紹介したのは使ってなかったりw

http://github.com/phf/go-sqlite3
http://github.com/phf/go-db

cgo経由でのCライブラリの呼び出しは色々疑問も有るものの、現状DBが無いので仕方なし。っていうか、presistent storegeが欲しいって事かなぁ。

嗚呼、golangもreleaseブランチじゃなくてdefaultブランチじゃないとだめですよ。

package main

import (
    "http";
    "io/ioutil";
    "strings";
    "bytes";
    "log";
    "db";
    "db/sqlite3";
    "template";
    "websocket";
    "os";
//  "io";
)

const createTableSql = "CREATE TABLE messages (t DATETIME DEFAULT CURRENT_TIMESTAMP, s VARCHAR(4096));";
const MAXREQSIZE = 0x1000;

func htmlEscape(s string) string {
    var buf bytes.Buffer
    template.HTMLEscape(&buf, strings.Bytes(s))
    return buf.String()
}

func routeDefault(){

    templateBytes, _ := ioutil.ReadFile("index.html");
    var templateBuffer = bytes.NewBuffer(templateBytes);
    var templateStr = templateBuffer.String();
    defaultTemplate := template.New(nil);
    defaultTemplate.SetDelims("{{", "}}");
    defaultTemplate.Parse(templateStr);

    dbh, _ := sqlite3.Open("test.db" + "?" + sqlite3.FlagsURL(sqlite3.OpenReadWrite));
    conn := dbh.(db.ClassicConnection);

    http.Handle("/", http.HandlerFunc(func(c *http.Conn, req *http.Request) {

        stm, err := conn.Prepare("select s from (select t,s from messages order by t desc limit 20) order by t asc;");
        if (err != nil ){
            log.Stderr(err);
        }
        cur, err := conn.ExecuteClassic(stm);
        data, err := db.ClassicFetchAll(cur);

        params         := new(struct { title string; messages [20]string });
        params.title       = "一行掲示板w";
        i := 0;
        for _, msg := range data {
            params.messages[i],_ = msg[0].(string);
            i++;
        }

        defaultTemplate.Execute( params, c);
    }));

}

func routeChat() {

    // initialize
    queue := make(chan string);
    listen_chan := make(map [*websocket.Conn] chan string);

    go func() {
        for {
            message := <- queue;
            for ws, _ := range listen_chan {
                ws.Write(bytes.NewBufferString(message).Bytes());
            }
        }
    }();

    read := func(ws *websocket.Conn, errChan chan os.Error) {
        buf := make([]byte, MAXREQSIZE);
        for {
            log.Stdout("Reading...
");
            l, err := ws.Read(buf);
            if err != nil {
                log.Stderr(err);
                errChan <- err;
                return;
            }
            log.Stdoutf("received: %s
", buf[0:l]);
            queue <- string(buf[0:l]);
            insertMessage( htmlEscape(string(buf[0:l])) );
        }
    };

    OnConnected := func(ws *websocket.Conn) {
        log.Stdout("Connection Opend
");
        mesgChan := make(chan string);

        listen_chan[ws] = mesgChan;

        errChan := make(chan os.Error);

        go read(ws, errChan);

        err := <-errChan;

        log.Stderrf("Done %d
", err);
        listen_chan[ws] = nil, false;
    };

    http.Handle("/chat/ws", websocket.Handler(OnConnected));

    http.Handle("/chat/write", http.HandlerFunc(func(c *http.Conn, req *http.Request) {

        log.Stdoutf( "%v
", req );

        var message string = htmlEscape( req.FormValue( "m" ) );

        queue <- message;
        insertMessage( message );

        http.Redirect(c, "/chat/", 302);

    }));

}

func initialize(){

    conn, _ := sqlite3.Open("test.db" + "?" + sqlite3.FlagsURL(sqlite3.OpenCreate));
    _, err := db.ExecuteDirectly(conn, createTableSql);
    if (err != nil){
        log.Stderr(err);
    }

}

func insertMessage(message string) {

    conn, _ := sqlite3.Open("test.db" + "?" + sqlite3.FlagsURL(sqlite3.OpenReadWrite));
    db.ExecuteDirectly( conn,
        "INSERT INTO messages values (CURRENT_TIMESTAMP, ?)", message );

}

func main() {

    initialize();

    routeDefault();
    routeChat();

    err := http.ListenAndServe(":12345", nil);
    if err != nil {
        panic("ListenAndServe: ", err.String())
    }

}

htmlのテンプレートは、

<html>
<head>
<title>{{title|str}}</title>
</head>
<body onload="document.f.m.focus()" >
<script>
var ws = null;
try {
  ws = new WebSocket("ws://kumama.org:443/chat/ws");
} catch(e){}
ws.onopen = function(){};
ws.onmessage = function(message) {
  var txtNode = document.createTextNode(message.data);
  var brNode = document.createElement('br');
  var cnode = document.getElementById("content");
  cnode.appendChild(txtNode);
  cnode.appendChild(brNode);
};
window.onunload = function() { if( ws != null ){ws.close();} };
function send(){
  if( ws != null && ws.readyState == WebSocket.OPEN ){
    ws.send(document.f.m.value);
    document.f.m.value='';
    document.f.m.focus();
    return false;
  }
  return true;
};
</script>

<div id=content>
{{.repeated section messages}}
  {{.section @}}
      {{ @|str }}<br />
  {{.end}}
{{.end}}
</div>

<form name=f method=post action="/chat/write" name=read onsubmit="return send()" > 
	<input type=hidden name=check value="check" /> 
	<input name=m /> 
	<input type=submit value="post" /> 
</form> 

</body>
</html>
Filed under: golang Comments Off