lots of api changes
use jsonapi to format responses add somewhat working websockets
This commit is contained in:
76
api/websockets/client.go
Normal file
76
api/websockets/client.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package websockets
|
||||
|
||||
import "github.com/gorilla/websocket"
|
||||
import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
hub *Hub
|
||||
|
||||
socket *websocket.Conn
|
||||
|
||||
send chan []byte
|
||||
}
|
||||
|
||||
func (c *Client) readPump() {
|
||||
defer func() {
|
||||
c.hub.unregister <- c
|
||||
c.socket.Close()
|
||||
}()
|
||||
c.socket.SetReadLimit(maxMessageSize)
|
||||
c.socket.SetReadDeadline(time.Now().Add(pongWait))
|
||||
c.socket.SetPongHandler(func(string) error {
|
||||
c.socket.SetReadDeadline(time.Now().Add(pongWait))
|
||||
return nil
|
||||
})
|
||||
for {
|
||||
_, _, err := c.socket.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||
log.WithError(err).Debug("Websocket closed unexpectedly.")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) writePump() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.socket.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case m, ok := <-c.send:
|
||||
c.socket.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if !ok {
|
||||
// The hub closed the channel
|
||||
c.socket.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
w, err := c.socket.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.Write([]byte{'['})
|
||||
w.Write(m)
|
||||
for i := 0; i < len(c.send)+1; i++ {
|
||||
w.Write([]byte{','})
|
||||
w.Write(<-c.send)
|
||||
}
|
||||
w.Write([]byte{']'})
|
||||
if err := w.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
c.socket.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := c.socket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
api/websockets/consolewriter.go
Normal file
28
api/websockets/consolewriter.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package websockets
|
||||
|
||||
import "io"
|
||||
|
||||
type ConsoleWriter struct {
|
||||
Hub *Hub
|
||||
HandlerFunc *func(string)
|
||||
}
|
||||
|
||||
var _ io.Writer = ConsoleWriter{}
|
||||
|
||||
func (c ConsoleWriter) Write(b []byte) (n int, e error) {
|
||||
line := make([]byte, len(b))
|
||||
copy(line, b)
|
||||
m := Message{
|
||||
Type: MessageTypeConsole,
|
||||
Payload: ConsolePayload{
|
||||
Line: string(line),
|
||||
Level: ConsoleLevelPlain,
|
||||
Source: ConsoleSourceServer,
|
||||
},
|
||||
}
|
||||
c.Hub.Broadcast <- m
|
||||
if c.HandlerFunc != nil {
|
||||
(*c.HandlerFunc)(string(line))
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
120
api/websockets/hub.go
Normal file
120
api/websockets/hub.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
writeWait = 10 * time.Second
|
||||
pongWait = 60 * time.Second
|
||||
|
||||
pingPeriod = pongWait * 9 / 10
|
||||
|
||||
maxMessageSize = 512
|
||||
)
|
||||
|
||||
var wsupgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type websocketMap map[*Client]bool
|
||||
|
||||
type Hub struct {
|
||||
clients websocketMap
|
||||
|
||||
Broadcast chan Message
|
||||
|
||||
register chan *Client
|
||||
unregister chan *Client
|
||||
close chan bool
|
||||
}
|
||||
|
||||
//var _ io.Writer = &Hub{}
|
||||
|
||||
func NewHub() *Hub {
|
||||
return &Hub{
|
||||
Broadcast: make(chan Message),
|
||||
register: make(chan *Client),
|
||||
unregister: make(chan *Client),
|
||||
close: make(chan bool),
|
||||
clients: make(websocketMap),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hub) Upgrade(w http.ResponseWriter, r *http.Request) {
|
||||
socket, err := wsupgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to upgrade to websocket")
|
||||
return
|
||||
}
|
||||
c := &Client{
|
||||
hub: h,
|
||||
socket: socket,
|
||||
send: make(chan []byte, 256),
|
||||
}
|
||||
h.register <- c
|
||||
|
||||
go c.readPump()
|
||||
go c.writePump()
|
||||
}
|
||||
|
||||
func (h *Hub) Subscribe(c *Client) {
|
||||
h.register <- c
|
||||
}
|
||||
|
||||
func (h *Hub) Unsubscribe(c *Client) {
|
||||
h.unregister <- c
|
||||
}
|
||||
|
||||
func (h *Hub) Run() {
|
||||
defer func() {
|
||||
for s := range h.clients {
|
||||
close(s.send)
|
||||
delete(h.clients, s)
|
||||
}
|
||||
close(h.register)
|
||||
close(h.unregister)
|
||||
close(h.Broadcast)
|
||||
close(h.close)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case s := <-h.register:
|
||||
h.clients[s] = true
|
||||
case s := <-h.unregister:
|
||||
if _, ok := h.clients[s]; ok {
|
||||
delete(h.clients, s)
|
||||
close(s.send)
|
||||
}
|
||||
case m := <-h.Broadcast:
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to encode websocket message.")
|
||||
continue
|
||||
}
|
||||
for s := range h.clients {
|
||||
select {
|
||||
case s.send <- b:
|
||||
default:
|
||||
close(s.send)
|
||||
delete(h.clients, s)
|
||||
}
|
||||
}
|
||||
case <-h.close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hub) Close() {
|
||||
h.close <- true
|
||||
}
|
||||
59
api/websockets/message.go
Normal file
59
api/websockets/message.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package websockets
|
||||
|
||||
type MessageType string
|
||||
|
||||
const (
|
||||
MessageTypeProc MessageType = "proc"
|
||||
MessageTypeConsole MessageType = "console"
|
||||
MessageTypeStatus MessageType = "status"
|
||||
)
|
||||
|
||||
// Message is a message that can be sent using a websocket in JSON format
|
||||
type Message struct {
|
||||
// Type is the type of a websocket message
|
||||
Type MessageType `json:"type"`
|
||||
// Payload is the payload of the message
|
||||
// The payload needs to support encoding in JSON
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
type ProcPayload struct {
|
||||
Memory int `json:"memory"`
|
||||
CPUCores []int `json:"cpu_cores"`
|
||||
CPUTotal int `json:"cpu_total"`
|
||||
Disk int `json:"disk"`
|
||||
}
|
||||
|
||||
type ConsoleSource string
|
||||
type ConsoleLevel string
|
||||
|
||||
const (
|
||||
ConsoleSourceWings ConsoleSource = "wings"
|
||||
ConsoleSourceServer ConsoleSource = "server"
|
||||
|
||||
ConsoleLevelPlain ConsoleLevel = "plain"
|
||||
ConsoleLevelInfo ConsoleLevel = "info"
|
||||
ConsoleLevelWarn ConsoleLevel = "warn"
|
||||
ConsoleLevelError ConsoleLevel = "error"
|
||||
)
|
||||
|
||||
type ConsolePayload struct {
|
||||
// Source is the source of the console line, either ConsoleSourceWings or ConsoleSourceServer
|
||||
Source ConsoleSource `json:"source"`
|
||||
// Level is the level of the message.
|
||||
// Use one of plain, info, warn or error. If omitted the default is plain.
|
||||
Level ConsoleLevel `json:"level,omitempty"`
|
||||
// Line is the actual line to print to the console.
|
||||
Line string `json:"line"`
|
||||
}
|
||||
|
||||
func (h *Hub) Log(l ConsoleLevel, m string) {
|
||||
h.Broadcast <- Message{
|
||||
Type: MessageTypeConsole,
|
||||
Payload: ConsolePayload{
|
||||
Source: ConsoleSourceWings,
|
||||
Level: l,
|
||||
Line: m,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user