|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
| 5 | + "context" |
4 | 6 | "crypto/rand" |
5 | 7 | "encoding/json" |
6 | 8 | "fmt" |
| 9 | + "io" |
7 | 10 | "log" |
8 | 11 | "net/http" |
9 | 12 | "os" |
10 | 13 | "strconv" |
11 | 14 | "strings" |
| 15 | + "time" |
12 | 16 |
|
13 | 17 | "go.etcd.io/bbolt" |
14 | 18 | ) |
15 | 19 |
|
16 | 20 | var db *bbolt.DB |
17 | 21 | var authToken string |
18 | 22 |
|
| 23 | +var rybbitSiteID = os.Getenv("RYBBIT_SITE_ID") |
| 24 | +var rybbitSiteKey = os.Getenv("RYBBIT_SITE_KEY") |
| 25 | +var rybbitSiteURL = os.Getenv("RYBBIT_SITE_URL") |
| 26 | +var rybbitClient = &http.Client{Timeout: 3 * time.Second} |
| 27 | + |
19 | 28 | const bucketName = "urls" |
20 | 29 | const maxPageSize = 100 |
21 | 30 |
|
@@ -216,6 +225,7 @@ func handleGetURL(w http.ResponseWriter, r *http.Request) { |
216 | 225 | return |
217 | 226 | } |
218 | 227 |
|
| 228 | + handlePageView(r) |
219 | 229 | http.Redirect(w, r, url, http.StatusFound) |
220 | 230 | } |
221 | 231 |
|
@@ -269,6 +279,65 @@ func handleDeleteURL(w http.ResponseWriter, r *http.Request, path string) { |
269 | 279 | w.WriteHeader(http.StatusNoContent) |
270 | 280 | } |
271 | 281 |
|
| 282 | +func handlePageView(r *http.Request) { |
| 283 | + if rybbitSiteID == "" || rybbitSiteKey == "" || rybbitSiteURL == "" { |
| 284 | + return |
| 285 | + } |
| 286 | + |
| 287 | + ip := r.Header.Get("X-Forwarded-For") |
| 288 | + if ip == "" { |
| 289 | + ip = strings.Split(r.RemoteAddr, ":")[0] |
| 290 | + } else { |
| 291 | + ip = strings.TrimSpace(strings.Split(ip, ",")[0]) |
| 292 | + } |
| 293 | + |
| 294 | + hostname := r.Host |
| 295 | + if hostname == "" { |
| 296 | + if h := r.URL.Hostname(); h != "" { |
| 297 | + hostname = h |
| 298 | + } |
| 299 | + } |
| 300 | + |
| 301 | + data := map[string]string{ |
| 302 | + "site_id": rybbitSiteID, |
| 303 | + "type": "pageview", |
| 304 | + "pathname": r.URL.Path, |
| 305 | + "hostname": hostname, |
| 306 | + "referrer": r.Referer(), |
| 307 | + "language": r.Header.Get("Accept-Language"), |
| 308 | + "user_agent": r.UserAgent(), |
| 309 | + "ip_address": ip, |
| 310 | + } |
| 311 | + |
| 312 | + jsonData, err := json.Marshal(data) |
| 313 | + if err != nil { |
| 314 | + return |
| 315 | + } |
| 316 | + |
| 317 | + go func(body []byte) { |
| 318 | + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) |
| 319 | + defer cancel() |
| 320 | + |
| 321 | + req, err := http.NewRequestWithContext(ctx, "POST", strings.TrimRight(rybbitSiteURL, "/")+"/api/track", bytes.NewReader(body)) |
| 322 | + if err != nil { |
| 323 | + return |
| 324 | + } |
| 325 | + req.Header.Set("Content-Type", "application/json") |
| 326 | + req.Header.Set("Authorization", "Bearer "+rybbitSiteKey) |
| 327 | + |
| 328 | + resp, err := rybbitClient.Do(req) |
| 329 | + if err != nil { |
| 330 | + return |
| 331 | + } |
| 332 | + defer resp.Body.Close() |
| 333 | + |
| 334 | + if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
| 335 | + b, _ := io.ReadAll(resp.Body) |
| 336 | + log.Printf("Rybbit tracking error: %s", string(b)) |
| 337 | + } |
| 338 | + }(jsonData) |
| 339 | +} |
| 340 | + |
272 | 341 | func main() { |
273 | 342 | authToken = os.Getenv("APP_TOKEN") |
274 | 343 | if authToken == "" { |
|
0 commit comments