forked from usefathom/fathom
-
Notifications
You must be signed in to change notification settings - Fork 0
/
collect.go
137 lines (114 loc) · 3.25 KB
/
collect.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
package api
import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"net/http"
"strings"
"time"
"github.com/dannyvankooten/ana/datastore"
"github.com/dannyvankooten/ana/models"
"github.com/mssola/user_agent"
)
var buffer []*models.Pageview
var bufferSize = 250
var timeout = 100 * time.Millisecond
func getRequestIp(r *http.Request) string {
ipAddress := r.RemoteAddr
headerForwardedFor := r.Header.Get("X-Forwarded-For")
if headerForwardedFor != "" {
ipAddress = headerForwardedFor
}
return ipAddress
}
func persistPageviews() {
if len(buffer) > 0 {
err := datastore.SavePageviews(buffer)
buffer = buffer[:0]
checkError(err)
}
}
func processBuffer(pv chan *models.Pageview) {
for {
select {
case pageview := <-pv:
buffer = append(buffer, pageview)
if len(buffer) >= bufferSize {
persistPageviews()
}
case <-time.After(timeout):
persistPageviews()
}
}
}
/* middleware */
func NewCollectHandler() http.Handler {
pageviews := make(chan *models.Pageview, 100)
go processBuffer(pageviews)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ua := user_agent.New(r.UserAgent())
// abort if this is a bot.
if ua.Bot() {
return
}
q := r.URL.Query()
// find or insert page
page, err := datastore.GetPageByHostnameAndPath(q.Get("h"), q.Get("p"))
if err != nil {
page = &models.Page{
Hostname: q.Get("h"),
Path: q.Get("p"),
Title: q.Get("t"),
}
err = datastore.SavePage(page)
checkError(err)
}
// find or insert visitor.
now := time.Now()
ipAddress := getRequestIp(r)
visitorKey := generateVisitorKey(now.Format("2006-01-02"), ipAddress, r.UserAgent())
visitor, err := datastore.GetVisitorByKey(visitorKey)
if err != nil {
visitor = &models.Visitor{
IpAddress: ipAddress,
BrowserLanguage: q.Get("l"),
ScreenResolution: q.Get("sr"),
DeviceOS: ua.OS(),
Country: "",
Key: visitorKey,
}
// add browser details
visitor.BrowserName, visitor.BrowserVersion = ua.Browser()
visitor.BrowserName = parseMajorMinor(visitor.BrowserName)
err = datastore.SaveVisitor(visitor)
checkError(err)
}
pageview := &models.Pageview{
PageID: page.ID,
VisitorID: visitor.ID,
ReferrerUrl: q.Get("ru"),
ReferrerKeyword: q.Get("rk"),
Timestamp: now.Format("2006-01-02 15:04:05"),
}
// only store referrer URL if not coming from own site
if strings.Contains(pageview.ReferrerUrl, page.Hostname) {
pageview.ReferrerUrl = ""
}
// push onto channel
pageviews <- pageview
// don't you cache this
w.Header().Set("Content-Type", "image/gif")
w.Header().Set("Expires", "Mon, 01 Jan 1990 00:00:00 GMT")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.WriteHeader(http.StatusOK)
// 1x1 px transparent GIF
b, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
w.Write(b)
})
}
// generateVisitorKey generates the "unique" visitor key from date, user agent + screen resolution
func generateVisitorKey(date string, ipAddress string, userAgent string) string {
byteKey := md5.Sum([]byte(date + ipAddress + userAgent))
return hex.EncodeToString(byteKey[:])
}