2022-06-28 16:17:44 -07:00
|
|
|
package board
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
|
"github.com/rs/cors"
|
2022-09-02 14:35:55 -07:00
|
|
|
log "github.com/spf13/jwalterweatherman"
|
2022-06-28 16:17:44 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// A minimal server capable of handling the requests from a single browser client running the board viewer.
|
|
|
|
|
type BoardServer struct {
|
|
|
|
|
game Game
|
|
|
|
|
events chan GameEvent // channel for sending events from the game runner to the browser client
|
|
|
|
|
done chan bool // channel for signalling (via closing) that all events have been sent to the browser client
|
|
|
|
|
|
|
|
|
|
httpServer *http.Server
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var upgrader = websocket.Upgrader{
|
|
|
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
|
|
|
return true
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewBoardServer(game Game) *BoardServer {
|
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
|
|
|
|
|
server := &BoardServer{
|
|
|
|
|
game: game,
|
|
|
|
|
events: make(chan GameEvent, 1000), // buffered channel to allow game to run ahead of browser client
|
|
|
|
|
done: make(chan bool),
|
|
|
|
|
httpServer: &http.Server{
|
|
|
|
|
Handler: cors.Default().Handler(mux),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mux.HandleFunc("/games/"+game.ID, server.handleGame)
|
|
|
|
|
mux.HandleFunc("/games/"+game.ID+"/events", server.handleWebsocket)
|
|
|
|
|
|
|
|
|
|
return server
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle the /games/:id request made by the board to fetch the game metadata.
|
|
|
|
|
func (server *BoardServer) handleGame(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Add("Content-Type", "application/json")
|
|
|
|
|
err := json.NewEncoder(w).Encode(struct {
|
|
|
|
|
Game Game
|
|
|
|
|
}{server.game})
|
|
|
|
|
if err != nil {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Unable to serialize game: %v", err)
|
2022-06-28 16:17:44 -07:00
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle the /games/:id/events websocket request made by the board to receive game events.
|
|
|
|
|
func (server *BoardServer) handleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
ws, err := upgrader.Upgrade(w, r, nil)
|
|
|
|
|
if err != nil {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Unable to upgrade connection: %v", err)
|
2022-06-28 16:17:44 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
err = ws.Close()
|
|
|
|
|
if err != nil {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Unable to close websocket stream")
|
2022-06-28 16:17:44 -07:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
for event := range server.events {
|
|
|
|
|
jsonStr, err := json.Marshal(event)
|
|
|
|
|
if err != nil {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Unable to serialize event for websocket: %v", err)
|
2022-06-28 16:17:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = ws.WriteMessage(websocket.TextMessage, jsonStr)
|
|
|
|
|
if err != nil {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Unable to write to websocket: %v", err)
|
2022-06-28 16:17:44 -07:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-02 14:35:55 -07:00
|
|
|
log.DEBUG.Printf("Finished writing all game events, signalling game server to stop")
|
2022-06-28 16:17:44 -07:00
|
|
|
close(server.done)
|
|
|
|
|
|
2022-09-02 14:35:55 -07:00
|
|
|
log.DEBUG.Printf("Sending websocket close message")
|
2022-06-28 16:17:44 -07:00
|
|
|
err = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
|
|
|
|
if err != nil {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Problem closing websocket: %v", err)
|
2022-06-28 16:17:44 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (server *BoardServer) Listen() (string, error) {
|
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
go func() {
|
|
|
|
|
err = server.httpServer.Serve(listener)
|
|
|
|
|
if err != http.ErrServerClosed {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Error in board HTTP server: %v", err)
|
2022-06-28 16:17:44 -07:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
url := "http://" + listener.Addr().String()
|
|
|
|
|
|
|
|
|
|
return url, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (server *BoardServer) Shutdown() {
|
|
|
|
|
close(server.events)
|
|
|
|
|
|
2022-09-02 14:35:55 -07:00
|
|
|
log.DEBUG.Printf("Waiting for websocket clients to finish")
|
2022-06-28 16:17:44 -07:00
|
|
|
<-server.done
|
2022-09-02 14:35:55 -07:00
|
|
|
log.DEBUG.Printf("Server is done, exiting")
|
2022-06-28 16:17:44 -07:00
|
|
|
|
|
|
|
|
err := server.httpServer.Shutdown(context.Background())
|
|
|
|
|
if err != nil {
|
2022-09-02 14:35:55 -07:00
|
|
|
log.ERROR.Printf("Error shutting down HTTP server: %v", err)
|
2022-06-28 16:17:44 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (server *BoardServer) SendEvent(event GameEvent) {
|
|
|
|
|
server.events <- event
|
|
|
|
|
}
|