More work for introducing a new game handler, also: ratelimiter

This commit is contained in:
Marco 2024-05-12 15:36:30 +02:00
parent 58002a1b38
commit fc088a04fe
9 changed files with 49 additions and 199 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 != "" &&
@ -127,8 +114,29 @@ func JoinPrivateGame(c *gin.Context) {
c.IndentedJSON(http.StatusOK, info) c.IndentedJSON(http.StatusOK, info)
} }
func JoinGame(c *gin.Context) { func JoinGameHandler(c *gin.Context) {
gameID := c.Param("id") limiter.Take()
log.Println(gameID)
c.JSON(http.StatusOK, gameID) 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

@ -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"

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,58 +0,0 @@
package lobby_registry
import (
"mchess_server/chess"
"mchess_server/utils"
"github.com/google/uuid"
)
type Lobby struct {
Uuid uuid.UUID
Game *chess.Game
PlayerJoined chan bool
Passphrase utils.Passphrase
}
func NewEmptyLobbyWithUUID(uuid uuid.UUID) *Lobby {
return &Lobby{
Uuid: uuid,
Game: chess.NewGame(),
PlayerJoined: make(chan bool),
}
}
func newEmptyLobbyWithPassphrase() *Lobby {
lobby := NewEmptyLobbyWithUUID(uuid.New())
lobby.Passphrase = utils.NewPassphrase()
return lobby
}
func (l *Lobby) AddPlayerAndStartGameIfFull(player *chess.Player) {
l.Game.AddPlayersToGame(player)
if l.IsFull() {
l.Game.StartHandling()
}
}
func (w *Lobby) IsFull() bool {
return len(w.Game.GetPlayers()) == 2
}
func (l *Lobby) GetPlayerByUUID(uuid uuid.UUID) (*chess.Player, bool) {
for _, player := range l.Game.GetPlayers() {
if player.Uuid == uuid {
return player, true
}
}
return nil, false
}
func (l *Lobby) GetPlayer1() *chess.Player {
return l.Game.GetPlayer1()
}
func (l *Lobby) GetPlayer2() *chess.Player {
return l.Game.GetPlayer2()
}

View File

@ -1,61 +0,0 @@
package lobby_registry
import (
"mchess_server/utils"
"github.com/google/uuid"
)
type LobbyRegistry struct {
lobbies map[uuid.UUID]*Lobby
}
var instance *LobbyRegistry
func GetLobbyRegistry() *LobbyRegistry {
if instance == nil {
instance = newLobbyRegistry()
}
return instance
}
func newLobbyRegistry() *LobbyRegistry {
return &LobbyRegistry{lobbies: make(map[uuid.UUID]*Lobby)}
}
func (r *LobbyRegistry) CreateNewPrivateLobby() *Lobby {
lobby := newEmptyLobbyWithPassphrase()
r.addNewLobby(lobby)
return lobby
}
func (r *LobbyRegistry) GetLobbyForPlayer() *Lobby {
for _, lobby := range r.lobbies {
if !lobby.IsFull() {
return lobby
}
}
newLobby := NewEmptyLobbyWithUUID(uuid.New())
r.addNewLobby(newLobby)
return newLobby
}
func (r *LobbyRegistry) GetLobbyByUUID(uuid uuid.UUID) *Lobby {
return r.lobbies[uuid]
}
func (r *LobbyRegistry) GetLobbyByPassphrase(p utils.Passphrase) *Lobby {
for _, lobby := range r.lobbies {
if lobby.Passphrase == p {
return lobby
}
}
return nil
}
func (r *LobbyRegistry) addNewLobby(lobby *Lobby) uuid.UUID {
r.lobbies[lobby.Uuid] = lobby
return lobby.Uuid
}

View File

@ -24,14 +24,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", websocket.RegisterWebSocketConnection)
router.GET("/api/getLobbyForPassphrase/:phrase", handler.GetLobbyForPassphraseHandler) router.GET("/api/getLobbyForPassphrase/:phrase", handler.GetLobbyForPassphraseHandler)
router.GET("/api/registerWsForGame/:id", websocket.ConnectWsForGame) router.GET("/api/registerWsForGame/:id", websocket.ConnectWsForGame)
router.POST("/api/joinGame/:id", handler.JoinGame) 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)
}