diff --git a/go.mod b/go.mod index 69b15af..a10a9f1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,8 @@ module tts-room -go 1.22.2 +go 1.24.0 + +require ( + github.com/gorilla/websocket v1.5.3 + golang.org/x/time v0.14.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..296e66c --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= diff --git a/src/server/server.go b/src/server/server.go new file mode 100644 index 0000000..5e3b852 --- /dev/null +++ b/src/server/server.go @@ -0,0 +1,33 @@ +package server + +import ( + "log" + "net/http" +) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/ws": + if err := s.WS(w, r); err != nil { + log.Println("[ws]", err) + } + default: + http.FileServer(http.Dir("./public")).ServeHTTP(w, r) + } +} + +func (s Server) WS(w http.ResponseWriter, r *http.Request) error { + sess, err := newSession(w, r) + if err != nil { + return err + } + defer sess.Close() + + return sess.Run() +} diff --git a/src/server/session.go b/src/server/session.go new file mode 100644 index 0000000..84e6e28 --- /dev/null +++ b/src/server/session.go @@ -0,0 +1,81 @@ +package server + +import ( + "context" + "log" + "net/http" + "sync" + + "github.com/gorilla/websocket" + "golang.org/x/time/rate" +) + +type session struct { + ctx context.Context + can context.CancelFunc + ws *websocket.Conn + wg sync.WaitGroup +} + +var upgrader = websocket.Upgrader{} + +func newSession(w http.ResponseWriter, r *http.Request) (*session, error) { + c, err := upgrader.Upgrade(w, r, nil) + ctx, can := context.WithCancel(r.Context()) + return &session{ + ctx: ctx, + can: can, + ws: c, + }, err +} + +func (s *session) Close() { + if s.ws != nil { + s.ws.Close() + } + s.ws = nil + s.can() + s.wg.Wait() +} + +func (s *session) Run() error { + defer s.Close() + + go s.gather() + go s.scatter() + + <-s.ctx.Done() + return s.ctx.Err() +} + +func (s *session) gather() { + s.while(func() error { + mt, message, err := s.ws.ReadMessage() + if err != nil { + return err + } + log.Println(" read:", mt, message) // TODO + return nil + }) +} + +func (s *session) scatter() { + s.while(func() error { + return s.ws.WriteMessage(1, []byte("message")) // TODO + }) +} + +func (s *session) while(foo func() error) { + defer s.can() + + s.wg.Add(1) + defer s.wg.Done() + + l := rate.NewLimiter(20, 1) + for l.Wait(s.ctx) == nil { + if err := foo(); err != nil { + log.Println(err) + return + } + } +}