2016-11-21 12:24:50 +00:00
|
|
|
package api
|
2016-11-19 21:35:23 +00:00
|
|
|
|
|
|
|
import (
|
2016-12-11 13:50:01 +00:00
|
|
|
"encoding/base64"
|
|
|
|
"net/http"
|
2016-12-24 13:57:04 +00:00
|
|
|
"time"
|
2016-12-25 15:37:45 +00:00
|
|
|
|
2016-12-11 13:50:01 +00:00
|
|
|
"github.com/mssola/user_agent"
|
2018-04-24 08:28:23 +00:00
|
|
|
"github.com/usefathom/fathom/pkg/datastore"
|
|
|
|
"github.com/usefathom/fathom/pkg/models"
|
2018-04-25 09:59:30 +00:00
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2016-11-19 21:35:23 +00:00
|
|
|
)
|
|
|
|
|
2018-05-06 09:53:19 +00:00
|
|
|
var buffer []*models.RawPageview
|
|
|
|
var bufferSize = 50
|
2018-05-02 13:33:01 +00:00
|
|
|
var timeout = 200 * time.Millisecond
|
2017-01-25 21:48:24 +00:00
|
|
|
|
|
|
|
func persistPageviews() {
|
|
|
|
if len(buffer) > 0 {
|
2018-05-06 09:53:19 +00:00
|
|
|
err := datastore.SaveRawPageviews(buffer)
|
2018-04-25 09:59:30 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error saving pageviews: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear buffer regardless of error... this means data loss, but better than filling the buffer for now
|
2017-01-25 21:48:24 +00:00
|
|
|
buffer = buffer[:0]
|
|
|
|
}
|
|
|
|
}
|
2016-12-11 13:50:01 +00:00
|
|
|
|
2018-05-06 09:53:19 +00:00
|
|
|
func processBuffer(pv chan *models.RawPageview) {
|
2017-01-25 21:48:24 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case pageview := <-pv:
|
|
|
|
buffer = append(buffer, pageview)
|
|
|
|
if len(buffer) >= bufferSize {
|
|
|
|
persistPageviews()
|
|
|
|
}
|
|
|
|
case <-time.After(timeout):
|
|
|
|
persistPageviews()
|
|
|
|
}
|
2016-12-11 13:50:01 +00:00
|
|
|
}
|
2017-01-25 21:48:24 +00:00
|
|
|
}
|
2016-12-11 13:50:01 +00:00
|
|
|
|
2017-01-25 21:48:24 +00:00
|
|
|
/* middleware */
|
|
|
|
func NewCollectHandler() http.Handler {
|
2018-05-06 09:53:19 +00:00
|
|
|
pageviews := make(chan *models.RawPageview, bufferSize)
|
2017-01-25 21:48:24 +00:00
|
|
|
go processBuffer(pageviews)
|
2016-12-11 13:50:01 +00:00
|
|
|
|
2018-04-25 09:59:30 +00:00
|
|
|
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
2017-01-25 21:48:24 +00:00
|
|
|
// abort if this is a bot.
|
2018-05-02 13:33:01 +00:00
|
|
|
userAgent := r.UserAgent()
|
|
|
|
ua := user_agent.New(userAgent)
|
2017-01-25 21:48:24 +00:00
|
|
|
if ua.Bot() {
|
2018-04-25 09:59:30 +00:00
|
|
|
return nil
|
2017-01-13 15:45:17 +00:00
|
|
|
}
|
2017-01-25 21:48:24 +00:00
|
|
|
|
|
|
|
q := r.URL.Query()
|
|
|
|
now := time.Now()
|
2016-12-11 13:50:01 +00:00
|
|
|
|
2018-05-02 13:33:01 +00:00
|
|
|
// get pageview details
|
2018-05-06 09:53:19 +00:00
|
|
|
pageview := &models.RawPageview{
|
|
|
|
SessionID: q.Get("sid"),
|
|
|
|
Pathname: q.Get("p"),
|
|
|
|
IsNewVisitor: q.Get("n") == "1",
|
|
|
|
IsUnique: q.Get("u") == "1",
|
|
|
|
IsBounce: q.Get("b") != "0",
|
|
|
|
Referrer: q.Get("r"),
|
|
|
|
Duration: 0,
|
|
|
|
Timestamp: now,
|
2017-01-25 21:48:24 +00:00
|
|
|
}
|
|
|
|
|
2018-05-06 09:53:19 +00:00
|
|
|
err := datastore.SaveRawPageview(pageview)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-01-25 21:48:24 +00:00
|
|
|
}
|
|
|
|
// push onto channel
|
2018-05-06 09:53:19 +00:00
|
|
|
//pageviews <- pageview
|
2016-12-11 13:50:01 +00:00
|
|
|
|
2017-01-25 21:48:24 +00:00
|
|
|
// 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)
|
2016-12-11 13:50:01 +00:00
|
|
|
|
2017-01-25 21:48:24 +00:00
|
|
|
// 1x1 px transparent GIF
|
|
|
|
b, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
|
|
|
|
w.Write(b)
|
2018-04-25 09:59:30 +00:00
|
|
|
return nil
|
2017-01-25 21:48:24 +00:00
|
|
|
})
|
2016-11-19 21:35:23 +00:00
|
|
|
}
|