蟻地獄

Twitterに書ききれない長めの文とか書くよ

GoでCometチャットサーバを書いてみた

最近流行りのGoでCometなチャットサーバを書いてみたよ!
いちおう動いてるっぽいけど正しいかは知らないよ!

http://www.cometeo.com:8080/

package main

import (
	"flag";
	"http";
	"log";
	"container/list";
	"strconv";
	"bytes";
	"json";
	"time";
)

type post struct {
	id uint64;
	name string;
	comment string;
	at *time.Time;
}

type chatData struct {
	lastId uint64;
	posts list.List;
	waits list.List;
}

type request interface {
	execute(data *chatData);
}

type addRequest struct {
	name string;
	comment string;
}

type getRequest struct {
	id uint64;
	resCh chan []post;
}

func (req addRequest) execute(data *chatData) {
	data.lastId++;
	p := post{data.lastId, req.name, req.comment, time.LocalTime()};
	data.posts.PushBack(p);
	if data.posts.Len() > 100 {
		data.posts.Remove(data.posts.Front());
	}

	for ch := range data.waits.Iter() {
		res := make([]post, 1);
		res[0] = p;
		ch.(chan []post) <- res;
	}
	data.waits.Init();
}

func (req getRequest) execute(data *chatData) {
	if req.id == data.lastId {
		data.waits.PushBack(req.resCh);
	}else{
		i := 0;
		e := data.posts.Front();
		for ; e != nil; i,e = i+1,e.Next() {
			if e.Value.(post).id > req.id {
				break;
			}
		}
		res := make([]post, data.posts.Len()-i);
		for i = 0; e != nil; i,e = i+1,e.Next() {
			res[i] = e.Value.(post);
		}
		req.resCh <- res;
	}
}

var requestCh = make(chan request)
var addr = flag.String("addr", ":8080", "http service address")

func backend() {
	var data chatData;
	for {
		req := <- requestCh;
		req.execute(&data);
	}
}

func add(c *http.Conn, req *http.Request) {
	requestCh <- addRequest{req.FormValue("name"), req.FormValue("c")};
	c.WriteHeader(http.StatusOK);
}

func get(c *http.Conn, req *http.Request){
	id,err := strconv.Atoui64(req.FormValue("id"));
	if err != nil {
		c.WriteHeader(http.StatusBadRequest);
	}else{
		q := getRequest{id, make(chan []post)};
		requestCh <- q;
		res := <- q.resCh;

		c.SetHeader("Content-Type", "application/json; charset=utf-8");
		var buf bytes.Buffer;
		buf.WriteString("{\"post\":[");
		last := len(res)-1;
		for i,val := range res {
			buf.WriteString("{\"id\":" + strconv.Uitoa64(val.id) 
				+ ",\"name\":" + json.Quote(val.name) 
				+ ",\"c\":" + json.Quote(val.comment) 
				+ ",\"at\":"+ json.Quote(val.at.String()) + "}");
			if(i != last){
				buf.WriteString(",");
			}
		}
		buf.WriteString("]}");
		c.Write(buf.Bytes());
	}
}

func main() {
	flag.Parse();

	http.Handle("/", http.FileServer("www", ""));
	http.Handle("/add", http.HandlerFunc(add));
	http.Handle("/get", http.HandlerFunc(get));

	go backend();

	err := http.ListenAndServe(*addr, nil);
	if err != nil {
		log.Exit("ListenAndServe:", err);
	}
}