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>
<% ●● %>: 構文
<%= ■■ %>: 出力
これは凄し