Some more changes for the new game handling api and also rate limiting #13

Merged
marco merged 4 commits from game-handler into master 2024-05-12 13:48:23 +00:00
10 changed files with 116 additions and 80 deletions

View File

@ -1,24 +1,26 @@
package handler package handler
import ( import (
"log"
"mchess_server/api" "mchess_server/api"
"mchess_server/chess" "mchess_server/chess"
lobbies "mchess_server/lobby_registry" "mchess_server/lobbies"
"mchess_server/usher"
"mchess_server/utils" "mchess_server/utils"
"net/http" "net/http"
"sync" "sync"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"go.uber.org/ratelimit"
) )
var mut sync.Mutex var mut sync.Mutex
var limiter = ratelimit.New(10)
func HostGameHandler(c *gin.Context) {
limiter.Take()
func HostPrivateGameHandler(c *gin.Context) {
player := chess.NewPlayer(uuid.New()) player := chess.NewPlayer(uuid.New())
u := usher.GetUsher() u := lobbies.GetUsher()
mut.Lock() mut.Lock()
defer mut.Unlock() defer mut.Unlock()
@ -36,6 +38,8 @@ func HostPrivateGameHandler(c *gin.Context) {
} }
func GetLobbyForPassphraseHandler(c *gin.Context) { func GetLobbyForPassphraseHandler(c *gin.Context) {
limiter.Take()
reqPassphrase := c.Param("phrase") reqPassphrase := c.Param("phrase")
if reqPassphrase == "" { if reqPassphrase == "" {
c.IndentedJSON(http.StatusBadRequest, reqPassphrase) c.IndentedJSON(http.StatusBadRequest, reqPassphrase)
@ -57,34 +61,17 @@ func GetLobbyForPassphraseHandler(c *gin.Context) {
c.IndentedJSON(http.StatusOK, lobbyInfo) c.IndentedJSON(http.StatusOK, lobbyInfo)
} }
func RegisterForRandomGame(c *gin.Context) { // TODO: this will be replaced by the JoinGameHandler()
player := chess.NewPlayer(uuid.New())
usher := usher.GetUsher()
mut.Lock()
defer mut.Unlock()
lobby := usher.WelcomeNewPlayer(player)
usher.AddPlayerToLobbyAndStartGameIfFull(player, lobby)
info := api.PlayerInfo{
PlayerID: &player.Uuid,
LobbyID: &lobby.Uuid,
}
log.Println("responding with info ", info)
c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, info)
}
func JoinPrivateGame(c *gin.Context) { func JoinPrivateGame(c *gin.Context) {
limiter.Take()
req := api.PlayerInfo{} req := api.PlayerInfo{}
log.Println(c.Request.Body)
err := c.ShouldBindJSON(&req) err := c.ShouldBindJSON(&req)
if err != nil || req.Passphrase == nil || *req.Passphrase == "" { if err != nil || req.Passphrase == nil || *req.Passphrase == "" {
c.IndentedJSON(http.StatusNotFound, req) c.IndentedJSON(http.StatusNotFound, req)
} }
u := usher.GetUsher() u := lobbies.GetUsher()
if req.Passphrase != nil && if req.Passphrase != nil &&
*req.Passphrase != "" && *req.Passphrase != "" &&
@ -126,3 +113,30 @@ func JoinPrivateGame(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, info) c.IndentedJSON(http.StatusOK, info)
} }
func JoinGameHandler(c *gin.Context) {
limiter.Take()
id := c.Param("id")
idAsUUID, err := uuid.Parse(id)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, nil)
return
}
passphrase := api.Passphrase{}
c.ShouldBindJSON(&passphrase)
u := lobbies.GetUsher()
lobby := u.GetLobbyByID(idAsUUID)
if lobby == nil {
c.IndentedJSON(http.StatusNotFound, nil)
return
}
lobbyInfo := api.LobbyInfo{
ID: &lobby.Uuid,
}
c.IndentedJSON(http.StatusOK, lobbyInfo)
}

View File

@ -20,7 +20,7 @@ func Test_GetLobbyFromPassphraseHandler(t *testing.T) {
t.Run("host a lobby", func(t *testing.T) { t.Run("host a lobby", func(t *testing.T) {
r1 := httptest.NewRecorder() r1 := httptest.NewRecorder()
ctx1, e1 := gin.CreateTestContext(r1) ctx1, e1 := gin.CreateTestContext(r1)
e1.GET("/api/hostPrivate", HostPrivateGameHandler) e1.GET("/api/hostPrivate", HostGameHandler)
hostGameRequest, _ := http.NewRequest("GET", "/api/hostPrivate", nil) hostGameRequest, _ := http.NewRequest("GET", "/api/hostPrivate", nil)
ctx1.Request = hostGameRequest ctx1.Request = hostGameRequest

View File

@ -1,4 +1,4 @@
package websocket package handler
import ( import (
"context" "context"
@ -8,7 +8,7 @@ import (
"mchess_server/api" "mchess_server/api"
"net/http" "net/http"
lobbies "mchess_server/lobby_registry" "mchess_server/lobbies"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
gorillaws "github.com/gorilla/websocket" gorillaws "github.com/gorilla/websocket"
@ -21,6 +21,8 @@ var upgrader = gorillaws.Upgrader{
} }
func RegisterWebSocketConnection(c *gin.Context) { func RegisterWebSocketConnection(c *gin.Context) {
limiter.Take()
log.Println(c.Request) log.Println(c.Request)
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
@ -31,6 +33,8 @@ func RegisterWebSocketConnection(c *gin.Context) {
} }
func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) { func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) {
limiter.Take()
msgType, msg, err := conn.ReadMessage() msgType, msg, err := conn.ReadMessage()
if err != nil { if err != nil {
errorMessage := fmt.Sprintf("Reading from websocket connection did not work: %s", err) errorMessage := fmt.Sprintf("Reading from websocket connection did not work: %s", err)
@ -70,3 +74,8 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) {
lobby.Game.SetWebsocketConnectionFor(ctx, player, conn) lobby.Game.SetWebsocketConnectionFor(ctx, player, conn)
log.Println("player after setting connection: ", player) log.Println("player after setting connection: ", player)
} }
func ConnectWsForGame(c *gin.Context) {
limiter.Take()
}

3
go.mod
View File

@ -9,6 +9,8 @@ require (
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
) )
require github.com/benbjohnson/clock v1.3.0 // indirect
require ( require (
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
@ -32,6 +34,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/ratelimit v0.3.1
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect

4
go.sum
View File

@ -1,3 +1,5 @@
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
@ -69,6 +71,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=

View File

@ -1,4 +1,4 @@
package lobby_registry package lobbies
import ( import (
"mchess_server/chess" "mchess_server/chess"

View File

@ -1,4 +1,4 @@
package lobby_registry package lobbies
import ( import (
"mchess_server/utils" "mchess_server/utils"

50
lobbies/usher.go Normal file
View File

@ -0,0 +1,50 @@
package lobbies
import (
"mchess_server/chess"
"mchess_server/utils"
"github.com/google/uuid"
)
type Usher struct {
}
var usherInstance *Usher
func newUsher() *Usher {
return &Usher{}
}
func GetUsher() *Usher {
if usherInstance == nil {
usherInstance = newUsher()
}
return usherInstance
}
func (u *Usher) WelcomeNewPlayer(player *chess.Player) *Lobby {
lobby := GetLobbyRegistry().GetLobbyForPlayer()
return lobby
}
func (u *Usher) CreateNewPrivateLobby(player *chess.Player) *Lobby {
lobby := GetLobbyRegistry().CreateNewPrivateLobby()
return lobby
}
func (u *Usher) FindExistingPrivateLobby(p utils.Passphrase) *Lobby {
lobby := GetLobbyRegistry().GetLobbyByPassphrase(p)
if lobby == nil || lobby.IsFull() {
return nil
}
return lobby
}
func (*Usher) GetLobbyByID(id uuid.UUID) *Lobby {
return GetLobbyRegistry().GetLobbyByUUID(id)
}
func (u *Usher) AddPlayerToLobbyAndStartGameIfFull(player *chess.Player, lobby *Lobby) {
lobby.AddPlayerAndStartGameIfFull(player)
}

View File

@ -4,7 +4,6 @@ import (
"flag" "flag"
"log" "log"
"mchess_server/api/handler" "mchess_server/api/handler"
"mchess_server/api/websocket"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -24,11 +23,13 @@ func main() {
} }
router := gin.Default() router := gin.Default()
router.GET("/api/random", handler.RegisterForRandomGame) router.GET("/api/hostPrivate", handler.HostGameHandler)
router.GET("/api/hostPrivate", handler.HostPrivateGameHandler)
router.POST("/api/joinPrivate", handler.JoinPrivateGame) router.POST("/api/joinPrivate", handler.JoinPrivateGame)
router.GET("/api/ws", websocket.RegisterWebSocketConnection) router.GET("/api/ws", handler.RegisterWebSocketConnection)
router.GET("/api/getLobbyForPassphrase/:phrase", handler.GetLobbyForPassphraseHandler) router.GET("/api/getLobbyForPassphrase/:phrase", handler.GetLobbyForPassphraseHandler)
router.GET("/api/registerWsForGame/:id", handler.ConnectWsForGame)
router.POST("/api/joinGame/:id", handler.JoinGameHandler)
if debugMode { if debugMode {
log.Println("Starting service WITHOUT TLS") log.Println("Starting service WITHOUT TLS")

View File

@ -1,45 +0,0 @@
package usher
import (
"mchess_server/chess"
lobbies "mchess_server/lobby_registry"
"mchess_server/utils"
)
type Usher struct {
}
var instance *Usher
func newUsher() *Usher {
return &Usher{}
}
func GetUsher() *Usher {
if instance == nil {
instance = newUsher()
}
return instance
}
func (u *Usher) WelcomeNewPlayer(player *chess.Player) *lobbies.Lobby {
lobby := lobbies.GetLobbyRegistry().GetLobbyForPlayer()
return lobby
}
func (u *Usher) CreateNewPrivateLobby(player *chess.Player) *lobbies.Lobby {
lobby := lobbies.GetLobbyRegistry().CreateNewPrivateLobby()
return lobby
}
func (u *Usher) FindExistingPrivateLobby(p utils.Passphrase) *lobbies.Lobby {
lobby := lobbies.GetLobbyRegistry().GetLobbyByPassphrase(p)
if lobby == nil || lobby.IsFull() {
return nil
}
return lobby
}
func (u *Usher) AddPlayerToLobbyAndStartGameIfFull(player *chess.Player, lobby *lobbies.Lobby) {
lobby.AddPlayerAndStartGameIfFull(player)
}