웹소켓 익힌 기념으로 go언어로 미리 준비된 방 3개짜리 채팅방 만들어봄. 접속하면 일단 랜덤하게 이름 만들어서 세션에 저장한 다음, 방 골라서 들어가게 만들어놓음. html 템플릿쪽은 아직 안 만듬 ㅋㅋㅋㅋ 진짜 초보적인 형태라 앞으로 살 붙일게 많다.
- main.go
package main
import (
"log"
"net/http"
"./app"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
var store = sessions.NewCookieStore([]byte("ASLDFKN23L4N1L2JKSDLKFJA1312KJ3H12KJ"))
func main() {
router := mux.NewRouter()
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
app.ServeRoot(w, r, store)
})
router.HandleFunc("/{room}", func(w http.ResponseWriter, r *http.Request) {
app.ServeRoom(w, r, store)
})
router.HandleFunc("/{room}/ws", func(w http.ResponseWriter, r *http.Request) {
app.ServeWS(w, r, store)
})
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
log.Fatal(http.ListenAndServe(":9999", router))
}
- app/handlers.go
package app
import (
"html/template"
"net/http"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/gorilla/websocket"
)
const sessionName = "chatsession"
var errMsg = []byte("Oops! Something Wrong.")
func ServeRoot(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore) {
if name, err := login(&w, r, store); err != nil {
w.Write(errMsg)
} else if r.Method == "GET" {
data := make(map[string]string)
data["Title"] = "Home"
data["Name"] = name
template.Must(template.ParseFiles("templates/index.html")).Execute(w, data)
} else if r.Method == "POST" {
r.ParseForm()
room := r.PostForm.Get("room")
http.Redirect(w, r, "/"+room, http.StatusFound)
} else {
w.Write(errMsg)
}
}
func ServeRoom(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore) {
if name, err := login(&w, r, store); err != nil {
w.Write(errMsg)
} else {
roomName := mux.Vars(r)["room"]
if _, ok := rooms[roomName]; !ok {
w.Write(errMsg)
} else {
data := make(map[string]string)
data["Title"] = roomName
data["Name"] = name
data["WS"] = "ws://" + r.Host + "/" + roomName + "/ws"
template.Must(template.ParseFiles("templates/room.html")).Execute(w, data)
}
}
}
func ServeWS(w http.ResponseWriter, r *http.Request, store *sessions.CookieStore) {
name, err := login(&w, r, store)
room := rooms[mux.Vars(r)["room"]]
upgrader := new(websocket.Upgrader)
conn, err := upgrader.Upgrade(w, r, nil)
if err == nil {
client := &_client{name, room, conn, make(chan []byte, 256)}
room.chRegister <- client
go client.write()
go client.read()
}
}
- app/room.go
package app
var rooms = make(map[string]*_room)
func init() {
rooms["go"] = newRoom("go")
rooms["java"] = newRoom("java")
rooms["python"] = newRoom("python")
for _, room := range rooms {
go room.run()
}
}
type _room struct {
name string
clients map[*_client]bool
chRoomMsg chan []byte
chRegister chan *_client
chUnregister chan *_client
}
func newRoom(name string) *_room {
return &_room{
name,
make(map[*_client]bool),
make(chan []byte),
make(chan *_client),
make(chan *_client),
}
}
func (r *_room) run() {
for {
select {
case client := <-r.chRegister:
r.clients[client] = true
case client := <-r.chUnregister:
if _, ok := r.clients[client]; ok {
r.clients[client] = false
close(client.chClientMsg)
}
case msg := <-r.chRoomMsg:
for client, connected := range r.clients {
if connected {
select {
case client.chClientMsg <- msg:
default:
r.clients[client] = false
close(client.chClientMsg)
}
}
}
}
}
}
- app/client.go
package app
import (
"bytes"
"time"
"github.com/gorilla/websocket"
)
const (
writeTimeout = 10 * time.Second
pongTimeout = 60 * time.Second
pingPeriod = (9 * pongTimeout) / 10
maxMsgSize = 512
)
type _client struct {
name string
room *_room
conn *websocket.Conn
chClientMsg chan []byte
}
func (c *_client) read() {
defer c.conn.Close()
c.conn.SetReadLimit(maxMsgSize)
c.conn.SetReadDeadline(time.Now().Add(pongTimeout))
c.conn.SetPongHandler(func(string) error {
c.conn.SetReadDeadline(time.Now().Add(pongTimeout))
return nil
})
for {
if _, msg, err := c.conn.ReadMessage(); err != nil {
c.room.chUnregister <- c
break
} else {
c.room.chRoomMsg <- append([]byte(c.name+" : "), bytes.Replace(msg, []byte("
"), []byte(" "), -1)...)
}
}
}
func (c *_client) write() {
defer c.conn.Close()
ticker := time.NewTicker(pingPeriod)
defer ticker.Stop()
for {
select {
case msg, ok := <-c.chClientMsg:
c.conn.SetWriteDeadline(time.Now().Add(writeTimeout))
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
} else {
if w, err := c.conn.NextWriter(websocket.TextMessage); err != nil {
return
} else {
w.Write(msg)
for i := 0; i < len(c.chClientMsg); i++ {
w.Write([]byte("
"))
w.Write(<-c.chClientMsg)
}
if err := w.Close(); err != nil {
return
}
}
}
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeTimeout))
if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return
}
}
}
}
- app/session.go
package app
import (
"net/http"
"github.com/gorilla/sessions"
)
func login(w *http.ResponseWriter, r *http.Request, store *sessions.CookieStore) (string, error) {
session, err := store.Get(r, sessionName)
if err != nil {
return "", err
} else {
if val := session.Values["name"]; val != nil {
return val.(string), nil
} else {
name := nameGenerator()
session.Values["name"] = name
session.Save(r, *w)
return name, nil
}
}
}
- app/tools.go
package app
import (
"math/rand"
"time"
)
var firstName = []string{"방황하는", "울부짖는", "물마시는", "털고르는", "벌벌떠는", "기뻐하는", "사랑스런", "밉살스런", "곰살스런", "뒤뚱뒤뚱", "허겁지겁", "좌충우돌", "재잘재잘", "처묵처묵", "귀염귀염", "발그스레"}
var lastName = []string{"호랑이", "다람쥐", "청설모", "코뿔소", "독수리", "너구리", "고양이", "강아지", "앵무새", "개구리", "두꺼비", "비둘기", "사랑새", "오귀발", "돌모란", "눈쟁이", "코끼리", "원숭이", "침팬치", "고릴라", "거북이", "부엉이", "올빼미", "햄스터", "코알라", "캥거루", "돌고래", "망아지"}
func nameGenerator() string {
rand.Seed(time.Now().UnixNano())
return firstName[rand.Intn(len(firstName))] + " " + lastName[rand.Intn(len(lastName))]
}
C#보다 간단하네 씨팔
고로 나도 고독교 ^^
고독교... 고독하게 고를 파는..