github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/litrpc/lndcproxy.go (about) 1 package litrpc 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "github.com/mit-dci/lit/crypto/koblitz" 9 "github.com/mit-dci/lit/logging" 10 "golang.org/x/net/websocket" 11 ) 12 13 // LndcRpcWebsocketProxy is a regular websocket server that translates the 14 // received requests into a call on the new LNDC based remote control transport 15 type LndcRpcWebsocketProxy struct { 16 lndcRpcClient *LndcRpcClient 17 } 18 19 // NewLocalLndcRpcWebsocketProxy is an overload to NewLndcRpcWebsocketProxyWithLndc 20 // connecting to the local lit using a derived key from the locally stored 21 // privkey.hex using the default home dir and port 22 func NewLocalLndcRpcWebsocketProxy() (*LndcRpcWebsocketProxy, error) { 23 client, err := NewLocalLndcRpcClient() 24 if err != nil { 25 return nil, err 26 } 27 return NewLndcRpcWebsocketProxyWithLndc(client), nil 28 } 29 30 // NewLocalLndcRpcWebsocketProxy is an overload to NewLndcRpcWebsocketProxyWithLndc 31 // connecting to the local lit using a derived key from the locally stored 32 // privkey.hex using the default home dir and the given port 33 func NewLocalLndcRpcWebsocketProxyWithPort(port uint32) (*LndcRpcWebsocketProxy, error) { 34 client, err := NewLocalLndcRpcClientWithPort(port) 35 if err != nil { 36 return nil, err 37 } 38 return NewLndcRpcWebsocketProxyWithLndc(client), nil 39 } 40 41 // NewLocalLndcRpcWebsocketProxy is an overload to NewLndcRpcWebsocketProxyWithLndc 42 // connecting to the local lit using a derived key from the locally stored 43 // privkey.hex using the given homedir and the given port 44 func NewLocalLndcRpcWebsocketProxyWithHomeDirAndPort(litHomeDir string, port uint32) (*LndcRpcWebsocketProxy, error) { 45 client, err := NewLocalLndcRpcClientWithHomeDirAndPort(litHomeDir, port) 46 if err != nil { 47 return nil, err 48 } 49 return NewLndcRpcWebsocketProxyWithLndc(client), nil 50 } 51 52 // NewLndcRpcWebsocketProxy is an overload to NewLndcRpcWebsocketProxyWithLndc 53 // connecting to the given lit node specified by litAdr, identifying with the 54 // given key. 55 func NewLndcRpcWebsocketProxy(litAdr string, key *koblitz.PrivateKey) (*LndcRpcWebsocketProxy, error) { 56 client, err := NewLndcRpcClient(litAdr, key) 57 if err != nil { 58 return nil, err 59 } 60 return NewLndcRpcWebsocketProxyWithLndc(client), nil 61 } 62 63 // NewLndcRpcWebsocketProxyWithLndc creates a new proxy that listens on the 64 // websocket port and translates requests into remote control messages over 65 // lndc transport. 66 func NewLndcRpcWebsocketProxyWithLndc(lndcRpcClient *LndcRpcClient) *LndcRpcWebsocketProxy { 67 proxy := new(LndcRpcWebsocketProxy) 68 proxy.lndcRpcClient = lndcRpcClient 69 return proxy 70 } 71 72 // Listen starts listening on the given host and port for websocket requests 73 // This function blocks unless an error occurs, so you should call it as a 74 // goroutine 75 func (p *LndcRpcWebsocketProxy) Listen(host string, port uint16) { 76 77 listenString := fmt.Sprintf("%s:%d", host, port) 78 http.HandleFunc("/ws", 79 func(w http.ResponseWriter, req *http.Request) { 80 s := websocket.Server{Handler: websocket.Handler(p.proxyServeWS)} 81 s.ServeHTTP(w, req) 82 }) 83 /*http.HandleFunc("/static/", WebUIHandler) 84 http.HandleFunc("/", WebUIHandler) 85 http.HandleFunc("/oneoff", serveOneoffs)*/ 86 87 logging.Infof("Listening regular Websocket RPC on %s...", listenString) 88 err := http.ListenAndServe(listenString, nil) 89 logging.Errorf("Error on websocket server: %s", err.Error()) 90 } 91 92 // proxyServeWS handles incoming websocket requests 93 func (p *LndcRpcWebsocketProxy) proxyServeWS(ws *websocket.Conn) { 94 95 // Close the connection when we're done 96 defer ws.Close() 97 for { 98 99 // Receive a websocket frame 100 var data interface{} 101 err := websocket.JSON.Receive(ws, &data) 102 if err != nil { 103 logging.Warnf("Error receiving websocket frame: %s\n", err.Error()) 104 break 105 } 106 if data == nil { 107 logging.Warnf("Received nil websocket frame") 108 break 109 } 110 var reply interface{} 111 // Parse the incoming request as generic JSON 112 msg := data.(map[string]interface{}) 113 method := msg["method"] 114 var args interface{} 115 116 // Default to a NoArgs in case the params array is nil 117 args = new(NoArgs) 118 119 if msg["params"] != nil { 120 121 // Parse the params into the args object 122 argsArray, ok := msg["params"].([]interface{}) 123 if ok && len(argsArray) > 0 { 124 // In JsonRpc 2.0, the params are always an array 125 // but lit always only expects a single Args object 126 // so we take index 0 from the array. 127 args = msg["params"].([]interface{})[0] 128 } 129 } 130 131 // store the id from the jsonrpc request 132 id := msg["id"] 133 134 // Use the LNDC RPC client to execute the call 135 err = p.lndcRpcClient.Call(method.(string), args, &reply) 136 var jsonResponse []byte 137 if err != nil { 138 // In case of an error send back a JSON RPC formatted error 139 // response 140 jsonResponse, _ = json.Marshal(errorResponse(id, err)) 141 } else { 142 // In case of success, send back a JSON RPC formatted 143 // response 144 jsonResponse, _ = json.Marshal(successResponse(id, reply)) 145 } 146 147 // Write the response to the stream, and continue with the next websocket 148 // frame 149 ws.Write(jsonResponse) 150 } 151 152 } 153 154 // baseResponse adds the basic elements for each response type to the JSON Response 155 func baseResponse(requestId interface{}) map[string]interface{} { 156 response := make(map[string]interface{}) 157 response["jsonrpc"] = "2.0" 158 response["id"] = requestId 159 return response 160 } 161 162 // errorResponse translates an error into a properly formatted JSON RPC response 163 func errorResponse(requestId interface{}, err error) map[string]interface{} { 164 errorObj := make(map[string]interface{}) 165 errorObj["code"] = -32000 166 errorObj["message"] = "Internal Server Error" 167 errorObj["data"] = err.Error() 168 169 response := baseResponse(requestId) 170 response["error"] = errorObj 171 172 return response 173 } 174 175 // successResponse formats a reply object into a properly formatted JSON RPC response 176 func successResponse(requestId interface{}, data interface{}) map[string]interface{} { 177 response := baseResponse(requestId) 178 response["result"] = data 179 return response 180 }