Skip to content

Commit 011eed2

Browse files
r-gochainjmank88
andauthored
WebSockets #37 (#64)
* WebSockets #37 * copy context to a goroutine * circleci: newer google-cloud-sdk Co-authored-by: jmank88 <jmank88@gmail.com>
1 parent 604731d commit 011eed2

6 files changed

Lines changed: 355 additions & 80 deletions

File tree

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ jobs:
1414
- run:
1515
name: install gcloud
1616
command: |
17-
wget https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-204.0.0-linux-x86_64.tar.gz --directory-prefix=tmp
18-
tar -xvzf tmp/google-cloud-sdk-204.0.0-linux-x86_64.tar.gz -C tmp
17+
wget https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-353.0.0-linux-x86_64.tar.gz --directory-prefix=tmp
18+
tar -xvzf tmp/google-cloud-sdk-353.0.0-linux-x86_64.tar.gz -C tmp
1919
./tmp/google-cloud-sdk/install.sh -q
2020
- setup_remote_docker
2121
- deploy:

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/go-chi/chi/v5 v5.0.3
1010
github.com/gochain/gochain/v3 v3.4.7
1111
github.com/golang/snappy v0.0.4 // indirect
12+
github.com/gorilla/websocket v1.4.2 // indirect
1213
github.com/pelletier/go-toml v1.9.3
1314
github.com/rs/cors v1.8.0
1415
github.com/treeder/gcputils v0.1.1

handler.go

Lines changed: 64 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -71,32 +71,34 @@ func parseRequests(r *http.Request) (string, []string, []ModifiedRequest, error)
7171
if err != nil {
7272
return "", nil, nil, fmt.Errorf("failed to read body: %v", err)
7373
}
74-
type rpcRequest struct {
75-
ID json.RawMessage `json:"id"`
76-
Method string `json:"method"`
77-
Params []json.RawMessage `json:"params"`
74+
methods, res, err = parseMessage(body, ip)
75+
if err != nil {
76+
return "", nil, nil, err
7877
}
79-
if isBatch(body) {
80-
var arr []rpcRequest
81-
err = json.Unmarshal(body, &arr)
82-
if err != nil {
83-
return "", nil, nil, fmt.Errorf("failed to parse JSON batch request: %v", err)
84-
}
85-
for _, t := range arr {
86-
methods = append(methods, t.Method)
87-
res = append(res, ModifiedRequest{
88-
ID: t.ID,
89-
Path: t.Method,
90-
RemoteAddr: ip,
91-
Params: t.Params,
92-
})
93-
}
94-
} else {
95-
var t rpcRequest
96-
err = json.Unmarshal(body, &t)
97-
if err != nil {
98-
return "", nil, nil, fmt.Errorf("failed to parse JSON request: %v", err)
99-
}
78+
}
79+
if len(res) == 0 {
80+
methods = append(methods, r.URL.Path)
81+
res = append(res, ModifiedRequest{
82+
Path: r.URL.Path,
83+
RemoteAddr: ip,
84+
})
85+
}
86+
return ip, methods, res, nil
87+
}
88+
89+
func parseMessage(body []byte, ip string) (methods []string, res []ModifiedRequest, err error) {
90+
type rpcRequest struct {
91+
ID json.RawMessage `json:"id"`
92+
Method string `json:"method"`
93+
Params []json.RawMessage `json:"params"`
94+
}
95+
if isBatch(body) {
96+
var arr []rpcRequest
97+
err := json.Unmarshal(body, &arr)
98+
if err != nil {
99+
return nil, nil, fmt.Errorf("failed to parse JSON batch request: %v", err)
100+
}
101+
for _, t := range arr {
100102
methods = append(methods, t.Method)
101103
res = append(res, ModifiedRequest{
102104
ID: t.ID,
@@ -105,15 +107,21 @@ func parseRequests(r *http.Request) (string, []string, []ModifiedRequest, error)
105107
Params: t.Params,
106108
})
107109
}
108-
}
109-
if len(res) == 0 {
110-
methods = append(methods, r.URL.Path)
110+
} else {
111+
var t rpcRequest
112+
err := json.Unmarshal(body, &t)
113+
if err != nil {
114+
return nil, nil, fmt.Errorf("failed to parse JSON request: %v", err)
115+
}
116+
methods = append(methods, t.Method)
111117
res = append(res, ModifiedRequest{
112-
Path: r.URL.Path,
118+
ID: t.ID,
119+
Path: t.Method,
113120
RemoteAddr: ip,
121+
Params: t.Params,
114122
})
115123
}
116-
return ip, methods, res, nil
124+
return methods, res, nil
117125
}
118126

119127
const (
@@ -123,16 +131,18 @@ const (
123131
jsonRPCInternal = -32603
124132
)
125133

134+
type ErrResponse struct {
135+
Version string `json:"jsonrpc"`
136+
ID json.RawMessage `json:"id"`
137+
Error struct {
138+
Code int `json:"code"`
139+
Message string `json:"message"`
140+
} `json:"error"`
141+
}
142+
126143
func jsonRPCError(id json.RawMessage, jsonCode int, msg string) interface{} {
127-
type errResponse struct {
128-
Version string `json:"jsonrpc"`
129-
ID json.RawMessage `json:"id"`
130-
Error struct {
131-
Code int `json:"code"`
132-
Message string `json:"message"`
133-
} `json:"error"`
134-
}
135-
resp := errResponse{
144+
145+
resp := ErrResponse{
136146
Version: "2.0",
137147
ID: id,
138148
}
@@ -187,8 +197,13 @@ func (t *myTransport) RoundTrip(req *http.Request) (*http.Response, error) {
187197

188198
ctx = gotils.With(ctx, "remoteIp", ip)
189199
ctx = gotils.With(ctx, "methods", methods)
190-
if blockResponse := t.block(ctx, parsedRequests); blockResponse != nil {
191-
return blockResponse, nil
200+
errorCode, resp := t.block(ctx, parsedRequests)
201+
if resp != nil {
202+
resp, err := jsonRPCResponse(errorCode, resp)
203+
if err != nil {
204+
gotils.L(ctx).Error().Printf("Failed to construct a response: %v", err)
205+
}
206+
return resp, nil
192207
}
193208

194209
gotils.L(ctx).Info().Print("Forwarding request")
@@ -197,71 +212,47 @@ func (t *myTransport) RoundTrip(req *http.Request) (*http.Response, error) {
197212
}
198213

199214
// block returns a response only if the request should be blocked, otherwise it returns nil if allowed.
200-
func (t *myTransport) block(ctx context.Context, parsedRequests []ModifiedRequest) *http.Response {
215+
func (t *myTransport) block(ctx context.Context, parsedRequests []ModifiedRequest) (int, interface{}) {
201216
var union *blockRange
202217
for _, parsedRequest := range parsedRequests {
203218
ctx = gotils.With(ctx, "ip", parsedRequest.RemoteAddr)
204219
if allowed, added := t.AllowVisitor(parsedRequest); !allowed {
205220
gotils.L(ctx).Info().Print("Request blocked: Rate limited")
206-
resp, err := jsonRPCResponse(http.StatusTooManyRequests, jsonRPCLimit(parsedRequest.ID))
207-
if err != nil {
208-
gotils.L(ctx).Error().Printf("Failed to construct rate-limit response: %v", err)
209-
}
210-
return resp
221+
return http.StatusTooManyRequests, jsonRPCLimit(parsedRequest.ID)
211222
} else if added {
212223
gotils.L(ctx).Info().Printf("Added new visitor, ip: %v", parsedRequest.RemoteAddr)
213224
}
214225

215226
if !t.MatchAnyRule(parsedRequest.Path) {
216227
gotils.L(ctx).Info().Print("Request blocked: Method not allowed")
217-
resp, err := jsonRPCResponse(http.StatusMethodNotAllowed, jsonRPCUnauthorized(parsedRequest.ID, parsedRequest.Path))
218-
if err != nil {
219-
gotils.L(ctx).Error().Printf("Failed to construct not-allowed response: %v", err)
220-
}
221-
return resp
228+
return http.StatusMethodNotAllowed, jsonRPCUnauthorized(parsedRequest.ID, parsedRequest.Path)
222229
}
223230
if t.blockRangeLimit > 0 && parsedRequest.Path == "eth_getLogs" {
224231
r, invalid, err := t.parseRange(ctx, parsedRequest)
225232
if err != nil {
226-
resp, err := jsonRPCResponse(http.StatusInternalServerError, jsonRPCError(parsedRequest.ID, jsonRPCInternal, err.Error()))
227-
if err != nil {
228-
gotils.L(ctx).Error().Printf("Failed to construct internal error response: %v", err)
229-
}
230-
return resp
233+
return http.StatusInternalServerError, jsonRPCError(parsedRequest.ID, jsonRPCInternal, err.Error())
231234
} else if invalid != nil {
232235
gotils.L(ctx).Info().Printf("Request blocked: Invalid params: %v", invalid)
233-
resp, err := jsonRPCResponse(http.StatusBadRequest, jsonRPCError(parsedRequest.ID, jsonRPCInvalidParams, invalid.Error()))
234-
if err != nil {
235-
gotils.L(ctx).Error().Printf("Failed to construct invalid params response: %v", err)
236-
}
237-
return resp
236+
return http.StatusBadRequest, jsonRPCError(parsedRequest.ID, jsonRPCInvalidParams, invalid.Error())
238237
}
239238
if r != nil {
240239
if l := r.len(); l > t.blockRangeLimit {
241240
gotils.L(ctx).Info().Println("Request blocked: Exceeds block range limit, range:", l, "limit:", t.blockRangeLimit)
242-
resp, err := jsonRPCResponse(http.StatusBadRequest, jsonRPCBlockRangeLimit(parsedRequest.ID, l, t.blockRangeLimit))
243-
if err != nil {
244-
gotils.L(ctx).Error().Printf("Failed to construct block range limit response: %v", err)
245-
}
246-
return resp
241+
return http.StatusBadRequest, jsonRPCBlockRangeLimit(parsedRequest.ID, l, t.blockRangeLimit)
247242
}
248243
if union == nil {
249244
union = r
250245
} else {
251246
union.extend(r)
252247
if l := union.len(); l > t.blockRangeLimit {
253248
gotils.L(ctx).Info().Println("Request blocked: Exceeds block range limit, range:", l, "limit:", t.blockRangeLimit)
254-
resp, err := jsonRPCResponse(http.StatusBadRequest, jsonRPCBlockRangeLimit(parsedRequest.ID, l, t.blockRangeLimit))
255-
if err != nil {
256-
gotils.L(ctx).Error().Printf("Failed to construct block range limit response: %v", err)
257-
}
258-
return resp
249+
return http.StatusBadRequest, jsonRPCBlockRangeLimit(parsedRequest.ID, l, t.blockRangeLimit)
259250
}
260251
}
261252
}
262253
}
263254
}
264-
return nil
255+
return 0, nil
265256
}
266257

267258
type blockRange struct{ start, end uint64 }

main.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var requestsPerMinuteLimit int
2323
type ConfigData struct {
2424
Port string `toml:",omitempty"`
2525
URL string `toml:",omitempty"`
26+
WSURL string `toml:",omitempty"`
2627
Allow []string `toml:",omitempty"`
2728
RPM int `toml:",omitempty"`
2829
NoLimit []string `toml:",omitempty"`
@@ -37,6 +38,7 @@ func main() {
3738
var configPath string
3839
var port string
3940
var redirecturl string
41+
var redirectWSUrl string
4042
var allowedPaths string
4143
var noLimitIPs string
4244
var blockRangeLimit uint64
@@ -64,6 +66,12 @@ func main() {
6466
Usage: "redirect url",
6567
Destination: &redirecturl,
6668
},
69+
&cli.StringFlag{
70+
Name: "wsurl, w",
71+
Value: "ws://127.0.0.1:8041",
72+
Usage: "redirect websocket url",
73+
Destination: &redirectWSUrl,
74+
},
6775
&cli.StringFlag{
6876
Name: "allow, a",
6977
Usage: "comma separated list of allowed paths",
@@ -111,6 +119,12 @@ func main() {
111119
}
112120
cfg.URL = redirecturl
113121
}
122+
if redirectWSUrl != "" {
123+
if cfg.WSURL != "" {
124+
return errors.New("ws url set in two places")
125+
}
126+
cfg.WSURL = redirectWSUrl
127+
}
114128
if requestsPerMinuteLimit != 0 {
115129
if cfg.RPM != 0 {
116130
return errors.New("rpm set in two places")
@@ -150,7 +164,7 @@ func (cfg *ConfigData) run(ctx context.Context) error {
150164
sort.Strings(cfg.Allow)
151165
sort.Strings(cfg.NoLimit)
152166

153-
gotils.L(ctx).Info().Println("Server starting, port:", cfg.Port, "redirectURL:", cfg.URL,
167+
gotils.L(ctx).Info().Println("Server starting, port:", cfg.Port, "redirectURL:", cfg.URL, "redirectWSURL:", cfg.WSURL,
154168
"rpmLimit:", cfg.RPM, "exempt:", cfg.NoLimit, "allowed:", cfg.Allow)
155169

156170
// Create proxy server.
@@ -189,5 +203,6 @@ func (cfg *ConfigData) run(ctx context.Context) error {
189203
w.WriteHeader(http.StatusOK)
190204
})
191205
r.HandleFunc("/*", server.RPCProxy)
206+
r.HandleFunc("/ws", server.WSProxy)
192207
return http.ListenAndServe(":"+cfg.Port, r)
193208
}

proxy.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import (
2121
)
2222

2323
type Server struct {
24-
target *url.URL
25-
proxy *httputil.ReverseProxy
24+
target *url.URL
25+
proxy *httputil.ReverseProxy
26+
wsProxy *WebsocketProxy
2627
myTransport
2728
homepage []byte
2829
}
@@ -32,8 +33,11 @@ func (cfg *ConfigData) NewServer() (*Server, error) {
3233
if err != nil {
3334
return nil, err
3435
}
35-
36-
s := &Server{target: url, proxy: httputil.NewSingleHostReverseProxy(url)}
36+
wsurl, err := url.Parse(cfg.WSURL)
37+
if err != nil {
38+
return nil, err
39+
}
40+
s := &Server{target: url, proxy: httputil.NewSingleHostReverseProxy(url), wsProxy: NewProxy(wsurl)}
3741
s.myTransport.blockRangeLimit = cfg.BlockRangeLimit
3842
s.myTransport.url = cfg.URL
3943
s.matcher, err = newMatcher(cfg.Allow)
@@ -46,6 +50,7 @@ func (cfg *ConfigData) NewServer() (*Server, error) {
4650
s.noLimitIPs[ip] = struct{}{}
4751
}
4852
s.proxy.Transport = &s.myTransport
53+
s.wsProxy.Transport = &s.myTransport
4954

5055
// Generate static home page.
5156
id := json.RawMessage([]byte(`"ID"`))
@@ -88,6 +93,11 @@ func (p *Server) RPCProxy(w http.ResponseWriter, r *http.Request) {
8893
p.proxy.ServeHTTP(w, r)
8994
}
9095

96+
func (p *Server) WSProxy(w http.ResponseWriter, r *http.Request) {
97+
w.Header().Set("X-rpc-proxy", "rpc-proxy")
98+
p.wsProxy.ServeHTTP(w, r)
99+
}
100+
91101
func (p *Server) Example(w http.ResponseWriter, r *http.Request) {
92102
method := chi.URLParam(r, "method")
93103
args := []string{

0 commit comments

Comments
 (0)