Merge branch 'websocket_rewrite'

This commit is contained in:
Jonathan Leibiusky @xetorthio
2017-10-31 16:58:36 -03:00
4 changed files with 204 additions and 44 deletions

View File

@@ -9,7 +9,6 @@ import (
"golang.org/x/crypto/acme/autocert"
"github.com/googollee/go-socket.io"
gh "github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/play-with-docker/play-with-docker/config"
@@ -21,7 +20,6 @@ import (
var core pwd.PWDApi
var e event.EventApi
var ws *socketio.Server
type HandlerExtender func(h *mux.Router)
@@ -31,15 +29,6 @@ func Bootstrap(c pwd.PWDApi, ev event.EventApi) {
}
func Register(extend HandlerExtender) {
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", WS)
server.On("error", WSError)
RegisterEvents(server)
r := mux.NewRouter()
corsRouter := mux.NewRouter()
@@ -71,7 +60,7 @@ func Register(extend HandlerExtender) {
http.ServeFile(rw, r, "www/sdk.js")
})
corsRouter.Handle("/sessions/{sessionId}/ws/", server)
corsRouter.HandleFunc("/sessions/{sessionId}/ws/", WSH)
r.Handle("/metrics", promhttp.Handler())
// Generic routes
@@ -129,12 +118,3 @@ func Register(extend HandlerExtender) {
log.Fatal(httpServer.ListenAndServe())
}
}
func RegisterEvents(s *socketio.Server) {
ws = s
e.OnAny(broadcastEvent)
}
func broadcastEvent(eventType event.EventType, sessionId string, args ...interface{}) {
ws.BroadcastTo(sessionId, eventType.String(), args...)
}

View File

@@ -1,14 +1,140 @@
package handlers
import (
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"github.com/googollee/go-socket.io"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/play-with-docker/play-with-docker/event"
"github.com/twinj/uuid"
)
func WS(so socketio.Socket) {
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
type message struct {
Name string `json:"name"`
Args []interface{} `json:"args"`
}
type socket struct {
c *websocket.Conn
mx sync.Mutex
listeners map[string][]func(args ...interface{})
r *http.Request
id string
closed bool
}
func newSocket(r *http.Request, c *websocket.Conn) *socket {
return &socket{
c: c,
listeners: map[string][]func(args ...interface{}){},
r: r,
id: uuid.NewV4().String(),
}
}
func (s *socket) Id() string {
return s.id
}
func (s *socket) Request() *http.Request {
return s.r
}
func (s *socket) Close() {
s.closed = true
s.onMessage(message{Name: "close"})
}
func (s *socket) process() {
defer s.Close()
for {
mt, m, err := s.c.ReadMessage()
if err != nil {
log.Printf("Error reading message from websocket. Got: %v\n", err)
break
}
if mt != websocket.TextMessage {
log.Printf("Received websocket message, but it is not a text message.\n")
continue
}
go func() {
var msg message
if err := json.Unmarshal(m, &msg); err != nil {
log.Printf("Cannot unmarshal message received from websocket. Got: %v\n", err)
return
}
s.onMessage(msg)
}()
}
}
func (s *socket) onMessage(msg message) {
s.mx.Lock()
defer s.mx.Unlock()
cbs, found := s.listeners[msg.Name]
if !found {
return
}
for _, cb := range cbs {
go cb(msg.Args...)
}
}
func (s *socket) Emit(ev string, args ...interface{}) {
s.mx.Lock()
defer s.mx.Unlock()
if s.closed {
return
}
m := message{Name: ev, Args: args}
b, err := json.Marshal(m)
if err != nil {
log.Printf("Cannot marshal event to json. Got: %v\n", err)
return
}
if err := s.c.WriteMessage(websocket.TextMessage, b); err != nil {
log.Printf("Cannot write event to websocket connection. Got: %v\n", err)
s.Close()
return
}
}
func (s *socket) On(ev string, cb func(args ...interface{})) {
s.mx.Lock()
defer s.mx.Unlock()
listeners, found := s.listeners[ev]
if !found {
listeners = []func(args ...interface{}){}
}
listeners = append(listeners, cb)
s.listeners[ev] = listeners
}
func WSH(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
s := newSocket(r, c)
ws(s)
s.process()
}
func ws(so *socket) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from ", r)
@@ -27,8 +153,6 @@ func WS(so socketio.Socket) {
client := core.ClientNew(so.Id(), session)
so.Join(session.Id)
m, err := NewManager(session)
if err != nil {
log.Printf("Error creating terminal manager. Got: %v", err)
@@ -48,26 +172,32 @@ func WS(so socketio.Socket) {
return
}
so.On("session close", func() {
so.On("session close", func(args ...interface{}) {
m.Close()
core.SessionClose(session)
})
so.On("instance terminal in", func(name, data string) {
so.On("instance terminal in", func(args ...interface{}) {
name := args[0].(string)
data := args[1].(string)
m.Send(name, []byte(data))
})
so.On("instance viewport resize", func(cols, rows uint) {
so.On("instance viewport resize", func(args ...interface{}) {
// User resized his viewport
core.ClientResizeViewPort(client, cols, rows)
cols := args[0].(float64)
rows := args[1].(float64)
core.ClientResizeViewPort(client, uint(cols), uint(rows))
})
so.On("disconnection", func() {
so.On("close", func(args ...interface{}) {
m.Close()
core.ClientClose(client)
})
}
func WSError(so socketio.Socket) {
log.Println("error ws")
e.OnAny(func(eventType event.EventType, sessionId string, args ...interface{}) {
if session.Id == sessionId {
so.Emit(eventType.String(), args...)
}
})
}

View File

@@ -27,7 +27,7 @@
$scope.selectedInstance = null;
$scope.isAlive = true;
$scope.ttl = '--:--:--';
$scope.connected = true;
$scope.connected = false;
$scope.type = {windows: false};
$scope.isInstanceBeingCreated = false;
$scope.newInstanceBtnText = '+ Add new instance';
@@ -35,7 +35,6 @@
$scope.isInstanceBeingDeleted = false;
$scope.uploadProgress = 0;
$scope.uploadFiles = function (files, invalidFiles) {
let total = files.length;
let uploadFile = function() {
@@ -179,7 +178,65 @@
$scope.idxByHostname[instance.hostname] = instance;
}
var socket = io({ path: '/sessions/' + sessionId + '/ws' });
var base = '';
if (window.location.protocol == 'http:') {
base = 'ws://';
} else {
base = 'wss://';
}
base += window.location.host;
if (window.location.port) {
base += ':' + window.location.port;
}
var socket = new ReconnectingWebSocket(base + '/sessions/' + sessionId + '/ws/', null, {reconnectInterval: 1000});
socket.listeners = {};
socket.on = function(name, cb) {
if (!socket.listeners[name]) {
socket.listeners[name] = [];
}
socket.listeners[name].push(cb);
}
socket.emit = function() {
var name = arguments[0]
var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
socket.send(JSON.stringify({name: name, args: args}));
}
socket.addEventListener('open', function (event) {
$scope.connected = true;
for (var i in $scope.instances) {
var instance = $scope.instances[i];
if (instance.term) {
instance.term.setOption('disableStdin', false);
}
}
});
socket.addEventListener('close', function (event) {
$scope.connected = false;
for (var i in $scope.instances) {
var instance = $scope.instances[i];
if (instance.term) {
instance.term.setOption('disableStdin', true);
}
}
});
socket.addEventListener('message', function (event) {
var m = JSON.parse(event.data);
var ls = socket.listeners[m.name];
if (ls) {
for (var i=0; i<ls.length; i++) {
var l = ls[i];
l.apply(l, m.args);
}
}
});
socket.on('instance terminal status', function(name, status) {
var instance = $scope.idx[name];
@@ -245,13 +302,6 @@
});
});
socket.on('connect_error', function() {
$scope.connected = false;
});
socket.on('connect', function() {
$scope.connected = true;
});
socket.on('instance stats', function(stats) {
$scope.idx[stats.instance].mem = stats.mem;
$scope.idx[stats.instance].cpu = stats.cpu;

View File

@@ -280,6 +280,7 @@
</md-dialog-actions>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.min.js" integrity="sha256-A4JwlcDvqO4JXpvEtvWY1RH8JAEMu5W21wP8GUXLUNs=" crossorigin="anonymous"></script>
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
@@ -294,7 +295,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/12.2.13/ng-file-upload-all.min.js" integrity="sha256-LrZq3efIkFX0BooX7x/rjWyYDvMKfFV2HJpy6HBw7cE=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.js"></script>
<script src="/assets/app.js"></script>
<script src="/assets/xterm.js"></script>
<script src="/assets/xterm-addons/fit.js"></script>