From 37254852b3d9b68c0eeaebdf6c862acbed2e6155 Mon Sep 17 00:00:00 2001 From: giongto35 Date: Sun, 19 May 2019 03:12:21 +0800 Subject: [PATCH] update architecture --- ' | 195 ++++++++ cmd/main.go | 40 +- cmd/main_test.go | 628 +++++++++++------------- cws/cws.go | 20 +- handler/browser.go | 164 ------- handler/handlers.go | 77 +-- handler/overlord.go | 178 ++++--- handler/session.go | 12 +- overlord/browser.go | 195 ++++++++ {handler => overlord}/gamelist/games.go | 0 overlord/{overlord.go => handlers.go} | 149 +++--- overlord/session.go | 21 + overlord/worker.go | 176 +++++++ 13 files changed, 1136 insertions(+), 719 deletions(-) create mode 100644 ' delete mode 100644 handler/browser.go create mode 100644 overlord/browser.go rename {handler => overlord}/gamelist/games.go (100%) rename overlord/{overlord.go => handlers.go} (55%) create mode 100644 overlord/session.go create mode 100644 overlord/worker.go diff --git a/' b/' new file mode 100644 index 00000000..5f8bf0e0 --- /dev/null +++ b/' @@ -0,0 +1,195 @@ +package handler + +import ( + "log" + + "github.com/giongto35/cloud-game/config" + "github.com/giongto35/cloud-game/cws" + "github.com/giongto35/cloud-game/webrtc" + "github.com/gorilla/websocket" +) + +// OverlordClient maintans connection to overlord +// We expect only one OverlordClient for each server +type OverlordClient struct { + *cws.Client + peerconnections map[string]*webrtc.WebRTC +} + +// NewOverlordClient returns a client connecting to overlord for coordiation between different server +func NewOverlordClient(oc *websocket.Conn) *OverlordClient { + if oc == nil { + return nil + } + + oclient := &OverlordClient{ + Client: cws.NewClient(oc), + peerconnections: map[string]*webrtc.WebRTC{}, + } + return oclient +} + +// RouteOverlord are all routes server received from overlord +func (s *Session) RouteOverlord() { + iceCandidates := [][]byte{} + oclient := s.OverlordClient + + // Received from overlord the serverID + oclient.Receive( + "serverID", + func(response cws.WSPacket) (request cws.WSPacket) { + // Stick session with serverID got from overlord + log.Println("Received serverID ", response.Data) + s.ServerID = response.Data + + return cws.EmptyPacket + }, + ) + + oclient.Receive( + "initwebrtc", + func(resp cws.WSPacket) (req cws.WSPacket) { + log.Println("Received user SDP") + localSession, err := s.peerconnection.StartClient(resp.Data, iceCandidates, config.Width, config.Height) + if err != nil { + if err != nil { + log.Println("Error: Cannot create new webrtc session", err) + return cws.EmptyPacket + } + } + + return cws.WSPacket{ + ID: "sdp", + Data: localSession, + SessionID: s.ID, + } + }, + ) + + // Received start from overlord. This happens when bridging + // TODO: refactor + //oclient.Receive( + //"start", + //func(resp cws.WSPacket) (req cws.WSPacket) { + //log.Println("Received a start request from overlord") + //log.Println("Add the connection to current room on the host ", resp.SessionID) + + //peerconnection := oclient.peerconnections[resp.SessionID] + //log.Println("start session") + + ////room := s.handler.createNewRoom(s.GameName, s.RoomID, s.PlayerIndex) + //// Request room from Server if roomID is existed on the server + //room := s.handler.getRoom(s.RoomID) + //if room == nil { + //log.Println("Room not found ", s.RoomID) + //return cws.EmptyPacket + //} + //s.handler.detachPeerConn(s.peerconnection) + //room.AddConnectionToRoom(peerconnection, s.PlayerIndex) + ////roomID, isNewRoom := startSession(peerconnection, resp.Data, resp.RoomID, resp.PlayerIndex) + //log.Println("Done, sending back") + + //req.ID = "start" + //req.RoomID = room.ID + //return req + //}, + //) + oclient.Receive( + "start", + func(resp cws.WSPacket) (req cws.WSPacket) { + log.Println("Received a start request from overlord") + log.Println("Add the connection to current room on the host ", resp.SessionID) + s.startGameHandler(resp.Data, resp.RoomID, resp.PlayerIndex) + }, + ) + // heartbeat to keep pinging overlord. We not ping from server to browser, so we don't call heartbeat in browserClient +} + +func getServerIDOfRoom(oc *OverlordClient, roomID string) string { + log.Println("Request overlord roomID ", roomID) + packet := oc.SyncSend( + cws.WSPacket{ + ID: "getRoom", + Data: roomID, + }, + ) + log.Println("Received roomID from overlord ", packet.Data) + + return packet.Data +} + +func (s *Session) startGameHandler(gameName, roomID string, playerIndex int) cws.WSPacket { + s.GameName = gameName + s.RoomID = roomID + s.PlayerIndex = playerIndex + + log.Println("Starting game") + // If we are connecting to overlord, request corresponding serverID based on roomID + // TODO: check if roomID is in the current server + room := s.handler.getRoom(s.RoomID) + log.Println("Got Room from local ", room, " ID: ", s.RoomID) + // If room is not running + if room == nil { + // Create new room + room = s.handler.createNewRoom(s.GameName, s.RoomID, s.PlayerIndex) + // Wait for done signal from room + go func() { + <-room.Done + s.handler.detachRoom(room.ID) + }() + } + + // Attach peerconnection to room. If PC is already in room, don't detach + log.Println("Is PC in room", room.IsPCInRoom(s.peerconnection)) + if !room.IsPCInRoom(s.peerconnection) { + s.handler.detachPeerConn(s.peerconnection) + room.AddConnectionToRoom(s.peerconnection, s.PlayerIndex) + } + s.RoomID = room.ID + + // Register room to overlord if we are connecting to overlord + if room != nil && s.OverlordClient != nil { + s.OverlordClient.Send(cws.WSPacket{ + ID: "registerRoom", + Data: s.RoomID, + }, nil) + } + req.ID = "start" + req.RoomID = s.RoomID + req.SessionID = s.ID + + return req +} + +//func (s *Session) bridgeConnection(serverID string, gameName string, roomID string, playerIndex int) { +//log.Println("Bridging connection to other Host ", serverID) +//client := s.BrowserClient +//// Ask client to init + +//log.Println("Requesting offer to browser", serverID) +//resp := client.SyncSend(cws.WSPacket{ +//ID: "requestOffer", +//Data: "", +//}) + +//// Ask overlord to relay SDP packet to serverID +//resp.TargetHostID = serverID +//log.Println("Sending offer to overlord to relay message to target host", resp.TargetHostID, "with payload") +//remoteTargetSDP := s.OverlordClient.SyncSend(resp) +//log.Println("Got back remote host SDP, sending to browser") +//// Send back remote SDP of remote server to browser +//s.BrowserClient.Send(cws.WSPacket{ +//ID: "sdp", +//Data: remoteTargetSDP.Data, +//}, nil) +//log.Println("Init session done, start game on target host") + +//s.OverlordClient.SyncSend(cws.WSPacket{ +//ID: "start", +//Data: gameName, +//TargetHostID: serverID, +//RoomID: roomID, +//PlayerIndex: playerIndex, +//}) +//log.Println("Game is started on remote host") +//} diff --git a/cmd/main.go b/cmd/main.go index 24c10aab..0a2e31a0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,11 +16,7 @@ import ( "github.com/gorilla/websocket" ) -const ( - gameboyIndex = "./static/gameboy2.html" - debugIndex = "./static/gameboy2.html" - gamePath = "games" -) +const gamePath = "games" // Time allowed to write a message to the peer. var upgrader = websocket.Upgrader{} @@ -40,13 +36,24 @@ func initilizeOverlord() { log.Println("http://localhost:9000") - // Can consider Overlord works as server but it is complicated + http.HandleFunc("/", overlord.GetWeb) + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) + + // browser facing port + go func() { + http.HandleFunc("/ws", overlord.WS) + http.ListenAndServe(":8000", nil) + }() + + // worker facing port http.HandleFunc("/wso", overlord.WSO) http.ListenAndServe(":9000", nil) + + log.Println("http://localhost:" + *config.Port) } -// initializeServer setup a server -func initializeServer() { +// initializeWorker setup a worker +func initializeWorker() { conn, err := createOverlordConnection() if err != nil { log.Println("Cannot connect to overlord") @@ -55,15 +62,13 @@ func initializeServer() { handler := handler.NewHandler(conn, *config.IsDebug, gamePath) + handler.Run() + // ignore origin - upgrader.CheckOrigin = func(r *http.Request) bool { return true } + //upgrader.CheckOrigin = func(r *http.Request) bool { return true } - http.HandleFunc("/", handler.GetWeb) - http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) - http.HandleFunc("/ws", handler.WS) - - log.Println("http://localhost:" + *config.Port) - http.ListenAndServe(":"+*config.Port, nil) + //http.ListenAndServe(":"+*config.Port, nil) + //log.Println("http://localhost:" + *config.Port) } func monitor() { @@ -75,7 +80,6 @@ func monitor() { func main() { flag.Parse() - log.Println("Usage: ./game [-debug]") rand.Seed(time.Now().UTC().UnixNano()) @@ -92,7 +96,7 @@ func main() { if strings.HasPrefix(*config.OverlordHost, "ws") && !strings.HasSuffix(*config.OverlordHost, "wso") { log.Fatal("Overlord connection is invalid. Should have the form `ws://.../wso`") } - log.Println("Running as slave ") - initializeServer() + log.Println("Running as worker ") + initializeWorker() } } diff --git a/cmd/main_test.go b/cmd/main_test.go index e8deb004..aa65f87a 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -5,7 +5,6 @@ import ( "log" "net/http" "net/http/httptest" - "runtime" "strings" "testing" "time" @@ -24,17 +23,19 @@ var host = "http://localhost:8000" var testGamePath = "../games" var webrtcconfig = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}} -func initOverlord() *httptest.Server { +func initOverlord() (*httptest.Server, *httptest.Server) { server := overlord.NewServer() - overlord := httptest.NewServer(http.HandlerFunc(server.WSO)) - return overlord + overlordWorker := httptest.NewServer(http.HandlerFunc(server.WSO)) + overlordBrowser := httptest.NewServer(http.HandlerFunc(server.WS)) + return overlordWorker, overlordBrowser } -func initServer(t *testing.T, oconn *websocket.Conn) *httptest.Server { - fmt.Println("Spawn new server") +func initWorker(t *testing.T, oconn *websocket.Conn) *handler.Handler { + fmt.Println("Spawn new worker") handler := handler.NewHandler(oconn, true, testGamePath) - server := httptest.NewServer(http.HandlerFunc(handler.WS)) - return server + go handler.Run() + //server := httptest.NewServer(http.HandlerFunc(handler.WS)) + return handler } func connectTestOverlordServer(t *testing.T, overlordURL string) *websocket.Conn { @@ -57,8 +58,8 @@ func initClient(t *testing.T, host string) (client *cws.Client) { // Convert http://127.0.0.1 to ws://127.0.0. u := "ws" + strings.TrimPrefix(host, "http") - // Connect to the server - fmt.Println("Connecting to server") + // Connect to the overlord + fmt.Println("Connecting to ", u) ws, _, err := websocket.DefaultDialer.Dial(u, nil) if err != nil { t.Fatalf("%v", err) @@ -82,7 +83,7 @@ func initClient(t *testing.T, host string) (client *cws.Client) { if err != nil { panic(err) } - // Send offer to server + // Send offer to overlord log.Println("Browser Client") client = cws.NewClient(ws) go client.Listen() @@ -141,42 +142,6 @@ func initClient(t *testing.T, host string) (client *cws.Client) { // If receive roomID, the server is running correctly } -func TestSingleServerNoOverlord(t *testing.T) { - /* - Case scenario: - - A server X are initilized - - Client join room with no coordinator - Expected behavior: - - Room received not empty - */ - - // Init slave server - s := initServer(t, nil) - defer s.Close() - - client := initClient(t, s.URL) - defer client.Close() - - roomID := make(chan string) - client.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: "", - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) - - respRoomID := <-roomID - if respRoomID == "" { - fmt.Println("RoomID should not be empty") - t.Fail() - } - time.Sleep(3 * time.Second) - fmt.Println("Done") -} - func TestSingleServerOneOverlord(t *testing.T) { /* Case scenario: @@ -186,16 +151,18 @@ func TestSingleServerOneOverlord(t *testing.T) { - Room received not empty. */ - o := initOverlord() - defer o.Close() + oworker, obrowser := initOverlord() + defer obrowser.Close() + defer oworker.Close() - oconn := connectTestOverlordServer(t, o.URL) + oconn := connectTestOverlordServer(t, oworker.URL) defer oconn.Close() - // Init slave server - s := initServer(t, oconn) - defer s.Close() + // Init worker + initWorker(t, oconn) + //defer h.Close() - client := initClient(t, s.URL) + // connect overlord + client := initClient(t, obrowser.URL) defer client.Close() fmt.Println("Sending start...") @@ -206,7 +173,6 @@ func TestSingleServerOneOverlord(t *testing.T) { RoomID: "", PlayerIndex: 1, }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) roomID <- resp.RoomID }) @@ -219,325 +185,325 @@ func TestSingleServerOneOverlord(t *testing.T) { fmt.Println("Done") } -func TestTwoServerOneOverlord(t *testing.T) { - /* - Case scenario: - - Two server X, Y are initilized - - Client A creates a room on server X - - Client B creates a room on server Y - - Client B join a room created by A - Expected behavior: - - Bridge connection will be conducted between server Y and X - - Client B can join a room hosted on A - */ +//func TestTwoServerOneOverlord(t *testing.T) { +//[> +//Case scenario: +//- Two server X, Y are initilized +//- Client A creates a room on server X +//- Client B creates a room on server Y +//- Client B join a room created by A +//Expected behavior: +//- Bridge connection will be conducted between server Y and X +//- Client B can join a room hosted on A +//*/ - o := initOverlord() - defer o.Close() +//o := initOverlord() +//defer o.Close() - oconn1 := connectTestOverlordServer(t, o.URL) - // Init slave server - s1 := initServer(t, oconn1) - defer s1.Close() +//oconn1 := connectTestOverlordServer(t, o.URL) +//// Init slave server +//s1 := initServer(t, oconn1) +//defer s1.Close() - oconn2 := connectTestOverlordServer(t, o.URL) - s2 := initServer(t, oconn2) - defer s2.Close() +//oconn2 := connectTestOverlordServer(t, o.URL) +//s2 := initServer(t, oconn2) +//defer s2.Close() - client1 := initClient(t, s1.URL) - defer client1.Close() +//client1 := initClient(t, s1.URL) +//defer client1.Close() - roomID := make(chan string) - client1.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: "", - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) +//roomID := make(chan string) +//client1.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: "", +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//roomID <- resp.RoomID +//}) - remoteRoomID := <-roomID - if remoteRoomID == "" { - fmt.Println("RoomID should not be empty") - t.Fail() - } - fmt.Println("Done create a room in server 1") +//remoteRoomID := <-roomID +//if remoteRoomID == "" { +//fmt.Println("RoomID should not be empty") +//t.Fail() +//} +//fmt.Println("Done create a room in server 1") - // ------------------------------------ - // Client2 trying to create a random room and later join the the room on server1 - client2 := initClient(t, s2.URL) - defer client2.Close() - // Wait - // Doing the same create local room. - localRoomID := make(chan string) - client2.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: "", - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - localRoomID <- resp.RoomID - }) +//// ------------------------------------ +//// Client2 trying to create a random room and later join the the room on server1 +//client2 := initClient(t, s2.URL) +//defer client2.Close() +//// Wait +//// Doing the same create local room. +//localRoomID := make(chan string) +//client2.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: "", +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//localRoomID <- resp.RoomID +//}) - <-localRoomID +//<-localRoomID - fmt.Println("Request the room from server 1", remoteRoomID) - log.Println("Server2 trying to join server1 room") - // After trying loging in to one session, login to other with the roomID - bridgeRoom := make(chan string) - client2.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: remoteRoomID, - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - bridgeRoom <- resp.RoomID - }) +//fmt.Println("Request the room from server 1", remoteRoomID) +//log.Println("Server2 trying to join server1 room") +//// After trying loging in to one session, login to other with the roomID +//bridgeRoom := make(chan string) +//client2.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: remoteRoomID, +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//bridgeRoom <- resp.RoomID +//}) - <-bridgeRoom - //respRoomID := <-bridgeRoom - //if respRoomID == "" { - //fmt.Println("The room ID should be equal to the saved room") - //t.Fail() - //} - // If receive roomID, the server is running correctly - time.Sleep(time.Second) - fmt.Println("Done") -} +//<-bridgeRoom +////respRoomID := <-bridgeRoom +////if respRoomID == "" { +////fmt.Println("The room ID should be equal to the saved room") +////t.Fail() +////} +//// If receive roomID, the server is running correctly +//time.Sleep(time.Second) +//fmt.Println("Done") +//} -func TestReconnectRoomNoOverlord(t *testing.T) { - /* - Case scenario: - - A server X is initialized - - Client A creates a room K on server X - - Server X is turned down, Client is closed - - Spawn a new server and a new client connecting to the same room K - Expected behavior: - - The game should be continue - TODO: Current test just make sure the game is running, not check if the game is the same - */ +//func TestReconnectRoomNoOverlord(t *testing.T) { +//[> +//Case scenario: +//- A server X is initialized +//- Client A creates a room K on server X +//- Server X is turned down, Client is closed +//- Spawn a new server and a new client connecting to the same room K +//Expected behavior: +//- The game should be continue +//TODO: Current test just make sure the game is running, not check if the game is the same +//*/ - // Init slave server - s := initServer(t, nil) +//// Init slave server +//s := initServer(t, nil) - client := initClient(t, s.URL) +//client := initClient(t, s.URL) - fmt.Println("Sending start...") - roomID := make(chan string) - client.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: "", - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) +//fmt.Println("Sending start...") +//roomID := make(chan string) +//client.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: "", +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//roomID <- resp.RoomID +//}) - saveRoomID := <-roomID - if saveRoomID == "" { - fmt.Println("RoomID should not be empty") - t.Fail() - } +//saveRoomID := <-roomID +//if saveRoomID == "" { +//fmt.Println("RoomID should not be empty") +//t.Fail() +//} - log.Println("Closing room and server") - client.Close() - s.Close() - // Close server and reconnect +//log.Println("Closing room and server") +//client.Close() +//s.Close() +//// Close server and reconnect - // Respawn slave server - s = initServer(t, nil) - defer s.Close() +//// Respawn slave server +//s = initServer(t, nil) +//defer s.Close() - client = initClient(t, s.URL) - defer client.Close() +//client = initClient(t, s.URL) +//defer client.Close() - fmt.Println("Re-access room ", saveRoomID) - roomID = make(chan string) - client.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: saveRoomID, - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) +//fmt.Println("Re-access room ", saveRoomID) +//roomID = make(chan string) +//client.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: saveRoomID, +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//roomID <- resp.RoomID +//}) - respRoomID := <-roomID - if respRoomID == "" || respRoomID != saveRoomID { - fmt.Println("The room ID should be equal to the saved room") - t.Fail() - } +//respRoomID := <-roomID +//if respRoomID == "" || respRoomID != saveRoomID { +//fmt.Println("The room ID should be equal to the saved room") +//t.Fail() +//} - time.Sleep(time.Second) - fmt.Println("Done") +//time.Sleep(time.Second) +//fmt.Println("Done") -} +//} -func TestReconnectRoomWithOverlord(t *testing.T) { - /* - Case scenario: - - A server X is initialized connecting to overlord - - Client A creates a room K on server X - - Server X is turned down, Client is closed - - Spawn a new server and a new client connecting to the same room K - Expected behavior: - - The game should be continue - TODO: Current test just make sure the game is running, not check if the game is the same - */ +//func TestReconnectRoomWithOverlord(t *testing.T) { +//[> +//Case scenario: +//- A server X is initialized connecting to overlord +//- Client A creates a room K on server X +//- Server X is turned down, Client is closed +//- Spawn a new server and a new client connecting to the same room K +//Expected behavior: +//- The game should be continue +//TODO: Current test just make sure the game is running, not check if the game is the same +//*/ - o := initOverlord() - defer o.Close() +//o := initOverlord() +//defer o.Close() - oconn := connectTestOverlordServer(t, o.URL) - // Init slave server - s := initServer(t, oconn) +//oconn := connectTestOverlordServer(t, o.URL) +//// Init slave server +//s := initServer(t, oconn) - client := initClient(t, s.URL) +//client := initClient(t, s.URL) - fmt.Println("Sending start...") - roomID := make(chan string) - client.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: "", - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) +//fmt.Println("Sending start...") +//roomID := make(chan string) +//client.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: "", +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//roomID <- resp.RoomID +//}) - saveRoomID := <-roomID - if saveRoomID == "" { - fmt.Println("RoomID should not be empty") - t.Fail() - } +//saveRoomID := <-roomID +//if saveRoomID == "" { +//fmt.Println("RoomID should not be empty") +//t.Fail() +//} - log.Println("Closing room and server") - client.Close() - s.Close() - oconn.Close() +//log.Println("Closing room and server") +//client.Close() +//s.Close() +//oconn.Close() - // Close server and reconnect +//// Close server and reconnect - log.Println("Server respawn") - // Init slave server again - oconn = connectTestOverlordServer(t, o.URL) - defer oconn.Close() - s = initServer(t, oconn) - defer s.Close() +//log.Println("Server respawn") +//// Init slave server again +//oconn = connectTestOverlordServer(t, o.URL) +//defer oconn.Close() +//s = initServer(t, oconn) +//defer s.Close() - client = initClient(t, s.URL) - defer client.Close() +//client = initClient(t, s.URL) +//defer client.Close() - fmt.Println("Re-access room ", saveRoomID) - roomID = make(chan string) - client.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: saveRoomID, - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) +//fmt.Println("Re-access room ", saveRoomID) +//roomID = make(chan string) +//client.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: saveRoomID, +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//roomID <- resp.RoomID +//}) - respRoomID := <-roomID - if respRoomID == "" || respRoomID != saveRoomID { - fmt.Println("The room ID should be equal to the saved room") - t.Fail() - } +//respRoomID := <-roomID +//if respRoomID == "" || respRoomID != saveRoomID { +//fmt.Println("The room ID should be equal to the saved room") +//t.Fail() +//} - time.Sleep(time.Second) - fmt.Println("Done") -} +//time.Sleep(time.Second) +//fmt.Println("Done") +//} -func TestRejoinNoOverlordMultiple(t *testing.T) { - /* - Case scenario: - - A server X without connecting to overlord - - Client A keeps creating a new room - Expected behavior: - - The game should running normally - */ +//func TestRejoinNoOverlordMultiple(t *testing.T) { +//[> +//Case scenario: +//- A server X without connecting to overlord +//- Client A keeps creating a new room +//Expected behavior: +//- The game should running normally +//*/ - // Init slave server - s := initServer(t, nil) - defer s.Close() +//// Init slave server +//s := initServer(t, nil) +//defer s.Close() - fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine()) - client := initClient(t, s.URL) - for i := 0; i < 100; i++ { - fmt.Println("Sending start...") - // Keep starting game - roomID := make(chan string) - client.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: "", - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) +//fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine()) +//client := initClient(t, s.URL) +//for i := 0; i < 100; i++ { +//fmt.Println("Sending start...") +//// Keep starting game +//roomID := make(chan string) +//client.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: "", +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//roomID <- resp.RoomID +//}) - respRoomID := <-roomID - if respRoomID == "" { - fmt.Println("The room ID should be equal to the saved room") - t.Fail() - } - } - time.Sleep(time.Second) - fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine()) - fmt.Println("Done") +//respRoomID := <-roomID +//if respRoomID == "" { +//fmt.Println("The room ID should be equal to the saved room") +//t.Fail() +//} +//} +//time.Sleep(time.Second) +//fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine()) +//fmt.Println("Done") -} +//} -func TestRejoinWithOverlordMultiple(t *testing.T) { - /* - Case scenario: - - A server X is initialized connecting to overlord - - Client A keeps creating a new room - Expected behavior: - - The game should running normally - */ +//func TestRejoinWithOverlordMultiple(t *testing.T) { +//[> +//Case scenario: +//- A server X is initialized connecting to overlord +//- Client A keeps creating a new room +//Expected behavior: +//- The game should running normally +//*/ - // Init slave server - o := initOverlord() - defer o.Close() +//// Init slave server +//o := initOverlord() +//defer o.Close() - oconn := connectTestOverlordServer(t, o.URL) - // Init slave server - s := initServer(t, oconn) - defer s.Close() +//oconn := connectTestOverlordServer(t, o.URL) +//// Init slave server +//s := initServer(t, oconn) +//defer s.Close() - fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine()) - client := initClient(t, s.URL) - for i := 0; i < 100; i++ { - fmt.Println("Sending start...") - // Keep starting game - roomID := make(chan string) - client.Send(cws.WSPacket{ - ID: "start", - Data: "Contra.nes", - RoomID: "", - PlayerIndex: 1, - }, func(resp cws.WSPacket) { - fmt.Println("RoomID:", resp.RoomID) - roomID <- resp.RoomID - }) +//fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine()) +//client := initClient(t, s.URL) +//for i := 0; i < 100; i++ { +//fmt.Println("Sending start...") +//// Keep starting game +//roomID := make(chan string) +//client.Send(cws.WSPacket{ +//ID: "start", +//Data: "Contra.nes", +//RoomID: "", +//PlayerIndex: 1, +//}, func(resp cws.WSPacket) { +//fmt.Println("RoomID:", resp.RoomID) +//roomID <- resp.RoomID +//}) - respRoomID := <-roomID - if respRoomID == "" { - fmt.Println("The room ID should be equal to the saved room") - t.Fail() - } - } - fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine()) - fmt.Println("Done") +//respRoomID := <-roomID +//if respRoomID == "" { +//fmt.Println("The room ID should be equal to the saved room") +//t.Fail() +//} +//} +//fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine()) +//fmt.Println("Done") -} +//} diff --git a/cws/cws.go b/cws/cws.go index 56e393d5..734d7025 100644 --- a/cws/cws.go +++ b/cws/cws.go @@ -68,11 +68,11 @@ func (c *Client) Send(request WSPacket, callback func(response WSPacket)) { // Wrap callback with sessionID and packetID if callback != nil { wrapperCallback := func(resp WSPacket) { - defer func() { - if err := recover(); err != nil { - log.Println("Recovered from err", err) - } - }() + //defer func() { + //if err := recover(); err != nil { + //log.Println("Recovered from err", err) + //} + //}() resp.PacketID = request.PacketID resp.SessionID = request.SessionID @@ -91,11 +91,11 @@ func (c *Client) Send(request WSPacket, callback func(response WSPacket)) { // Receive receive and response back func (c *Client) Receive(id string, f func(response WSPacket) (request WSPacket)) { c.recvCallback[id] = func(response WSPacket) { - defer func() { - if err := recover(); err != nil { - log.Println("Recovered from err", err) - } - }() + //defer func() { + //if err := recover(); err != nil { + //log.Println("Recovered from err", err) + //} + //}() req := f(response) // Add Meta data diff --git a/handler/browser.go b/handler/browser.go deleted file mode 100644 index 51961944..00000000 --- a/handler/browser.go +++ /dev/null @@ -1,164 +0,0 @@ -package handler - -import ( - "log" - - "github.com/giongto35/cloud-game/config" - "github.com/giongto35/cloud-game/cws" - "github.com/gorilla/websocket" -) - -type BrowserClient struct { - *cws.Client -} - -// RouteBrowser are all routes server received from browser -func (s *Session) RouteBrowser() { - iceCandidates := [][]byte{} - - browserClient := s.BrowserClient - - browserClient.Receive("heartbeat", func(resp cws.WSPacket) cws.WSPacket { - return resp - }) - - browserClient.Receive("icecandidate", func(resp cws.WSPacket) cws.WSPacket { - log.Println("Received candidates ", resp.Data) - iceCandidates = append(iceCandidates, []byte(resp.Data)) - return cws.EmptyPacket - }) - - browserClient.Receive("initwebrtc", func(resp cws.WSPacket) cws.WSPacket { - log.Println("Received user SDP") - localSession, err := s.peerconnection.StartClient(resp.Data, iceCandidates, config.Width, config.Height) - if err != nil { - if err != nil { - log.Println("Error: Cannot create new webrtc session", err) - return cws.EmptyPacket - } - } - - return cws.WSPacket{ - ID: "sdp", - Data: localSession, - SessionID: s.ID, - } - }) - - // TODO: Add save and load - browserClient.Receive("save", func(resp cws.WSPacket) (req cws.WSPacket) { - log.Println("Saving game state") - req.ID = "save" - req.Data = "ok" - if s.RoomID != "" { - room := s.handler.getRoom(s.RoomID) - if room == nil { - return - } - err := room.SaveGame() - if err != nil { - log.Println("[!] Cannot save game state: ", err) - req.Data = "error" - } - } else { - req.Data = "error" - } - - return req - }) - - browserClient.Receive("load", func(resp cws.WSPacket) (req cws.WSPacket) { - log.Println("Loading game state") - req.ID = "load" - req.Data = "ok" - if s.RoomID != "" { - room := s.handler.getRoom(s.RoomID) - err := room.LoadGame() - if err != nil { - log.Println("[!] Cannot load game state: ", err) - req.Data = "error" - } - } else { - req.Data = "error" - } - - return req - }) - - browserClient.Receive("quit", func(resp cws.WSPacket) (req cws.WSPacket) { - log.Println("Received quit", req) - s.GameName = resp.Data - s.RoomID = resp.RoomID - s.PlayerIndex = resp.PlayerIndex - - room := s.handler.getRoom(s.RoomID) - if room.IsPCInRoom(s.peerconnection) { - s.handler.detachPeerConn(s.peerconnection) - } - - return cws.EmptyPacket - }) - - browserClient.Receive("start", func(resp cws.WSPacket) (req cws.WSPacket) { - s.GameName = resp.Data - s.RoomID = resp.RoomID - s.PlayerIndex = resp.PlayerIndex - - log.Println("Starting game") - // If we are connecting to overlord, request corresponding serverID based on roomID - if s.OverlordClient != nil { - roomServerID := getServerIDOfRoom(s.OverlordClient, s.RoomID) - log.Println("Server of RoomID ", s.RoomID, " is ", roomServerID, " while current server is ", s.ServerID) - // If the target serverID is different from current serverID - if roomServerID != "" && s.ServerID != roomServerID { - // TODO: Re -register - // Bridge Connection to the target serverID - go s.bridgeConnection(roomServerID, s.GameName, s.RoomID, s.PlayerIndex) - return - } - } - - // Get Room in local server - // TODO: check if roomID is in the current server - room := s.handler.getRoom(s.RoomID) - log.Println("Got Room from local ", room, " ID: ", s.RoomID) - // If room is not running - if room == nil { - // Create new room - room = s.handler.createNewRoom(s.GameName, s.RoomID, s.PlayerIndex) - // Wait for done signal from room - go func() { - <-room.Done - s.handler.detachRoom(room.ID) - }() - } - - // Attach peerconnection to room. If PC is already in room, don't detach - log.Println("Is PC in room", room.IsPCInRoom(s.peerconnection)) - if !room.IsPCInRoom(s.peerconnection) { - s.handler.detachPeerConn(s.peerconnection) - room.AddConnectionToRoom(s.peerconnection, s.PlayerIndex) - } - s.RoomID = room.ID - - // Register room to overlord if we are connecting to overlord - if room != nil && s.OverlordClient != nil { - s.OverlordClient.Send(cws.WSPacket{ - ID: "registerRoom", - Data: s.RoomID, - }, nil) - } - req.ID = "start" - req.RoomID = s.RoomID - req.SessionID = s.ID - - return req - }) -} - -// NewOverlordClient returns a client connecting to browser. This connection exchanges information between clients and server -func NewBrowserClient(c *websocket.Conn) *BrowserClient { - return &BrowserClient{ - Client: cws.NewClient(c), - } -} diff --git a/handler/handlers.go b/handler/handlers.go index c1c81bef..84469b13 100644 --- a/handler/handlers.go +++ b/handler/handlers.go @@ -5,13 +5,10 @@ import ( "log" "net/http" - "github.com/giongto35/cloud-game/cws" storage "github.com/giongto35/cloud-game/handler/cloud-storage" - "github.com/giongto35/cloud-game/handler/gamelist" "github.com/giongto35/cloud-game/handler/room" "github.com/giongto35/cloud-game/webrtc" "github.com/gorilla/websocket" - uuid "github.com/satori/go.uuid" ) const ( @@ -30,7 +27,7 @@ type Handler struct { // ID of the current server globalwise serverID string // isDebug determines the mode handler is running - isDebug bool + //isDebug bool // Path to game list gamePath string // All webrtc peerconnections are handled by the server @@ -44,26 +41,34 @@ type Handler struct { func NewHandler(overlordConn *websocket.Conn, isDebug bool, gamePath string) *Handler { onlineStorage := storage.NewInitClient() + oClient := NewOverlordClient(overlordConn) return &Handler{ - oClient: NewOverlordClient(overlordConn), + oClient: oClient, rooms: map[string]*room.Room{}, peerconnections: map[string]*webrtc.WebRTC{}, - isDebug: isDebug, + //isDebug: isDebug, gamePath: gamePath, onlineStorage: onlineStorage, } } +func (h *Handler) Run() { + go h.oClient.Heartbeat() + + h.RouteOverlord() + h.oClient.Listen() +} + // GetWeb returns web frontend func (h *Handler) GetWeb(w http.ResponseWriter, r *http.Request) { indexFN := "" - if h.isDebug { - indexFN = debugIndex - } else { - indexFN = gameboyIndex - } + //if h.isDebug { + //indexFN = debugIndex + //} else { + indexFN = gameboyIndex + //} bs, err := ioutil.ReadFile(indexFN) if err != nil { @@ -72,56 +77,6 @@ func (h *Handler) GetWeb(w http.ResponseWriter, r *http.Request) { w.Write(bs) } -// WS handles normal traffic (from browser to host) -func (h *Handler) WS(w http.ResponseWriter, r *http.Request) { - defer func() { - if r := recover(); r != nil { - log.Println("Warn: Something wrong. Recovered in f", r) - } - }() - - c, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Print("[!] WS upgrade:", err) - return - } - defer c.Close() - - client := NewBrowserClient(c) - sessionID := uuid.Must(uuid.NewV4()).String() - wssession := &Session{ - ID: sessionID, - BrowserClient: client, - OverlordClient: h.oClient, - peerconnection: webrtc.NewWebRTC(), - handler: h, - } - defer wssession.Close() - - if wssession.OverlordClient != nil { - wssession.RouteOverlord() - go wssession.OverlordClient.Heartbeat() - go wssession.OverlordClient.Listen() - } - - wssession.RouteBrowser() - log.Println("oclient : ", h.oClient) - - wssession.BrowserClient.Send(cws.WSPacket{ - ID: "gamelist", - Data: gamelist.GetEncodedGameList(h.gamePath), - }, nil) - - // If peerconnection is done (client.Done is signalled), we close peerconnection - go func() { - <-client.Done - log.Println("Socket terminated, detach connection") - h.detachPeerConn(wssession.peerconnection) - }() - - wssession.BrowserClient.Listen() -} - // Detach peerconnection detach/remove a peerconnection from current room func (h *Handler) detachPeerConn(pc *webrtc.WebRTC) { log.Println("Detach peerconnection") diff --git a/handler/overlord.go b/handler/overlord.go index 19ed73f5..73d0dc97 100644 --- a/handler/overlord.go +++ b/handler/overlord.go @@ -13,7 +13,6 @@ import ( // We expect only one OverlordClient for each server type OverlordClient struct { *cws.Client - peerconnections map[string]*webrtc.WebRTC } // NewOverlordClient returns a client connecting to overlord for coordiation between different server @@ -23,15 +22,15 @@ func NewOverlordClient(oc *websocket.Conn) *OverlordClient { } oclient := &OverlordClient{ - Client: cws.NewClient(oc), - peerconnections: map[string]*webrtc.WebRTC{}, + Client: cws.NewClient(oc), } return oclient } // RouteOverlord are all routes server received from overlord -func (s *Session) RouteOverlord() { - oclient := s.OverlordClient +func (h *Handler) RouteOverlord() { + iceCandidates := [][]byte{} + oclient := h.oClient // Received from overlord the serverID oclient.Receive( @@ -39,62 +38,76 @@ func (s *Session) RouteOverlord() { func(response cws.WSPacket) (request cws.WSPacket) { // Stick session with serverID got from overlord log.Println("Received serverID ", response.Data) - s.ServerID = response.Data + h.serverID = response.Data return cws.EmptyPacket }, ) - // Received from overlord the sdp. This is happens when bridging - // TODO: refactor oclient.Receive( "initwebrtc", func(resp cws.WSPacket) (req cws.WSPacket) { - log.Println("Received a sdp request from overlord") - log.Println("Start peerconnection from the sdp") + log.Println("Received relay SDP of a browser from overlord") peerconnection := webrtc.NewWebRTC() - // init new peerconnection from sessionID - localSession, err := peerconnection.StartClient(resp.Data, nil, config.Width, config.Height) - oclient.peerconnections[resp.SessionID] = peerconnection + localSession, err := peerconnection.StartClient(resp.Data, iceCandidates, config.Width, config.Height) + h.peerconnections[resp.SessionID] = peerconnection + log.Println("Start peerconnection") if err != nil { - log.Println("Error: Cannot create new webrtc session", err) - return cws.EmptyPacket + if err != nil { + log.Println("Error: Cannot create new webrtc session", err) + return cws.EmptyPacket + } } return cws.WSPacket{ - ID: "sdp", - Data: localSession, + ID: "sdp", + Data: localSession, + SessionID: resp.SessionID, } }, ) // Received start from overlord. This happens when bridging // TODO: refactor + //oclient.Receive( + //"start", + //func(resp cws.WSPacket) (req cws.WSPacket) { + //log.Println("Received a start request from overlord") + //log.Println("Add the connection to current room on the host ", resp.SessionID) + + //peerconnection := oclient.peerconnections[resp.SessionID] + //log.Println("start session") + + ////room := s.handler.createNewRoom(s.GameName, s.RoomID, s.PlayerIndex) + //// Request room from Server if roomID is existed on the server + //room := s.handler.getRoom(s.RoomID) + //if room == nil { + //log.Println("Room not found ", s.RoomID) + //return cws.EmptyPacket + //} + //s.handler.detachPeerConn(s.peerconnection) + //room.AddConnectionToRoom(peerconnection, s.PlayerIndex) + ////roomID, isNewRoom := startSession(peerconnection, resp.Data, resp.RoomID, resp.PlayerIndex) + //log.Println("Done, sending back") + + //req.ID = "start" + //req.RoomID = room.ID + //return req + //}, + //) + oclient.Receive( "start", func(resp cws.WSPacket) (req cws.WSPacket) { log.Println("Received a start request from overlord") log.Println("Add the connection to current room on the host ", resp.SessionID) - - peerconnection := oclient.peerconnections[resp.SessionID] - log.Println("start session") - - //room := s.handler.createNewRoom(s.GameName, s.RoomID, s.PlayerIndex) - // Request room from Server if roomID is existed on the server - room := s.handler.getRoom(s.RoomID) - if room == nil { - log.Println("Room not found ", s.RoomID) - return cws.EmptyPacket + peerconnection := h.peerconnections[resp.SessionID] + roomID := h.startGameHandler(resp.Data, resp.RoomID, resp.PlayerIndex, peerconnection) + return cws.WSPacket{ + ID: "start", + RoomID: roomID, } - s.handler.detachPeerConn(s.peerconnection) - room.AddConnectionToRoom(peerconnection, s.PlayerIndex) - //roomID, isNewRoom := startSession(peerconnection, resp.Data, resp.RoomID, resp.PlayerIndex) - log.Println("Done, sending back") - - req.ID = "start" - req.RoomID = room.ID - return req }, ) // heartbeat to keep pinging overlord. We not ping from server to browser, so we don't call heartbeat in browserClient @@ -113,35 +126,74 @@ func getServerIDOfRoom(oc *OverlordClient, roomID string) string { return packet.Data } -func (s *Session) bridgeConnection(serverID string, gameName string, roomID string, playerIndex int) { - log.Println("Bridging connection to other Host ", serverID) - client := s.BrowserClient - // Ask client to init +func (h *Handler) startGameHandler(gameName, roomID string, playerIndex int, peerconnection *webrtc.WebRTC) string { + //s.GameName = gameName + //s.RoomID = roomID + //s.PlayerIndex = playerIndex - log.Println("Requesting offer to browser", serverID) - resp := client.SyncSend(cws.WSPacket{ - ID: "requestOffer", - Data: "", - }) + log.Println("Starting game") + // If we are connecting to overlord, request corresponding serverID based on roomID + // TODO: check if roomID is in the current server + room := h.getRoom(roomID) + log.Println("Got Room from local ", room, " ID: ", roomID) + // If room is not running + if room == nil { + // Create new room + room = h.createNewRoom(gameName, roomID, playerIndex) + // Wait for done signal from room + go func() { + <-room.Done + h.detachRoom(room.ID) + }() + } - // Ask overlord to relay SDP packet to serverID - resp.TargetHostID = serverID - log.Println("Sending offer to overlord to relay message to target host", resp.TargetHostID, "with payload") - remoteTargetSDP := s.OverlordClient.SyncSend(resp) - log.Println("Got back remote host SDP, sending to browser") - // Send back remote SDP of remote server to browser - s.BrowserClient.Send(cws.WSPacket{ - ID: "sdp", - Data: remoteTargetSDP.Data, - }, nil) - log.Println("Init session done, start game on target host") + // Attach peerconnection to room. If PC is already in room, don't detach + log.Println("Is PC in room", room.IsPCInRoom(peerconnection)) + if !room.IsPCInRoom(peerconnection) { + h.detachPeerConn(peerconnection) + room.AddConnectionToRoom(peerconnection, playerIndex) + } - s.OverlordClient.SyncSend(cws.WSPacket{ - ID: "start", - Data: gameName, - TargetHostID: serverID, - RoomID: roomID, - PlayerIndex: playerIndex, - }) - log.Println("Game is started on remote host") + // Register room to overlord if we are connecting to overlord + if room != nil && h.oClient != nil { + h.oClient.Send(cws.WSPacket{ + ID: "registerRoom", + Data: roomID, + }, nil) + } + + return room.ID } + +//func (s *Session) bridgeConnection(serverID string, gameName string, roomID string, playerIndex int) { +//log.Println("Bridging connection to other Host ", serverID) +//client := s.BrowserClient +//// Ask client to init + +//log.Println("Requesting offer to browser", serverID) +//resp := client.SyncSend(cws.WSPacket{ +//ID: "requestOffer", +//Data: "", +//}) + +//// Ask overlord to relay SDP packet to serverID +//resp.TargetHostID = serverID +//log.Println("Sending offer to overlord to relay message to target host", resp.TargetHostID, "with payload") +//remoteTargetSDP := s.OverlordClient.SyncSend(resp) +//log.Println("Got back remote host SDP, sending to browser") +//// Send back remote SDP of remote server to browser +//s.BrowserClient.Send(cws.WSPacket{ +//ID: "sdp", +//Data: remoteTargetSDP.Data, +//}, nil) +//log.Println("Init session done, start game on target host") + +//s.OverlordClient.SyncSend(cws.WSPacket{ +//ID: "start", +//Data: gameName, +//TargetHostID: serverID, +//RoomID: roomID, +//PlayerIndex: playerIndex, +//}) +//log.Println("Game is started on remote host") +//} diff --git a/handler/session.go b/handler/session.go index 3e03ed3c..fd59e02a 100644 --- a/handler/session.go +++ b/handler/session.go @@ -1,16 +1,14 @@ package handler -import ( - "github.com/giongto35/cloud-game/webrtc" -) +import "github.com/giongto35/cloud-game/webrtc" // Session represents a session connected from the browser to the current server // It requires one connection to browser and one connection to the overlord // connection to browser is 1-1. connection to overlord is n - 1 // Peerconnection can be from other server to ensure better latency type Session struct { - ID string - BrowserClient *BrowserClient + ID string + //BrowserClient *BrowserClient OverlordClient *OverlordClient peerconnection *webrtc.WebRTC @@ -22,7 +20,3 @@ type Session struct { RoomID string PlayerIndex int } - -func (s *Session) Close() { - s.peerconnection.StopClient() -} diff --git a/overlord/browser.go b/overlord/browser.go new file mode 100644 index 00000000..d0fccdb0 --- /dev/null +++ b/overlord/browser.go @@ -0,0 +1,195 @@ +package overlord + +import ( + "log" + + "github.com/giongto35/cloud-game/cws" + "github.com/gorilla/websocket" +) + +type BrowserClient struct { + *cws.Client +} + +// RouteBrowser are all routes server received from browser +func (s *Session) RouteBrowser() { + iceCandidates := [][]byte{} + + browserClient := s.BrowserClient + + browserClient.Receive("heartbeat", func(resp cws.WSPacket) cws.WSPacket { + return resp + }) + + browserClient.Receive("icecandidate", func(resp cws.WSPacket) cws.WSPacket { + log.Println("Received candidates ", resp.Data) + iceCandidates = append(iceCandidates, []byte(resp.Data)) + return cws.EmptyPacket + }) + + browserClient.Receive("initwebrtc", func(resp cws.WSPacket) cws.WSPacket { + log.Println("Overlord: Received a sdp request from a browser") + log.Println("Overlord: Relay sdp request from a browser to worker") + // relay SDP to target worker and get back SDP of the worker + // TODO: Async + log.Println("Overlord: serverID: ", s.ServerID) + sdp := s.handler.servers[s.ServerID].SyncSend( + resp, + ) + + return sdp + }) + + browserClient.Receive("quit", func(resp cws.WSPacket) (req cws.WSPacket) { + log.Println("Received quit", req) + s.GameName = resp.Data + s.RoomID = resp.RoomID + s.PlayerIndex = resp.PlayerIndex + + // TODO: + //room := s.handler.getRoom(s.RoomID) + //if room.IsPCInRoom(s.peerconnection) { + //s.handler.detachPeerConn(s.peerconnection) + //} + log.Println("Sending to target host", resp.TargetHostID, " ", resp) + resp = s.handler.servers[resp.TargetHostID].SyncSend( + resp, + ) + + return cws.EmptyPacket + }) + + browserClient.Receive("start", func(resp cws.WSPacket) cws.WSPacket { + log.Println("Overlord: Received a relay start request from a host") + // TODO: Abstract + // TODO: if resp.TargetHostID != ""c:w { + // relay start to target host + log.Println("Sending to target host", resp.TargetHostID, " ", resp) + // TODO: Async + resp = s.handler.servers[s.ServerID].SyncSend( + resp, + ) + + return resp + }) + + //browserClient.Receive("initwebrtc", func(resp cws.WSPacket) cws.WSPacket { + //log.Println("Received user SDP") + //localSession, err := s.peerconnection.StartClient(resp.Data, iceCandidates, config.Width, config.Height) + //if err != nil { + //if err != nil { + //log.Println("Error: Cannot create new webrtc session", err) + //return cws.EmptyPacket + //} + //} + + //return cws.WSPacket{ + //ID: "sdp", + //Data: localSession, + //SessionID: s.ID, + //} + //}) + + // TODO: Add save and load + //browserClient.Receive("save", func(resp cws.WSPacket) (req cws.WSPacket) { + //log.Println("Saving game state") + //req.ID = "save" + //req.Data = "ok" + //if s.RoomID != "" { + //room := s.handler.getRoom(s.RoomID) + //if room == nil { + //return + //} + //err := room.SaveGame() + //if err != nil { + //log.Println("[!] Cannot save game state: ", err) + //req.Data = "error" + //} + //} else { + //req.Data = "error" + //} + + //return req + //}) + + //browserClient.Receive("load", func(resp cws.WSPacket) (req cws.WSPacket) { + //log.Println("Loading game state") + //req.ID = "load" + //req.Data = "ok" + //if s.RoomID != "" { + //room := s.handler.getRoom(s.RoomID) + //err := room.LoadGame() + //if err != nil { + //log.Println("[!] Cannot load game state: ", err) + //req.Data = "error" + //} + //} else { + //req.Data = "error" + //} + + //return req + //}) + + //browserClient.Receive("start", func(resp cws.WSPacket) (req cws.WSPacket) { + //s.GameName = resp.Data + //s.RoomID = resp.RoomID + //s.PlayerIndex = resp.PlayerIndex + + //log.Println("Starting game") + //// If we are connecting to overlord, request corresponding serverID based on roomID + //if s.OverlordClient != nil { + //roomServerID := getServerIDOfRoom(s.OverlordClient, s.RoomID) + //log.Println("Server of RoomID ", s.RoomID, " is ", roomServerID, " while current server is ", s.ServerID) + //// If the target serverID is different from current serverID + //if roomServerID != "" && s.ServerID != roomServerID { + //// TODO: Re -register + //// Bridge Connection to the target serverID + //go s.bridgeConnection(roomServerID, s.GameName, s.RoomID, s.PlayerIndex) + //return + //} + //} + + //// Get Room in local server + //// TODO: check if roomID is in the current server + //room := s.handler.getRoom(s.RoomID) + //log.Println("Got Room from local ", room, " ID: ", s.RoomID) + //// If room is not running + //if room == nil { + //// Create new room + //room = s.handler.createNewRoom(s.GameName, s.RoomID, s.PlayerIndex) + //// Wait for done signal from room + //go func() { + //<-room.Done + //s.handler.detachRoom(room.ID) + //}() + //} + + //// Attach peerconnection to room. If PC is already in room, don't detach + //log.Println("Is PC in room", room.IsPCInRoom(s.peerconnection)) + //if !room.IsPCInRoom(s.peerconnection) { + //s.handler.detachPeerConn(s.peerconnection) + //room.AddConnectionToRoom(s.peerconnection, s.PlayerIndex) + //} + //s.RoomID = room.ID + + //// Register room to overlord if we are connecting to overlord + //if room != nil && s.OverlordClient != nil { + //s.OverlordClient.Send(cws.WSPacket{ + //ID: "registerRoom", + //Data: s.RoomID, + //}, nil) + //} + //req.ID = "start" + //req.RoomID = s.RoomID + //req.SessionID = s.ID + + //return req + //}) +} + +// NewOverlordClient returns a client connecting to browser. This connection exchanges information between clients and server +func NewBrowserClient(c *websocket.Conn) *BrowserClient { + return &BrowserClient{ + Client: cws.NewClient(c), + } +} diff --git a/handler/gamelist/games.go b/overlord/gamelist/games.go similarity index 100% rename from handler/gamelist/games.go rename to overlord/gamelist/games.go diff --git a/overlord/overlord.go b/overlord/handlers.go similarity index 55% rename from overlord/overlord.go rename to overlord/handlers.go index 4b7cfb5e..ce5195a8 100644 --- a/overlord/overlord.go +++ b/overlord/handlers.go @@ -1,14 +1,24 @@ package overlord import ( + "errors" "fmt" + "io/ioutil" "log" "math/rand" "net/http" "strconv" "github.com/giongto35/cloud-game/cws" + "github.com/giongto35/cloud-game/overlord/gamelist" "github.com/gorilla/websocket" + uuid "github.com/satori/go.uuid" +) + +const ( + gameboyIndex = "./static/gameboy2.html" + debugIndex = "./static/gameboy2.html" + gamePath = "games" ) type Server struct { @@ -18,6 +28,7 @@ type Server struct { } var upgrader = websocket.Upgrader{} +var errNotFound = errors.New("Not found") func NewServer() *Server { return &Server{ @@ -28,6 +39,22 @@ func NewServer() *Server { } } +// GetWeb returns web frontend +func (o *Server) GetWeb(w http.ResponseWriter, r *http.Request) { + indexFN := "" + //if h.isDebug { + //indexFN = debugIndex + //} else { + indexFN = gameboyIndex + //} + + bs, err := ioutil.ReadFile(indexFN) + if err != nil { + log.Fatal(err) + } + w.Write(bs) +} + // If it's overlord, handle overlord connection (from host to overlord) func (o *Server) WSO(w http.ResponseWriter, r *http.Request) { fmt.Println("Connected") @@ -73,72 +100,68 @@ func (o *Server) WSO(w http.ResponseWriter, r *http.Request) { Data: o.roomToServer[resp.Data], } }) - - // Relay message from server to other target server - // TODO: Generalize - client.Receive("initwebrtc", func(resp cws.WSPacket) cws.WSPacket { - log.Println("Overlord: Received a relay sdp request from a host") - // TODO: Abstract - if resp.TargetHostID != serverID { - log.Println("Overlord: Sending relay sdp to target host") - // relay SDP to target host and get back sdp - // TODO: Async - sdp := o.servers[resp.TargetHostID].SyncSend( - resp, - ) - - return sdp - } - log.Println("Overlord: Target host is overlord itself: start peerconnection") - // If the target is in master - // start by its old - //localSession, err := wssession.peerconnection.StartClient(resp.Data, width, height) - //if err != nil { - //log.Fatalln(err) - //} - - //return cws.WSPacket{ - //ID: "sdp", - //Data: localSession, - //} - return cws.EmptyPacket - }) - - // TODO: use relay ID type - // TODO: Merge sdp and start - client.Receive("start", func(resp cws.WSPacket) cws.WSPacket { - log.Println("Overlord: Received a relay start request from a host") - // TODO: Abstract - if resp.TargetHostID != serverID { - // relay start to target host - log.Println("Sending to target host", resp.TargetHostID, " ", resp) - // TODO: Async - resp := o.servers[resp.TargetHostID].SyncSend( - resp, - ) - - return resp - } - log.Println("Overlord: Target host is overlord itself: start game") - //// If the target is in master - //// start by its old - //roomID, isNewRoom := startSession(wssession.peerconnection, resp.Data, resp.RoomID, resp.PlayerIndex) - //// Bridge always access to old room - //// TODO: log warn - //if isNewRoom == true { - //log.Fatal("Bridge should not spawn new room") - //} - - //return cws.WSPacket{ - //ID: "start", - //RoomID: roomID, - //} - return cws.EmptyPacket - }) - client.Listen() } +func (o *Server) WS(w http.ResponseWriter, r *http.Request) { + log.Println("Browser connected to overlord") + //TODO: Add it back + //defer func() { + //if r := recover(); r != nil { + //log.Println("Warn: Something wrong. Recovered in ", r) + //} + //}() + + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("[!] WS upgrade:", err) + return + } + defer c.Close() + + client := NewBrowserClient(c) + sessionID := uuid.Must(uuid.NewV4()).String() + serverID, err := o.findBestServer() + if err != nil { + log.Fatal(err) + } + + wssession := &Session{ + ID: sessionID, + BrowserClient: client, + handler: o, + ServerID: serverID, + } + //defer wssession.Close() + log.Println("New client will conect to server", wssession.ServerID) + + wssession.RouteBrowser() + + wssession.BrowserClient.Send(cws.WSPacket{ + ID: "gamelist", + Data: gamelist.GetEncodedGameList(gamePath), + }, nil) + + wssession.BrowserClient.Listen() +} + +func (o *Server) findBestServer() (string, error) { + // TODO: Find best Server by latency, currently return by ping + if len(o.servers) == 0 { + return "", errors.New("No server found") + } + + r := rand.Intn(len(o.servers)) + for k, _ := range o.servers { + if r == 0 { + return k, nil + } + r-- + } + + return "", errors.New("No server found") +} + func (o *Server) cleanConnection(client *cws.Client, serverID string) { log.Println("Unregister server from overlord") // Remove serverID from servers diff --git a/overlord/session.go b/overlord/session.go new file mode 100644 index 00000000..0b9e015b --- /dev/null +++ b/overlord/session.go @@ -0,0 +1,21 @@ +package overlord + +// Session represents a session connected from the browser to the current server +// It requires one connection to browser and one connection to the overlord +// connection to browser is 1-1. connection to overlord is n - 1 +// Peerconnection can be from other server to ensure better latency +type Session struct { + ID string + BrowserClient *BrowserClient + WorkerClient *WorkerClient + //OverlordClient *OverlordClient + //peerconnection *webrtc.WebRTC + + // TODO: Decouple this + handler *Server + + ServerID string + GameName string + RoomID string + PlayerIndex int +} diff --git a/overlord/worker.go b/overlord/worker.go new file mode 100644 index 00000000..616fccb9 --- /dev/null +++ b/overlord/worker.go @@ -0,0 +1,176 @@ +package overlord + +import ( + "log" + + "github.com/giongto35/cloud-game/cws" + "github.com/gorilla/websocket" +) + +type WorkerClient struct { + *cws.Client +} + +// RouteWorker are all routes server received from worker +func (s *Session) RouteWorker() { + iceCandidates := [][]byte{} + + workerClient := s.WorkerClient + + workerClient.Receive("heartbeat", func(resp cws.WSPacket) cws.WSPacket { + return resp + }) + + workerClient.Receive("icecandidate", func(resp cws.WSPacket) cws.WSPacket { + log.Println("Received candidates ", resp.Data) + iceCandidates = append(iceCandidates, []byte(resp.Data)) + return cws.EmptyPacket + }) + + workerClient.Receive("initwebrtc", func(resp cws.WSPacket) cws.WSPacket { + log.Println("Overlord: Received sdp request from a worker") + log.Println("Overlord: Sending back sdp to browser") + s.BrowserClient.Send(resp, nil) + + return cws.EmptyPacket + }) + + workerClient.Receive("quit", func(resp cws.WSPacket) (req cws.WSPacket) { + log.Println("Received quit", req) + s.GameName = resp.Data + s.RoomID = resp.RoomID + s.PlayerIndex = resp.PlayerIndex + + // TODO: + //room := s.handler.getRoom(s.RoomID) + //if room.IsPCInRoom(s.peerconnection) { + //s.handler.detachPeerConn(s.peerconnection) + //} + log.Println("Sending to target host", resp.TargetHostID, " ", resp) + resp = s.handler.servers[resp.TargetHostID].SyncSend( + resp, + ) + + return cws.EmptyPacket + }) + + //browserClient.Receive("initwebrtc", func(resp cws.WSPacket) cws.WSPacket { + //log.Println("Received user SDP") + //localSession, err := s.peerconnection.StartClient(resp.Data, iceCandidates, config.Width, config.Height) + //if err != nil { + //if err != nil { + //log.Println("Error: Cannot create new webrtc session", err) + //return cws.EmptyPacket + //} + //} + + //return cws.WSPacket{ + //ID: "sdp", + //Data: localSession, + //SessionID: s.ID, + //} + //}) + + // TODO: Add save and load + //browserClient.Receive("save", func(resp cws.WSPacket) (req cws.WSPacket) { + //log.Println("Saving game state") + //req.ID = "save" + //req.Data = "ok" + //if s.RoomID != "" { + //room := s.handler.getRoom(s.RoomID) + //if room == nil { + //return + //} + //err := room.SaveGame() + //if err != nil { + //log.Println("[!] Cannot save game state: ", err) + //req.Data = "error" + //} + //} else { + //req.Data = "error" + //} + + //return req + //}) + + //browserClient.Receive("load", func(resp cws.WSPacket) (req cws.WSPacket) { + //log.Println("Loading game state") + //req.ID = "load" + //req.Data = "ok" + //if s.RoomID != "" { + //room := s.handler.getRoom(s.RoomID) + //err := room.LoadGame() + //if err != nil { + //log.Println("[!] Cannot load game state: ", err) + //req.Data = "error" + //} + //} else { + //req.Data = "error" + //} + + //return req + //}) + + //browserClient.Receive("start", func(resp cws.WSPacket) (req cws.WSPacket) { + //s.GameName = resp.Data + //s.RoomID = resp.RoomID + //s.PlayerIndex = resp.PlayerIndex + + //log.Println("Starting game") + //// If we are connecting to overlord, request corresponding serverID based on roomID + //if s.OverlordClient != nil { + //roomServerID := getServerIDOfRoom(s.OverlordClient, s.RoomID) + //log.Println("Server of RoomID ", s.RoomID, " is ", roomServerID, " while current server is ", s.ServerID) + //// If the target serverID is different from current serverID + //if roomServerID != "" && s.ServerID != roomServerID { + //// TODO: Re -register + //// Bridge Connection to the target serverID + //go s.bridgeConnection(roomServerID, s.GameName, s.RoomID, s.PlayerIndex) + //return + //} + //} + + //// Get Room in local server + //// TODO: check if roomID is in the current server + //room := s.handler.getRoom(s.RoomID) + //log.Println("Got Room from local ", room, " ID: ", s.RoomID) + //// If room is not running + //if room == nil { + //// Create new room + //room = s.handler.createNewRoom(s.GameName, s.RoomID, s.PlayerIndex) + //// Wait for done signal from room + //go func() { + //<-room.Done + //s.handler.detachRoom(room.ID) + //}() + //} + + //// Attach peerconnection to room. If PC is already in room, don't detach + //log.Println("Is PC in room", room.IsPCInRoom(s.peerconnection)) + //if !room.IsPCInRoom(s.peerconnection) { + //s.handler.detachPeerConn(s.peerconnection) + //room.AddConnectionToRoom(s.peerconnection, s.PlayerIndex) + //} + //s.RoomID = room.ID + + //// Register room to overlord if we are connecting to overlord + //if room != nil && s.OverlordClient != nil { + //s.OverlordClient.Send(cws.WSPacket{ + //ID: "registerRoom", + //Data: s.RoomID, + //}, nil) + //} + //req.ID = "start" + //req.RoomID = s.RoomID + //req.SessionID = s.ID + + //return req + //}) +} + +// NewWorkerClient returns a client connecting to worker. This connection exchanges information between workers and server +func NewWorkerClient(c *websocket.Conn) *WorkerClient { + return &WorkerClient{ + Client: cws.NewClient(c), + } +}