-
Notifications
You must be signed in to change notification settings - Fork 1
/
proxy.go
85 lines (71 loc) · 2.04 KB
/
proxy.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
package webserver
import (
"errors"
"net/http"
"github.com/rpc-ag/rpc-aggregator/internal/config"
"github.com/rpc-ag/rpc-aggregator/internal/upstream"
"github.com/valyala/fasthttp"
"go.uber.org/zap"
)
// Proxy process the actual request
func (s *WebServer) Proxy(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK)
next := s.upstream.Balancer.Next("a")
if next == nil {
ctx.SetStatusCode(fasthttp.StatusBadGateway)
s.logger.Error("no healthy node")
return
}
node, ok := next.(*upstream.Node)
if !ok {
ctx.SetStatusCode(fasthttp.StatusBadGateway)
return
}
key, err := s.Auth(ctx)
if err != nil {
ctx.SetStatusCode(http.StatusForbidden)
ctx.Response.Header.Set("X-RCP-Error", err.Error())
return
}
// TODO: if a node is not accepting a new request due to rate limiting
// decide to bypass or just remove from the list?
// can healtchchecker check limiter and add back if allowed?
// for now, just remove the node from balancer list and try the next one
// OH! we do auth again, separate request/auth and do auth only once
if !node.RateLimiter.Allow() {
node.SetHealthy(false)
s.Proxy(ctx)
return
}
if !key.RateLimiter.Allow() {
ctx.SetStatusCode(http.StatusTooManyRequests)
ctx.Response.Header.Set("X-RCP-Error", "rate limit exceeded")
return
}
took, err := node.ServeHTTP(ctx)
if err != nil {
node.SetHealthy(false)
if err == fasthttp.ErrTimeout {
s.Proxy(ctx) // just try again with a new node
return
}
ctx.SetStatusCode(fasthttp.StatusBadGateway)
s.logger.Error("failed serving", zap.Error(err))
node.SetHealthy(false)
return
}
s.metrics.NodeRequests.With(node.ToPromLabels()).Observe(took.Seconds())
}
// Auth check authentication first
// TODO: can that be a lightweight middleware please?
func (s *WebServer) Auth(ctx *fasthttp.RequestCtx) (*config.APIKey, error) {
k := ctx.UserValue("api_key").(string)
if k == "" {
return nil, errors.New("no ap key in request")
}
key, found := s.auth.Auth(k)
if !found {
return nil, errors.New("api key not found")
}
return key, nil
}