웹소켓 익힌 기념으로 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))] }