[revel framework] websocket qua simple app demo (phần 1)
Bài đăng này đã không được cập nhật trong 8 năm
Trước revel mình chưa hề làm việc với websoket và khái niệm về nó mình cũng chỉ biết qua qua thôi. Nhưng trong report tuần này mình viết về nó(websocket) lại còn trên 1 framework cực kì lạ lẫm. Không phải vì mình giỏi đâu các bạn ạ, mà là vì Websocket đã được hỗ trợ tối đa trong framework này(revel). OK Chúng ta cùng tìm hiểu về nó nhé.
1. Giới thiệu
Về khoản websocket là j mình xin phép được bỏ qua vì nó không phải nội dung trọng tâm của bài viết này. Bài viết này mình chỉ muốn chỉ ra sự thú vị của websocket trong revel framework. Nó đã được hỗ trợ như là 1 kiểu action trong controller của các fw thông thường(PUT, PATCH, GET...) đó là kiểu WS.
2. Tạo simple app chat
đầu tiên bạn phải tạo router cho application ở conf/routes
GET /websocket/room WebSocket.Room
WS /websocket/room/socket WebSocket.RoomSocket
đây là file router của revel syntax của khai báo route là:
[METHOD] [URL Pattern] [Controller.Action]
OK vậy là đã có url cho app, h chúng ta sẽ tạp controller cho app.
//app/controlller/websocket.go
package controllers
import (
// core of revel framework
"github.com/revel/revel"
)
type WebSocket struct {
*revel.Controller
}
func (c WebSocket) Room() revel.Result {
}
func (c WebSocket) RoomSocket() revel.Result {
}
trong hàm get khi vào url chúng ta sẽ truyền vào 1 param làm tên cho người dùng trong method Room. và render nó ra view.
func (c WebSocket) Room(user string) revel.Result {
return c.Render(user)
}
còn đây là view cho ứng dụng:
//app/view/header.html
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" media="screen" href="/public/stylesheets/main.css">
<link rel="shortcut icon" type="image/png" href="/public/images/favicon.png">
<script src="/public/javascripts/jquery-1.5.min.js" type="text/javascript" charset="utf-8"></script>
<script src="/public/javascripts/templating.js" type="text/javascript" charset="utf-8"></script>
<script src="/public/javascripts/jquery.scrollTo-min.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
//app/view/websocket/Room.html
{{set . "title" "Chat room"}}
{{template "header.html" .}}
<h1>WebSocket — You are now chatting as {{.user}}
<a href="/">Leave the chat room</a></h1>
<div id="thread">
</div>
<div id="newMessage">
<input type="text" id="message" autocomplete="off" autofocus>
<input type="submit" value="send" id="send">
</div>
cú pháp ngoài trong view template của revel giống hệt với blade template trong laravel fw nên cũng không quá khó để hiểu về nó. h mình sẽ sẽ sang phần chính đó là xây dựng chat realtime. Trước tiên chúng ta thiết lập connect cho phía client.
//app/view/websocket/Room.html
{{set . "title" "Chat room"}}
{{template "header.html" .}}
<h1>WebSocket — You are now chatting as {{.user}}
<a href="/">Leave the chat room</a></h1>
<div id="thread">
<script type="text/html" id="message_tmpl">
<% if(event.Type == 'message') { %>
<div class="message <%= event.User == '{{.user}}' ? 'you' : '' %>">
<h2><%= event.User %></h2>
<p>
<%= event.Text %>
</p>
</div>
<% } %>
<% if(event.Type == 'join') { %>
<div class="message notice">
<h2></h2>
<p>
<%= event.User %> joined the room
</p>
</div>
<% } %>
<% if(event.Type == 'leave') { %>
<div class="message notice">
<h2></h2>
<p>
<%= event.User %> left the room
</p>
</div>
<% } %>
<% if(event.Type == 'quit') { %>
<div class="message important">
<h2></h2>
<p>
You are now disconnected!
</p>
</div>
<% } %>
</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">
// Create a socket
var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')
// Display a message
var display = function(event) {
$('#thread').append(tmpl('message_tmpl', {event: event}));
$('#thread').scrollTo('max')
}
// Message received on the socket
socket.onmessage = function(event) {
console.log(event)
display(JSON.parse(event.data))
}
$('#send').click(function(e) {
var message = $('#message').val()
$('#message').val('')
socket.send(message)
});
$('#message').keypress(function(e) {
if(e.charCode == 13 || e.keyCode == 13) {
$('#send').click()
e.preventDefault()
}
})
</script>
còn bên phía server. Trước tiên ta phải include thêm package websocket thư viện do revel hỗ trợ. và 1 thư viện hỗ trợ kết nối connect(mình sẽ có bài mổ sẻ thư viện này sau).
//app/controllers/websocket.go
import (
//thu vien de van hanh websocket
"golang.org/x/net/websocket"
//thu vien nhan class revel
"github.com/revel/revel"
// thu vien chat do user viet
"/app/tuanna"
)
//app/tuanna/tuanna.go
package tuanna
import (
"container/list"
"time"
)
type Event struct {
Type string // "join", "leave", or "message"
User string
Timestamp int // Unix timestamp (secs)
Text string // What the user said (if Type == "message")
}
type Subscription struct {
Archive []Event // All the events from the archive.
New <-chan Event // New events coming in.
}
// Owner of a subscription must cancel it when they stop listening to events.
func (s Subscription) Cancel() {
unsubscribe <- s.New // Unsubscribe the channel.
drain(s.New) // Drain it, just in case there was a pending publish.
}
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 (
// Send a channel here to get room events back. It will send the entire
// archive initially, and then new messages as they come in.
subscribe = make(chan (chan<- Subscription), 10)
// Send a channel here to unsubscribe.
unsubscribe = make(chan (<-chan Event), 10)
// Send events here to publish them.
publish = make(chan Event, 10)
)
// This function loops forever, handling the chat room pubsub
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()
}
// Helpers
// Drains a given channel of any messages.
func drain(ch <-chan Event) {
for {
select {
case _, ok := <-ch:
if !ok {
return
}
default:
return
}
}
}
Việc phân tích thư viện này mình xin giành cho bài viết sau. H chúng ta sẽ chỉ include vào để sử dụng. Một điều rất hay là revel fw áp dụng theo mô hình dependence injection nên các phần rất tách bạch với nhau(tiện cho những người lười như mình ). h chúng ta chỉ việc lôi các hàm ra và sử dụng trong controller xử lí logic websocket.
// This function loops forever, handling the chat room pubsub
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
}
}
}
}
}
Tổng kết
Bản thân mình thấy Golang vẫn còn khá là thô so với các framework phổ biến. nhưng nó cũng có 1 điểm hay đó là hỗ trợ cho websocket gần như tối đa. coi nó như là 1 route thông thường. P/s: bài viết này lấy ví dụ từ trang chủ revel, mình đọc code và trình bày lại theo ý hiểu của mình, bạn muốn nhìn tổng quát hơn có thể lên trang chủ để đọc. Bài viết lần sau mình sẽ đi vào mổ sẻ thư viện chat room của revel.
Tài liệu liên quan
All rights reserved