This repository was archived by the owner on Feb 9, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
152 lines (134 loc) · 3.41 KB
/
main.go
File metadata and controls
152 lines (134 loc) · 3.41 KB
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
150
151
152
package main
import (
"flag"
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
)
func main() {
var port int
var mongoAddr string
var mongoDB string
var mongoCollection string
flag.IntVar(&port, "port", 8080, "HTTP port")
flag.StringVar(&mongoAddr, "mongoAddr", "", "MongoDB addr")
flag.StringVar(&mongoDB, "mongoDatabase", "", "MongoDB database")
flag.StringVar(&mongoCollection, "mongoCollection", "", "MongoDB collection")
flag.Parse()
mustBe("mongoAddr", mongoAddr)
mustBe("mongoDatabase", mongoDB)
mustBe("mongoCollection", mongoCollection)
mux := http.NewServeMux()
storer := &mongoStorer{
addr: mongoAddr,
db: mongoDB,
collection: mongoCollection,
}
if err := storer.test(); err != nil {
fmt.Printf("Cannot connect to MongoDB, %v\n", err)
os.Exit(1)
}
mux.HandleFunc("/trck", track(storer))
fmt.Printf("starting trck listening on http:<addr>:%d/trck/:userid\n", port)
err := http.ListenAndServe(":"+strconv.Itoa(port), mux)
if err != nil {
fmt.Printf("Cannot start HTTP server, %v\n", err)
os.Exit(1)
}
}
func mustBe(name, val string) {
if val == "" {
fmt.Printf("%s must be set\n", name)
os.Exit(1)
}
}
func track(storer storer) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
user, err := extractUser(r.URL.EscapedPath())
if err != nil {
fmt.Printf("bad request, %v\n", err)
w.WriteHeader(http.StatusBadRequest)
return
}
ip, err := extractIP(r)
if err != nil {
fmt.Printf("cannot extract the IP from the request, %v\n", err)
}
err = storer.store(Record{
ID: bson.NewObjectId(),
Timestamp: time.Now(),
URL: r.URL.String(),
User: user,
UserAgent: r.UserAgent(),
IP: ip,
})
if err != nil {
fmt.Printf("cannot store the tracking request, %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
}
func extractIP(r *http.Request) (string, error) {
// if the original user is behind a proxy, then the original IP is in the header X-Forwarded-For
if source := r.Header.Get(http.CanonicalHeaderKey("X-Forwarded-For")); source != "" {
return source, nil
}
// Otherwise, the source IP is in request.RemoteAddr
ip, _, err := net.SplitHostPort(r.RemoteAddr)
return ip, err
}
func extractUser(path string) (string, error) {
paths := strings.Split(path, "/")
if len(paths) != 3 {
return "", fmt.Errorf("invalid path %s", path)
}
user := paths[2]
if len(user) == 0 {
return "", fmt.Errorf("invalid path %s", path)
}
return user, nil
}
// Record holds the tracking information of 1 visit
type Record struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Timestamp time.Time
URL string
User string
IP string
UserAgent string
}
type storer interface {
store(record Record) error
}
type mongoStorer struct {
addr string
db string
collection string
}
func (m *mongoStorer) test() error {
session, err := mgo.Dial(m.addr)
if err != nil {
return err
}
defer session.Close()
return session.Ping()
}
func (m *mongoStorer) store(record Record) error {
session, err := mgo.Dial(m.addr)
if err != nil {
return err
}
// For storing tracking events, the exact ordering doesn't matter
session.SetMode(mgo.Eventual, false)
defer session.Close()
c := session.DB(m.db).C(m.collection)
return c.Insert(record)
}