Skip to content

Commit 86ff7bf

Browse files
committed
Support querying about counters. Add user agent filter.
1 parent 1ed2e1d commit 86ff7bf

File tree

5 files changed

+172
-6
lines changed

5 files changed

+172
-6
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ Example: `analytics-server.yaml`
3535

3636
```yaml
3737
listen: ":443"
38-
db: "redis://default:@localhost/0"
38+
db: "redis://default:@::1/0"
3939

4040
key: "/etc/letsencrypt/live/symboltics.com/privkey.pem"
4141
cert: "/etc/letsencrypt/live/symboltics.com/fullchain.pem"
4242

4343
access_token: ""
4444

45+
user_agent_filter:
46+
- bot
47+
- crawler
48+
4549
header:
4650
key:
4751
connecting_ip: "CF-Connecting-IP"

cmd/analytics-server/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type Config struct {
1313

1414
AccessToken string `yaml:"access_token"`
1515

16+
UserAgentFilter []string `yaml:"user_agent_filter"`
17+
1618
Header struct {
1719
Key struct {
1820
ConnectingIP string `yaml:"connecting_ip"`

cmd/analytics-server/serve.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ func Serve(ctx context.Context, config *Config) error {
6565
}
6666
}()
6767

68+
go func() {
69+
err := db.CountAll(ctx, config.UserAgentFilter)
70+
if err != nil {
71+
log.Println("Occurred an error while counting targets:", err)
72+
}
73+
}()
74+
6875
switch {
6976
case config.Key == "" || config.Cert == "":
7077
log.Println("Listen on http://" + config.Listen)

server/api_v1.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package server
77
import (
88
"context"
99
"encoding/json"
10+
"fmt"
1011
"github.com/jellyterra/go-httpform"
1112
"net/http"
1213
)
@@ -172,5 +173,86 @@ func V1GetApi(e *ApiEnv) map[string]http.HandlerFunc {
172173

173174
return 0, nil
174175
}),
176+
177+
"/getAccessCountOfTarget": Wrap(func(w http.ResponseWriter, r *http.Request) (int, error) {
178+
ctx := r.Context()
179+
180+
wrap, err := httpform.WrapFromRequest(r)
181+
if err != nil {
182+
return http.StatusBadRequest, err
183+
}
184+
185+
var (
186+
target = wrap.StringRequired("target")
187+
)
188+
189+
err = wrap.Parse()
190+
if err != nil {
191+
return http.StatusBadRequest, err
192+
}
193+
194+
c, err := e.Database.GetAccessCountOfTarget(ctx, *target)
195+
if err != nil {
196+
return http.StatusInternalServerError, err
197+
}
198+
199+
_, _ = w.Write([]byte(c))
200+
201+
return 0, nil
202+
}),
203+
204+
"/getAddrCountOfTarget": Wrap(func(w http.ResponseWriter, r *http.Request) (int, error) {
205+
ctx := r.Context()
206+
207+
wrap, err := httpform.WrapFromRequest(r)
208+
if err != nil {
209+
return http.StatusBadRequest, err
210+
}
211+
212+
var (
213+
target = wrap.StringRequired("target")
214+
)
215+
216+
err = wrap.Parse()
217+
if err != nil {
218+
return http.StatusBadRequest, err
219+
}
220+
221+
c, err := e.Database.GetAccessAddrCountOfTarget(ctx, *target)
222+
if err != nil {
223+
return http.StatusInternalServerError, err
224+
}
225+
226+
_, _ = w.Write([]byte(fmt.Sprint(c)))
227+
228+
return 0, nil
229+
}),
230+
231+
"/getUuidCountOfTarget": Wrap(func(w http.ResponseWriter, r *http.Request) (int, error) {
232+
ctx := r.Context()
233+
234+
wrap, err := httpform.WrapFromRequest(r)
235+
if err != nil {
236+
return http.StatusBadRequest, err
237+
}
238+
239+
var (
240+
target = wrap.StringRequired("target")
241+
)
242+
243+
err = wrap.Parse()
244+
if err != nil {
245+
return http.StatusBadRequest, err
246+
}
247+
248+
c, err := e.Database.GetAccessUuidCountOfTarget(ctx, *target)
249+
if err != nil {
250+
return http.StatusInternalServerError, err
251+
}
252+
253+
_, _ = w.Write([]byte(fmt.Sprint(c)))
254+
255+
return 0, nil
256+
}),
175257
}
176258
}

server/db.go

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@ import (
88
"context"
99
"github.com/redis/go-redis/v9"
1010
"strconv"
11+
"strings"
1112
"time"
1213
)
1314

15+
const (
16+
kAccessReports_ = "AccessReports:"
17+
kAccessReports_Timestamps = "AccessReports:Timestamps"
18+
kAccessCounts_Targets = "AccessCounts:Targets"
19+
kAccessAddrs_Target_ = "AccessAddrs:Target:"
20+
kAccessUuids_Target_ = "AccessUuids:Target:"
21+
)
22+
1423
type Database struct {
1524
Client *redis.Client
1625
}
@@ -23,23 +32,85 @@ func (db *Database) AddAccessReport(ctx context.Context, report *AccessReport) e
2332
score, _ = strconv.ParseFloat(now.Format("20060102150405.99"), 64)
2433
)
2534

26-
err := db.Client.HSet(ctx, "AccessReports:"+timestamp, *report).Err()
35+
err := db.Client.HSet(ctx, kAccessReports_+timestamp, *report).Err()
2736
if err != nil {
2837
return err
2938
}
3039

31-
err = db.Client.ZAdd(ctx, "AccessReports:Timestamps", redis.Z{Score: score, Member: timestamp}).Err()
40+
err = db.Client.ZAdd(ctx, kAccessReports_Timestamps, redis.Z{Score: score, Member: timestamp}).Err()
3241
if err != nil {
3342
return err
3443
}
3544

36-
return nil
45+
return db.Client.HIncrBy(ctx, kAccessCounts_Targets, report.Target, 1).Err()
46+
}
47+
48+
func (db *Database) AddAccessAddrOfTarget(ctx context.Context, target, addr string) error {
49+
return db.Client.SAdd(ctx, kAccessAddrs_Target_+target, addr).Err()
50+
}
51+
52+
func (db *Database) AddAccessUuidOfTarget(ctx context.Context, target, uuid string) error {
53+
return db.Client.SAdd(ctx, kAccessUuids_Target_+target, uuid).Err()
3754
}
3855

3956
func (db *Database) GetAccessReportsTimestamps(ctx context.Context, start, end string) ([]string, error) {
40-
return db.Client.ZRangeByScore(ctx, "AccessReports:Timestamps", &redis.ZRangeBy{Min: start, Max: end}).Result()
57+
return db.Client.ZRangeByScore(ctx, kAccessReports_Timestamps, &redis.ZRangeBy{Min: start, Max: end}).Result()
4158
}
4259

4360
func (db *Database) GetAccessReportByTimestamp(ctx context.Context, timestamp string) (report AccessReport, _ error) {
44-
return report, db.Client.HGetAll(ctx, "AccessReports:"+timestamp).Scan(&report)
61+
return report, db.Client.HGetAll(ctx, kAccessReports_+timestamp).Scan(&report)
62+
}
63+
64+
func (db *Database) GetAccessCountOfTarget(ctx context.Context, target string) (string, error) {
65+
return db.Client.HGet(ctx, kAccessCounts_Targets, target).Result()
66+
}
67+
68+
func (db *Database) GetAccessAddrCountOfTarget(ctx context.Context, target string) (int64, error) {
69+
return db.Client.SCard(ctx, kAccessAddrs_Target_+target).Result()
70+
}
71+
72+
func (db *Database) GetAccessUuidCountOfTarget(ctx context.Context, target string) (int64, error) {
73+
return db.Client.SCard(ctx, kAccessUuids_Target_+target).Result()
74+
}
75+
76+
func (db *Database) CountAll(ctx context.Context, userAgentFilter []string) error {
77+
timestamps, err := db.GetAccessReportsTimestamps(ctx, "0", time.Now().UTC().Format("20060102150405.99"))
78+
if err != nil {
79+
return err
80+
}
81+
82+
for _, timestamp := range timestamps {
83+
report, err := db.GetAccessReportByTimestamp(ctx, timestamp)
84+
if err != nil {
85+
return err
86+
}
87+
88+
isFiltered := false
89+
for _, substr := range userAgentFilter {
90+
if strings.Contains(report.UserAgent, substr) {
91+
isFiltered = true
92+
break
93+
}
94+
}
95+
if isFiltered {
96+
continue
97+
}
98+
99+
err = db.AddAccessAddrOfTarget(ctx, report.Target, report.SourceIP)
100+
if err != nil {
101+
return err
102+
}
103+
104+
err = db.AddAccessUuidOfTarget(ctx, report.Target, report.UUID)
105+
if err != nil {
106+
return err
107+
}
108+
109+
err = db.Client.HIncrBy(ctx, kAccessCounts_Targets, report.Target, 1).Err()
110+
if err != nil {
111+
return err
112+
}
113+
}
114+
115+
return nil
45116
}

0 commit comments

Comments
 (0)