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