// // rhctl // // Copyright (C) 2009-2016 Christian Pointner // // This file is part of rhctl. // // rhctl is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // rhctl is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with rhctl. If not, see . // package main import ( "encoding/json" "fmt" "io" "io/ioutil" "net/http" "github.com/gorilla/websocket" ) type webSocketRequestData struct { Command string `json:"COMMAND"` } type webSocketResponseBaseData struct { ResponseCode int `json:"RESPONSE_CODE"` Type string `json:"TYPE"` ErrorString string `json:"ERROR_STRING"` } type webSocketResponseStateData struct { webSocketResponseBaseData State State `json:"STATE"` } func sendWebSocketResponse(ws *websocket.Conn, rd interface{}) { if err := ws.WriteJSON(rd); err != nil { rhdl.Println("Web(socket) client", ws.RemoteAddr(), "write error:", err) } } func sendWebSocketErrorResponse(ws *websocket.Conn, code int, errStr string) { rd := &webSocketResponseBaseData{} rd.ResponseCode = code rd.Type = "error" rd.ErrorString = errStr sendWebSocketResponse(ws, rd) } func sendWebSocketStateResponse(ws *websocket.Conn, state State) { rd := &webSocketResponseStateData{} rd.ResponseCode = http.StatusOK rd.Type = "state" rd.ErrorString = "OK" rd.State = state sendWebSocketResponse(ws, rd) } func webSocketSessionHandler(reqchan <-chan webSocketRequestData, ws *websocket.Conn, ctrl *SwitchControl) { defer ws.Close() for { select { case reqdata, ok := <-reqchan: if !ok { return } switch reqdata.Command { case "state": resp := make(chan interface{}) ctrl.Commands <- &Command{Type: CmdState, Response: resp} result := <-resp switch result.(type) { case State: sendWebSocketStateResponse(ws, result.(State)) case error: sendWebSocketErrorResponse(ws, http.StatusInternalServerError, result.(error).Error()) default: sendWebSocketErrorResponse(ws, http.StatusInternalServerError, fmt.Sprintf("invalid response of type %T: %+v", result, result)) } default: sendWebSocketErrorResponse(ws, http.StatusBadRequest, fmt.Sprintf("unknown command '%s'", reqdata.Command)) } } } } func webSocketHandler(ctrl *SwitchControl, w http.ResponseWriter, r *http.Request) { ws, err := websocket.Upgrade(w, r, nil, 64*1024, 64*1024) if _, ok := err.(websocket.HandshakeError); ok { http.Error(w, "Not a websocket handshake", 400) return } else if err != nil { rhdl.Println("Web(socket) client", ws.RemoteAddr(), "error:", err) return } rhdl.Println("Web(socket) client", ws.RemoteAddr(), "connected") reqchan := make(chan webSocketRequestData) go webSocketSessionHandler(reqchan, ws, ctrl) defer close(reqchan) for { t, r, err := ws.NextReader() if err != nil { rhdl.Println("Web(socket) Client", ws.RemoteAddr(), "disconnected:", err) return } switch t { case websocket.TextMessage: var reqdata webSocketRequestData if err := json.NewDecoder(r).Decode(&reqdata); err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } rhdl.Println("Web(socket) client", ws.RemoteAddr(), "request error:", err) sendWebSocketErrorResponse(ws, http.StatusBadRequest, err.Error()) return } // rhdl.Printf("Web(socket) client %s got: %+v", ws.RemoteAddr(), reqdata) reqchan <- reqdata case websocket.BinaryMessage: sendWebSocketErrorResponse(ws, http.StatusBadRequest, "binary messages are not allowed") io.Copy(ioutil.Discard, r) // consume all the data } } }