[Go言語] revelでchatを作る

conf/routes

GET		/websocket/room		  WebSocket.Room
WS      /websocket/room/socket   WebSocket.RoomSocket 

app/views/WebSocket/room.html

{{set . "title" "Chat room"}}
{{template "header.html" .}}

<h1>WebSocket - You are now chatting as {{.user}} <a href="/">Leave</a></h1>

<div id="thread">
	<script type="text/html" id="message_tmpl">
		{{raw "<%"}} if(event.Type == 'message') { %>
			<div class="message <%= event.User == '{{.user}}' ? 'you' : '' %>">
				<h2>{{raw "<%"}}= event.User %></h2>
				<p>
					{{raw "<%"}}= event.Text %>
				</p>
			</div>
		{{raw "<%"}} } %>
		{{raw "<%"}} if(event.Type == 'join') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> joined the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'leave') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> left the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'quit') { %>
			<div class="message important">
				<h2></h2>
				<p>
					You are disconnected!
				</p>
			</div>
		{{raw "<%"}} }  %>
	</script>
</div>

<div id="newMessage">
	<input type="text" id="message" autocomplete="off" autofocus>
	<input type="submit" value="send" id="send">
</div>

<script type="text/javascript">

	var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

	var display = function(event){
		$('#thread').append(tmpl('message_tmpl', {event: event}));
		$('#thread').scrollTo('max')
	}

	socket.onmessage = function(event){
		display(JSON.parse(event.data))
	}

	$('#send').click(function(e){
		var message = $('#message').val()
		$('#message').val('')
		socket.send(JSON.stringify(message))
	})

	$('#message').keypress(function(e){
		if(e.charCode == 13 || e.keyCode == 13){
			$('#send').click()
			e.preventDefault()
		}
	})
</script>

app/views/header.html

    <script src="/public/js/jquery.scrollTo-min.js"></script>
    <script src="/public/js/templating.js"></script>

chatroom.go

package chatroom

import (
	"container/list"
	"time"
)

type Event struct {
	Type string
	User string
	Timestamp int
	Text string
}

type Subscription struct {
	Archive []Event
	New <-chan Event
}

func (s Subscription) Cancel(){
	unsubscribe <- s.New
	drain(s.New)
}

func newEvent(typ, user, msg string) Event {
	return Event{typ, user, int(time.Now().Unix()), msg}
}

func Subscribe() Subscription {
	resp := make(chan Subscription)
	subscribe <- resp
	return <- resp
}

func Join(user string){
	publish <- newEvent("join", user, "")
}

func Say(user, message string){
	publish <- newEvent("message", user, message)
}

func Leave(user string){
	publish <- newEvent("leave", user, "")
}

const archiveSize = 10

var (
	subscribe = make(chan (chan<- Subscription), 10)

	unsubscribe = make(chan (<-chan Event), 10)
	publish = make(chan Event, 10)
)

func chatroom(){
	archive := list.New()
	subscribers := list.New()

	for {
		select {
		case ch := <-subscribe:
			var events []Event
			for e := archive.Front(); e != nil; e = e.Next(){
				events = append(events, e.Value.(Event))
			}
			subscriber := make(chan Event, 10)
			subscribers.PushBack(subscriber)
			ch <- Subscription{events, subscriber}
		case event := <-publish:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next(){
				ch.Value.(chan Event) <- event
			}
			if archive.Len() >= archiveSize {
				archive.Remove(archive.Front())
			}
			archive.PushBack(event)
		case unsub := <-unsubscribe:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next(){
				if ch.Value.(chan Event) == unsub {
					subscribers.Remove(ch)
					break
				}
			}
		}
	}
}

func init(){
	go chatroom()
}

func drain(ch <-chan Event){
	for {
		select {
		case _, ok := <-ch:
			if !ok {
				return
			}
		default:
			return
		}
	}
}

websocket.go

package controllers

import (
	"github.com/revel/revel"
	"app/app/chatroom"
)

type WebSocket struct {
	*revel.Controller
}

func (c WebSocket) Room(user string) revel.Result {
	user = "hpscript"
	return c.Render(user)
}

func (c WebSocket) RoomSocket(user string, ws revel.ServerWebSocket) revel.Result {

	if ws == nil {
		return nil
	}

	subscription := chatroom.Subscribe()
	defer subscription.Cancel()

	chatroom.Join(user)
	defer chatroom.Leave(user)

	for _, event := range subscription.Archive {
		if ws.MessageSendJSON(&event) != nil {
			return nil
		}
	}

	newMessages := make(chan string)
	go func(){
		var msg string
		for {
			err := ws.MessageReceiveJSON(&msg)
			if err != nil {
				close(newMessages)
				return
			}
			newMessages <- msg
		}
	}()

	for {
		select {
		case event := <-subscription.New:
			if ws.MessageSendJSON(&event) != nil {
				return nil
			}
		case msg, ok := <-newMessages:
			if !ok {
				return nil
			}
			chatroom.Say(user, msg)
		}
	}
	return nil
}

public/js/templating.js

{{set . "title" "Chat room"}}
{{template "header.html" .}}

<h1>WebSocket - You are now chatting as {{.user}} <a href="/">Leave</a></h1>

<div id="thread">
	<script type="text/html" id="message_tmpl">
		{{raw "<%"}} if(event.Type == 'message') { %>
			<div class="message <%= event.User == '{{.user}}' ? 'you' : '' %>">
				<h2>{{raw "<%"}}= event.User %></h2>
				<p>
					{{raw "<%"}}= event.Text %>
				</p>
			</div>
		{{raw "<%"}} } %>
		{{raw "<%"}} if(event.Type == 'join') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> joined the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'leave') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> left the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'quit') { %>
			<div class="message important">
				<h2></h2>
				<p>
					You are disconnected!
				</p>
			</div>
		{{raw "<%"}} }  %>
	</script>
</div>

<div id="newMessage">
	<input type="text" id="message" autocomplete="off" autofocus>
	<input type="submit" value="send" id="send">
</div>

<script type="text/javascript">

	var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

	var display = function(event){
		$('#thread').append(tmpl('message_tmpl', {event: event}));
		$('#thread').scrollTo('max')
	}

	socket.onmessage = function(event){
		display(JSON.parse(event.data))
	}

	$('#send').click(function(e){
		var message = $('#message').val()
		$('#message').val('')
		socket.send(JSON.stringify(message))
	})

	$('#message').keypress(function(e){
		if(e.charCode == 13 || e.keyCode == 13){
			$('#send').click()
			e.preventDefault()
		}
	})
</script>

<% ●● %>: 構文
<%= ■■ %>: 出力

これは凄し