Source

bereal / bereal.go

package main

import (
	"code.google.com/p/go.net/websocket"
	//"github.com/garyburd/redigo/redis"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

const (
	STATIC_ROOT    string = "/home/yml/Developments/python/bereal/var/static"
	STATIC_URL     string = "/static"
	DJANGO_SERVER string = "http://127.0.0.1:8000"
	REALTIME_URL   string = "/auth_ping/"
	GO_ADDR string = ":8080"
)

// Websocket Connection
type connection struct {
	// The websocket connection.
	ws *websocket.Conn

	// Redis Channels the connection subscribes to
	channels []string

	// Buffered channel of outbound messages.
	send chan string
}

type PubSubInfo struct {
	user_id  int
	channels []string
}


// authenticationCheck  contact the django backend and veryfy that the user
// that issued the websocket connection is authenticated. Django returns
// a json string with the user_id and the list of redis pubsub channel to 
// connect to this websocket.
func (c *connection) authenticationCheck(authUrl string) (is_authenticated bool) {
	is_authenticated = false
	// Craft a request with the same cookie than the current websocket
	client := &http.Client{}
	req, err := http.NewRequest("GET", authUrl, nil)
	if err != nil {
		log.Fatal(err)
	}
	for _, cookie := range c.ws.Request().Cookies() {
		req.AddCookie(cookie)
	}
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode == 200 {
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
		log.Println("user is authenticated. pubsubinfo:", string(body))
		var info = &PubSubInfo{}
		json.Unmarshal(body, info)
		c.channels = info.channels
		is_authenticated = true
	} else {
		log.Println("Not authorized")
	}
	return
}

func (c *connection) reader() {
	log.Println("reader")
	for {
		var message string
		err := websocket.Message.Receive(c.ws, &message)
		if err != nil {
			break
		}
		h.broadcast <- message
	}
	c.ws.Close()
}

func (c *connection) writer() {
	log.Println("Writer")
	for message := range c.send {
		err := websocket.Message.Send(c.ws, message)
		if err != nil {
			break
		}
	}
	c.ws.Close()
}

func djangoAuthWebsocketHandler(ws *websocket.Conn) {
	log.Println("djangoAuthWebsocketHandler")
	c := &connection{send: make(chan string, 256), ws: ws}
	is_authenticated := c.authenticationCheck(DJANGO_SERVER + REALTIME_URL)
	if is_authenticated {
		h.register <- c
		defer func() { h.unregister <- c }()
		go c.writer()
		c.reader()
	}
}

// hub is used to do the booking off all open websocket connections
type hub struct {
	// Registered connections.
	connections map[*connection]bool

	// Inbound messages from the connections.
	broadcast chan string

	// Register requests from the connections.
	register chan *connection

	// Unregister requests from connections.
	unregister chan *connection
}

func (h *hub) run() {
	log.Println("Start the hub")
	for {
		select {
		case c := <-h.register:
			log.Println("Register:", c)
			h.connections[c] = true
		case c := <-h.unregister:
			log.Println("Unregister", c)
			delete(h.connections, c)
			close(c.send)
		case m := <-h.broadcast:
			log.Println("broadcast", m)
			for c := range h.connections {
				select {
				case c.send <- m:
				default:
					delete(h.connections, c)
					close(c.send)
					go c.ws.Close()
				}
			}
		}
	}
}

var h = hub{
	broadcast:   make(chan string),
	register:    make(chan *connection),
	unregister:  make(chan *connection),
	connections: make(map[*connection]bool),
}

func main() {
	dstUrl, err := url.Parse(DJANGO_SERVER)
	if err != nil {
		log.Fatal(err)
	}
	go h.run()
	http.Handle("/ws", websocket.Handler(djangoAuthWebsocketHandler))
	// serve the static files located in STATIC_ROOT
	http.Handle(STATIC_URL, http.FileServer(http.Dir(STATIC_ROOT)))
	// Proxy all the requests to the django app
	reverse_proxy := httputil.NewSingleHostReverseProxy(dstUrl)
	http.Handle("/", reverse_proxy)

	log.Println("start bereal go server", GO_ADDR)
	if err = http.ListenAndServe(GO_ADDR, nil); err != nil {
		log.Fatal(err)
	}
}