// // dolmetschctl // // // Copyright (C) 2019 Christian Pointner // // This file is part of dolmetschctl. // // dolmetschctl 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. // // dolmetschctl 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 dolmetschctl. If not, see . // package main import ( "encoding/json" "io" "io/ioutil" "log" "net/http" "github.com/gorilla/websocket" "spreadspace.org/dolmetschctl/pkg/types" ) func sendWebSocketResponse(ws *websocket.Conn, rd interface{}) { if err := ws.WriteJSON(rd); err != nil { log.Println("Web(socket) client", ws.RemoteAddr(), "write error:", err) } } func sendWebSocketErrorResponse(ws *websocket.Conn, code int, errStr string) { rd := &types.WebSocketResponseError{} rd.ResponseCode = code rd.Type = "error" rd.ErrorString = errStr sendWebSocketResponse(ws, rd) } type webSocketSession struct { ws *websocket.Conn sm *StateMachine stateSubCh chan types.FullState stateUnsubCh chan<- struct{} } func (s *webSocketSession) handleRequest(req types.WebSocketRequest) { switch req.Command { case "state": if len(req.Args) != 0 { sendWebSocketErrorResponse(s.ws, http.StatusBadRequest, "command 'state' expects no arguments") return } var result types.WebSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State, result.Ratio, result.Lang = s.sm.GetState() sendWebSocketResponse(s.ws, result) case "subscribe": if len(req.Args) != 0 { sendWebSocketErrorResponse(s.ws, http.StatusBadRequest, "command 'subscribe' expects no arguments") return } s.stateUnsubCh = s.sm.SubscribeState(s.stateSubCh) var result types.WebSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State, result.Ratio, result.Lang = s.sm.GetState() sendWebSocketResponse(s.ws, result) case "languages": if len(req.Args) != 0 { sendWebSocketErrorResponse(s.ws, http.StatusBadRequest, "command 'languages' expects no arguments") return } var result types.WebSocketResponseLanguages result.ResponseCode = http.StatusOK result.Type = "languages" result.Languages = s.sm.GetLanguages() sendWebSocketResponse(s.ws, result) case "language": if len(req.Args) != 1 { sendWebSocketErrorResponse(s.ws, http.StatusBadRequest, "command 'language' expects exatly one argument") return } if err := s.sm.SetLanguage(types.Language(req.Args[0])); err != nil { sendWebSocketErrorResponse(s.ws, http.StatusBadRequest, err.Error()) return } var result types.WebSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State, result.Ratio, result.Lang = s.sm.GetState() sendWebSocketResponse(s.ws, result) default: sendWebSocketErrorResponse(s.ws, http.StatusBadRequest, "unkown command: '"+req.Command+"'") } } func (s *webSocketSession) Handler(reqCh <-chan types.WebSocketRequest) { defer func() { s.ws.Close() if s.stateUnsubCh != nil { close(s.stateUnsubCh) } }() s.stateSubCh = make(chan types.FullState, 100) for { select { case state, ok := <-s.stateSubCh: if !ok { return } var result types.WebSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State = state.State result.Ratio = state.Ratio result.Lang = state.Language sendWebSocketResponse(s.ws, result) case req, ok := <-reqCh: if !ok { return } s.handleRequest(req) } } } func webSocketHandler(sm *StateMachine, 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 { log.Println("Web(socket) client", ws.RemoteAddr(), "error:", err) return } log.Println("Web(socket) client", ws.RemoteAddr(), "connected") reqCh := make(chan types.WebSocketRequest) session := webSocketSession{ws: ws, sm: sm} go session.Handler(reqCh) defer close(reqCh) for { t, r, err := ws.NextReader() if err != nil { log.Println("Web(socket) client", ws.RemoteAddr(), "disconnected:", err) return } switch t { case websocket.TextMessage: var req types.WebSocketRequest dec := json.NewDecoder(r) dec.DisallowUnknownFields() if err := dec.Decode(&req); err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } log.Println("Web(socket) client", ws.RemoteAddr(), "request error:", err) sendWebSocketErrorResponse(ws, http.StatusBadRequest, err.Error()) return } reqCh <- req case websocket.BinaryMessage: sendWebSocketErrorResponse(ws, http.StatusBadRequest, "binary messages are not allowed") io.Copy(ioutil.Discard, r) // consume all the data } } }