add websocket
This commit is contained in:
parent
fc00fdb7a3
commit
0d64ba751e
|
@ -26,6 +26,7 @@ done
|
|||
|
||||
# Failures have incomplete results, so don't send
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN -f profile.cov
|
||||
goveralls -v -coverprofile=profile.cov -service=$CI -repotoken=$COVERALLS_REPO_TOKEN
|
||||
fi
|
||||
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const channelBufSize = 100
|
||||
|
||||
type Client struct {
|
||||
server *Server
|
||||
ws *websocket.Conn
|
||||
out chan *Message
|
||||
writeQuit chan bool
|
||||
readQuit chan bool
|
||||
}
|
||||
|
||||
func NewClient(s *Server, ws *websocket.Conn) *Client {
|
||||
|
||||
if ws == nil {
|
||||
log.Panic("ws cannot be nil")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
server: s,
|
||||
ws: ws,
|
||||
out: make(chan *Message, channelBufSize),
|
||||
writeQuit: make(chan bool),
|
||||
readQuit: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetID() string {
|
||||
return c.ws.RemoteAddr().String()
|
||||
}
|
||||
|
||||
func (c *Client) Write(msg *Message) {
|
||||
select {
|
||||
case c.out <- msg:
|
||||
default:
|
||||
c.server.SessionManager.Remove(c)
|
||||
c.server.DelClient(c)
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
c.writeQuit <- true
|
||||
c.readQuit <- true
|
||||
log.Info("client disconnecting...", c.GetID())
|
||||
}
|
||||
|
||||
// Listen Write and Read request via channel
|
||||
func (c *Client) Listen() {
|
||||
go c.listenWrite()
|
||||
c.server.AddClient(c)
|
||||
c.server.SessionManager.Init(c)
|
||||
c.listenRead()
|
||||
}
|
||||
|
||||
func (c *Client) handleInput(msg *Message) {
|
||||
msg.From = c
|
||||
if c.server.SessionManager.HandleMessage(msg) {
|
||||
return
|
||||
}
|
||||
if ok, err := msg.Validate(); ok {
|
||||
c.server.msgChanIn <- msg
|
||||
} else {
|
||||
log.Println("no valid msg for:", c.GetID(), "error:", err, "\nmessage:", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Listen write request via channel
|
||||
func (c *Client) listenWrite() {
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.out:
|
||||
websocket.WriteJSON(c.ws, msg)
|
||||
|
||||
case <-c.writeQuit:
|
||||
c.server.SessionManager.Remove(c)
|
||||
c.server.DelClient(c)
|
||||
close(c.out)
|
||||
close(c.writeQuit)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen read request via channel
|
||||
func (c *Client) listenRead() {
|
||||
for {
|
||||
select {
|
||||
|
||||
case <-c.readQuit:
|
||||
c.server.SessionManager.Remove(c)
|
||||
c.server.DelClient(c)
|
||||
close(c.readQuit)
|
||||
return
|
||||
|
||||
default:
|
||||
var msg Message
|
||||
err := websocket.ReadJSON(c.ws, &msg)
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Println(err, c.GetID())
|
||||
} else {
|
||||
c.handleInput(&msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
Session uuid.UUID `json:"-"`
|
||||
From *Client `json:"-"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body interface{} `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
func (msg *Message) Validate() (bool, error) {
|
||||
if msg.Subject == "" {
|
||||
return false, errors.New("no subject definied")
|
||||
}
|
||||
if msg.From == nil {
|
||||
return false, errors.New("no sender definied")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (msg *Message) Answer(subject string, body interface{}) {
|
||||
msg.From.Write(&Message{
|
||||
ID: msg.ID,
|
||||
Session: msg.Session,
|
||||
From: msg.From,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package websocket
|
||||
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMSGValidate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
msg := &Message{}
|
||||
assert.False(msg.Validate())
|
||||
|
||||
msg.Subject = "login"
|
||||
assert.False(msg.Validate())
|
||||
|
||||
msg.From = &Client{}
|
||||
assert.True(msg.Validate())
|
||||
|
||||
msg.Subject = ""
|
||||
assert.False(msg.Validate())
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
msgChanIn chan *Message
|
||||
clients map[string]*Client
|
||||
clientsMutex sync.Mutex
|
||||
SessionManager *SessionManager
|
||||
upgrader websocket.Upgrader
|
||||
}
|
||||
|
||||
func NewServer(msgChanIn chan *Message) *Server {
|
||||
return &Server{
|
||||
clients: make(map[string]*Client),
|
||||
msgChanIn: msgChanIn,
|
||||
SessionManager: NewSessionManager(),
|
||||
upgrader: websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Handler(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := s.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
client := NewClient(s, conn)
|
||||
defer client.Close()
|
||||
client.Listen()
|
||||
}
|
||||
|
||||
func (s *Server) AddClient(c *Client) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
if id := c.GetID(); id != "" {
|
||||
s.clientsMutex.Lock()
|
||||
defer s.clientsMutex.Unlock()
|
||||
s.clients[id] = c
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) DelClient(c *Client) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
if id := c.GetID(); id != "" {
|
||||
s.clientsMutex.Lock()
|
||||
delete(s.clients, id)
|
||||
s.clientsMutex.Unlock()
|
||||
s.SessionManager.Remove(c)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const SessionMessageInit = "session_init"
|
||||
|
||||
type SessionManager struct {
|
||||
sessionToClient map[uuid.UUID]map[string]*Client
|
||||
clientToSession map[string]uuid.UUID
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewSessionManager() *SessionManager {
|
||||
return &SessionManager{
|
||||
sessionToClient: make(map[uuid.UUID]map[string]*Client),
|
||||
clientToSession: make(map[string]uuid.UUID),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SessionManager) Init(c *Client) {
|
||||
c.Write(&Message{Subject: SessionMessageInit})
|
||||
}
|
||||
func (s *SessionManager) HandleMessage(msg *Message) bool {
|
||||
if msg == nil {
|
||||
return false
|
||||
}
|
||||
if msg.ID != uuid.Nil && msg.Subject == SessionMessageInit && msg.From != nil {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
list := s.sessionToClient[msg.ID]
|
||||
if list == nil {
|
||||
list = make(map[string]*Client)
|
||||
}
|
||||
id := msg.From.GetID()
|
||||
list[id] = msg.From
|
||||
s.clientToSession[id] = msg.ID
|
||||
s.sessionToClient[msg.ID] = list
|
||||
return true
|
||||
} else {
|
||||
id := msg.From.GetID()
|
||||
msg.Session = s.clientToSession[id]
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
func (s *SessionManager) Remove(c *Client) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
if id := c.GetID(); id != "" {
|
||||
session := s.clientToSession[id]
|
||||
if session != uuid.Nil {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
list := s.sessionToClient[session]
|
||||
delete(list, id)
|
||||
if len(list) > 0 {
|
||||
s.sessionToClient[session] = list
|
||||
} else {
|
||||
delete(s.sessionToClient, session)
|
||||
}
|
||||
}
|
||||
delete(s.clientToSession, id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *SessionManager) Send(id uuid.UUID, msg *Message) {
|
||||
session := s.sessionToClient[id]
|
||||
for _, c := range session {
|
||||
c.Write(msg)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSessionManager(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
session := NewSessionManager()
|
||||
assert.NotNil(session)
|
||||
|
||||
out := make(chan *Message, channelBufSize)
|
||||
client := &Client{
|
||||
out: out,
|
||||
writeQuit: make(chan bool),
|
||||
readQuit: make(chan bool),
|
||||
ws: &websocket.Conn{},
|
||||
}
|
||||
|
||||
session.Init(client)
|
||||
msg := <-out
|
||||
assert.Equal(SessionMessageInit, msg.Subject)
|
||||
|
||||
result := session.HandleMessage(nil)
|
||||
assert.False(result)
|
||||
|
||||
msgFillSession := &Message{}
|
||||
result = session.HandleMessage(msgFillSession)
|
||||
assert.False(result)
|
||||
|
||||
result = session.HandleMessage(&Message{
|
||||
ID: uuid.New(),
|
||||
From: client,
|
||||
Subject: SessionMessageInit,
|
||||
})
|
||||
assert.True(result)
|
||||
}
|
|
@ -1,13 +1,17 @@
|
|||
// Package with a lib for cronjobs to run in background
|
||||
package worker
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Struct which handles the job
|
||||
type Worker struct {
|
||||
every time.Duration
|
||||
run func()
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Function to create a new Worker with a timestamp, run, every and it's function
|
||||
|
@ -23,6 +27,7 @@ func NewWorker(every time.Duration, f func()) (w *Worker) {
|
|||
// Function to start the Worker
|
||||
// (please us it as a go routine with go w.Start())
|
||||
func (w *Worker) Start() {
|
||||
w.wg.Add(1)
|
||||
ticker := time.NewTicker(w.every)
|
||||
for {
|
||||
select {
|
||||
|
@ -30,6 +35,7 @@ func (w *Worker) Start() {
|
|||
w.run()
|
||||
case <-w.quit:
|
||||
ticker.Stop()
|
||||
w.wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -38,4 +44,5 @@ func (w *Worker) Start() {
|
|||
// Function to stop the Worker
|
||||
func (w *Worker) Close() {
|
||||
close(w.quit)
|
||||
w.wg.Wait()
|
||||
}
|
||||
|
|
|
@ -22,5 +22,4 @@ func TestWorker(t *testing.T) {
|
|||
w.Close()
|
||||
|
||||
assert.Equal(3, runtime)
|
||||
time.Sleep(time.Duration(8) * time.Millisecond)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue