/
method_data.go
149 lines (129 loc) · 4.08 KB
/
method_data.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
package common
import (
"encoding/json"
"runtime"
"strconv"
"strings"
"github.com/getsentry/raven-go"
"github.com/jmoiron/sqlx"
"github.com/valyala/fasthttp"
"golang.org/x/exp/slog"
"gopkg.in/redis.v5"
)
// MethodData is a struct containing the data passed over to an API method.
type MethodData struct {
User Token
DB *sqlx.DB
R *redis.Client
Ctx *fasthttp.RequestCtx
}
// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
func (md MethodData) ClientIP() string {
clientIP := strings.TrimSpace(string(md.Ctx.Request.Header.Peek("X-Real-Ip")))
if len(clientIP) > 0 {
return clientIP
}
clientIP = string(md.Ctx.Request.Header.Peek("X-Forwarded-For"))
if index := strings.IndexByte(clientIP, ','); index >= 0 {
clientIP = clientIP[0:index]
}
clientIP = strings.TrimSpace(clientIP)
if len(clientIP) > 0 {
return clientIP
}
return md.Ctx.RemoteIP().String()
}
// Err logs an error. If RavenClient is set, it will use the client to report
// the error to sentry, otherwise it will just write the error to stdout.
func (md MethodData) Err(err error) {
user := &raven.User{
ID: strconv.Itoa(md.User.UserID),
Username: md.User.Value,
IP: md.Ctx.RemoteAddr().String(),
}
// Generate tags for error
tags := map[string]string{
"endpoint": string(md.Ctx.RequestURI()),
"token": md.User.Value,
}
_err(err, tags, user, md.Ctx)
}
// Err for peppy API calls
func Err(c *fasthttp.RequestCtx, err error) {
// Generate tags for error
tags := map[string]string{
"endpoint": string(c.RequestURI()),
}
_err(err, tags, nil, c)
}
// WSErr is the error function for errors happening in the websockets.
func WSErr(err error) {
_err(err, map[string]string{
"endpoint": "/api/v1/ws",
}, nil, nil)
}
// GenericError is just an error. Can't make a good description.
func GenericError(err error) {
_err(err, nil, nil, nil)
}
func _err(err error, tags map[string]string, user *raven.User, c *fasthttp.RequestCtx) {
_, file, no, ok := runtime.Caller(2)
if ok {
slog.Error("An error occurred", "filename", file, "line", no, "error", err.Error())
} else {
slog.Error("An error occurred", "error", err.Error())
}
}
func generateRavenHTTP(ctx *fasthttp.RequestCtx) *raven.Http {
if ctx == nil {
return nil
}
// build uri
uri := ctx.URI()
// safe to use b2s because a new string gets allocated eventually for
// concatenation
sURI := b2s(uri.Scheme()) + "://" + b2s(uri.Host()) + b2s(uri.Path())
// build header map
// using ctx.Request.Header.Len would mean calling .VisitAll two times
// which can be quite expensive since it means iterating over all the
// headers, so we give a rough estimate of the number of headers we expect
// to have
m := make(map[string]string, 16)
ctx.Request.Header.VisitAll(func(k, v []byte) {
// not using b2s because we mustn't keep references to the underlying
// k and v
m[string(k)] = string(v)
})
return &raven.Http{
URL: sURI,
// Not using b2s because raven sending is concurrent and may happen
// AFTER the request, meaning that values could potentially be replaced
// by new ones.
Method: string(ctx.Method()),
Query: string(uri.QueryString()),
Cookies: string(ctx.Request.Header.Peek("Cookie")),
Headers: m,
}
}
// ID retrieves the Token's owner user ID.
func (md MethodData) ID() int {
return md.User.UserID
}
// Query is shorthand for md.C.Query.
func (md MethodData) Query(q string) string {
return b2s(md.Ctx.QueryArgs().Peek(q))
}
// HasQuery returns true if the parameter is encountered in the querystring.
// It returns true even if the parameter is "" (the case of ?param&etc=etc)
func (md MethodData) HasQuery(q string) bool {
return md.Ctx.QueryArgs().Has(q)
}
// Unmarshal unmarshals a request's JSON body into an interface.
func (md MethodData) Unmarshal(into interface{}) error {
return json.Unmarshal(md.Ctx.PostBody(), into)
}
// IsBearer tells whether the current token is a Bearer (oauth) token.
func (md MethodData) IsBearer() bool {
return md.User.ID == -1
}