// // 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" ) type webSocketRequest struct { Command string `json:"command"` Args []string `json:"arguments"` } type webSocketResponseBase struct { ResponseCode int `json:"response-code"` Type string `json:"type"` } type webSocketResponseError struct { webSocketResponseBase ErrorString string `json:"error"` } type webSocketResponseState struct { webSocketResponseBase webState } type webSocketResponseLanguages struct { webSocketResponseBase Languages []Language `json:"languages"` } 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 := &webSocketResponseError{} rd.ResponseCode = code rd.Type = "error" rd.ErrorString = errStr sendWebSocketResponse(ws, rd) } func webSocketHandleRequest(ws *websocket.Conn, sm *StateMachine, req webSocketRequest, subCh chan<- FullState) { switch req.Command { case "state": if len(req.Args) != 0 { sendWebSocketErrorResponse(ws, http.StatusBadRequest, "command 'state' expects no arguments") return } var result webSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State, result.Ratio, result.Lang = sm.GetState() sendWebSocketResponse(ws, result) case "subscribe": if len(req.Args) != 0 { sendWebSocketErrorResponse(ws, http.StatusBadRequest, "command 'subscribe' expects no arguments") return } if err := sm.SubscribeState(subCh); err != nil { sendWebSocketErrorResponse(ws, http.StatusInternalServerError, err.Error()) return } var result webSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State, result.Ratio, result.Lang = sm.GetState() sendWebSocketResponse(ws, result) case "languages": if len(req.Args) != 0 { sendWebSocketErrorResponse(ws, http.StatusBadRequest, "command 'languages' expects no arguments") return } var result webSocketResponseLanguages result.ResponseCode = http.StatusOK result.Type = "languages" result.Languages = sm.GetLanguages() sendWebSocketResponse(ws, result) case "language": if len(req.Args) != 1 { sendWebSocketErrorResponse(ws, http.StatusBadRequest, "command 'language' expects exatly one argument") return } if err := sm.SetLanguage(Language(req.Args[0])); err != nil { sendWebSocketErrorResponse(ws, http.StatusBadRequest, err.Error()) return } var result webSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State, result.Ratio, result.Lang = sm.GetState() sendWebSocketResponse(ws, result) default: sendWebSocketErrorResponse(ws, http.StatusBadRequest, "unkown command: '"+req.Command+"'") } } func webSocketSessionHandler(ws *websocket.Conn, sm *StateMachine, reqCh <-chan webSocketRequest) { defer ws.Close() subCh := make(chan FullState, 100) for { select { case state, ok := <-subCh: if !ok { return } var result webSocketResponseState result.ResponseCode = http.StatusOK result.Type = "state" result.State = state.state result.Ratio = state.ratio result.Lang = state.language sendWebSocketResponse(ws, result) case req, ok := <-reqCh: if !ok { return } webSocketHandleRequest(ws, sm, req, subCh) } } } 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 webSocketRequest) go webSocketSessionHandler(ws, sm, 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 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 } } }