2022-11-10 04:26:21 +00:00
|
|
|
package gametable
|
|
|
|
|
|
|
|
import (
|
2022-11-11 06:00:05 +00:00
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"golang.org/x/time/rate"
|
2023-01-31 04:38:30 +00:00
|
|
|
"hacklab.nilfm.cc/felt/admin"
|
|
|
|
"hacklab.nilfm.cc/felt/models"
|
|
|
|
"hacklab.nilfm.cc/felt/mongodb"
|
|
|
|
"hacklab.nilfm.cc/quartzgun/auth"
|
|
|
|
"hacklab.nilfm.cc/quartzgun/cookie"
|
|
|
|
"hacklab.nilfm.cc/quartzgun/renderer"
|
2023-02-10 05:45:09 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"nhooyr.io/websocket"
|
2022-11-11 06:00:05 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2022-11-10 04:26:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Subscriber struct {
|
2022-11-11 06:00:05 +00:00
|
|
|
msgs chan []byte
|
|
|
|
closeSlow func()
|
2022-11-10 04:26:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type GameTableServer struct {
|
2022-11-11 06:00:05 +00:00
|
|
|
subscribeMessageBuffer int
|
|
|
|
publishLimiter *rate.Limiter
|
|
|
|
logf func(f string, v ...interface{})
|
|
|
|
serveMux http.ServeMux
|
|
|
|
subscribersLock sync.Mutex
|
2022-12-07 04:58:42 +00:00
|
|
|
subscribers map[*Subscriber]models.TableKey
|
|
|
|
dbAdapter mongodb.DbAdapter
|
2022-11-10 04:26:21 +00:00
|
|
|
}
|
|
|
|
|
2022-12-22 16:39:39 +00:00
|
|
|
func New(adapter mongodb.DbAdapter, udb auth.UserStore) *GameTableServer {
|
2022-11-11 06:00:05 +00:00
|
|
|
srvr := &GameTableServer{
|
|
|
|
subscribeMessageBuffer: 16,
|
|
|
|
logf: log.Printf,
|
2022-12-07 04:58:42 +00:00
|
|
|
subscribers: make(map[*Subscriber]models.TableKey),
|
2022-11-11 06:00:05 +00:00
|
|
|
publishLimiter: rate.NewLimiter(rate.Every(time.Millisecond*100), 8),
|
2022-11-27 15:56:48 +00:00
|
|
|
dbAdapter: adapter,
|
2022-11-11 06:00:05 +00:00
|
|
|
}
|
2023-01-26 05:57:31 +00:00
|
|
|
srvr.serveMux.Handle("/table/", http.StripPrefix("/table/", renderer.Subtree("./static")))
|
|
|
|
srvr.serveMux.Handle("/admin/", http.StripPrefix("/admin", admin.CreateAdminInterface(udb, adapter)))
|
2022-11-11 06:00:05 +00:00
|
|
|
srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler)
|
|
|
|
srvr.serveMux.HandleFunc("/publish", srvr.publishHandler)
|
|
|
|
|
|
|
|
return srvr
|
2022-11-10 04:26:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *GameTableServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2022-11-11 06:00:05 +00:00
|
|
|
self.serveMux.ServeHTTP(w, r)
|
2022-11-10 04:26:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *GameTableServer) subscribeHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
c, err := websocket.Accept(w, r, nil)
|
|
|
|
if err != nil {
|
|
|
|
self.logf("%v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer c.Close(websocket.StatusInternalError, "")
|
|
|
|
|
2022-12-07 04:58:42 +00:00
|
|
|
err = self.subscribe(r, c)
|
2022-11-10 04:26:21 +00:00
|
|
|
if errors.Is(err, context.Canceled) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if websocket.CloseStatus(err) == websocket.StatusNormalClosure ||
|
|
|
|
websocket.CloseStatus(err) == websocket.StatusGoingAway {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
self.logf("%v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-27 15:56:48 +00:00
|
|
|
func (self *GameTableServer) subscribe(r *http.Request, c *websocket.Conn) error {
|
|
|
|
ctx := r.Context()
|
2022-11-10 04:26:21 +00:00
|
|
|
ctx = c.CloseRead(ctx)
|
|
|
|
|
|
|
|
s := &Subscriber{
|
|
|
|
msgs: make(chan []byte, self.subscribeMessageBuffer),
|
|
|
|
closeSlow: func() {
|
|
|
|
c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
|
|
|
|
},
|
|
|
|
}
|
2022-11-27 15:56:48 +00:00
|
|
|
|
2023-02-11 17:30:26 +00:00
|
|
|
tableKey := models.TableKey{}
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&tableKey)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
2022-11-27 15:56:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !self.dbAdapter.CheckTable(tableKey) {
|
|
|
|
return errors.New("Table with matching key was not found on this server")
|
|
|
|
}
|
|
|
|
|
2022-12-07 04:58:42 +00:00
|
|
|
self.addSubscriber(s, tableKey)
|
2022-11-27 15:56:48 +00:00
|
|
|
|
2022-11-10 04:26:21 +00:00
|
|
|
defer self.deleteSubscriber(s)
|
2022-11-27 15:56:48 +00:00
|
|
|
select {
|
|
|
|
case s.msgs <- self.getCurrentState(tableKey):
|
|
|
|
default:
|
|
|
|
go s.closeSlow()
|
|
|
|
}
|
2022-11-10 04:26:21 +00:00
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case msg := <-s.msgs:
|
|
|
|
err := writeTimeout(ctx, time.Second*5, c, msg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *GameTableServer) publishHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != "POST" {
|
|
|
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
body := http.MaxBytesReader(w, r.Body, 8192)
|
|
|
|
msg, err := ioutil.ReadAll(body)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.publish(msg)
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusAccepted)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *GameTableServer) publish(msg []byte) {
|
|
|
|
self.subscribersLock.Lock()
|
|
|
|
defer self.subscribersLock.Unlock()
|
|
|
|
|
2022-11-11 06:00:05 +00:00
|
|
|
// decode message and store in DB
|
2023-02-11 17:30:26 +00:00
|
|
|
tableMsg := models.TableMessage{}
|
|
|
|
err := json.NewDecoder(msg).Decode(&tableMsg)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.writeToDB(tableMsg)
|
2022-11-10 04:26:21 +00:00
|
|
|
|
|
|
|
self.publishLimiter.Wait(context.Background())
|
|
|
|
|
2023-02-11 17:30:26 +00:00
|
|
|
for s, k := range self.subscribers {
|
|
|
|
if k == tableMsg.tableKey {
|
|
|
|
select {
|
|
|
|
case s.msgs <- msg:
|
|
|
|
default:
|
|
|
|
go s.closeSlow()
|
|
|
|
}
|
2022-11-10 04:26:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-11 17:30:26 +00:00
|
|
|
func (self *GameTableServer) getCurrentState(tableKey models.TableKey) ([]byte, error) {
|
2022-11-27 15:56:48 +00:00
|
|
|
// get diceroll log, map, and token state
|
2023-02-11 17:30:26 +00:00
|
|
|
|
2022-11-27 15:56:48 +00:00
|
|
|
// build into a []byte message
|
|
|
|
|
2023-02-11 17:30:26 +00:00
|
|
|
return make([]byte, 1), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *GameTableServer) writeToDB(tableMsg models.TableMessage) error {
|
|
|
|
return nil
|
2022-11-27 15:56:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *GameTableServer) addSubscriber(s *Subscriber, k models.TableKey) {
|
2022-11-10 04:26:21 +00:00
|
|
|
self.subscribersLock.Lock()
|
2022-11-27 15:56:48 +00:00
|
|
|
self.subscribers[s] = k
|
2022-11-10 04:26:21 +00:00
|
|
|
self.subscribersLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *GameTableServer) deleteSubscriber(s *Subscriber) {
|
|
|
|
self.subscribersLock.Lock()
|
|
|
|
delete(self.subscribers, s)
|
|
|
|
self.subscribersLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeTimeout(ctx context.Context, timeout time.Duration, c *websocket.Conn, msg []byte) error {
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
return c.Write(ctx, websocket.MessageText, msg)
|
2022-11-11 06:00:05 +00:00
|
|
|
}
|