/
decisions.go
209 lines (197 loc) · 4.83 KB
/
decisions.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Copyright (c) 2012 Jason McVetta. This is Free Software, released under the
// terms of the GPL v3. See http://www.gnu.org/copyleft/gpl.html for details.
package main
import (
"encoding/json"
"fmt"
"github.com/bmizerany/pat"
"github.com/darkhelmet/env"
"github.com/jmcvetta/neo4j"
"github.com/jmcvetta/randutil"
"github.com/stathat/go"
"io/ioutil"
"labix.org/v2/mgo"
"log"
"net/http"
"strings"
"time"
)
var (
mongo *mgo.Database
neo *neo4j.Database
statPrefix string // Prefix for stat names to track on StatHat
ezkey string // EZ Key for StatHat
)
type Choice struct {
Text string
}
type DecisionRequest struct {
Quandary string
Choices []Choice
}
type DecisionResponse struct {
Decision string
}
type Decision struct {
Quandary string
Choices []string
Winner string
Ip string
Timestamp time.Time
UserAgent string
}
// Decide receives a JSON payload containing several strings, and returns a JSON
// message containing one of said strings, selected at random.
func Decide(w http.ResponseWriter, req *http.Request) {
//
// Parse Payload
//
if req.ContentLength <= 0 {
msg := "Content-Length must be greater than 0."
http.Error(w, msg, http.StatusLengthRequired)
return
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var dreq DecisionRequest
err = json.Unmarshal(body, &dreq)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if dreq.Quandary == "" {
msg := "Must supply a quandary"
http.Error(w, msg, http.StatusBadRequest)
return
}
//
// Discard empty choices
//
validChoices := []string{}
for _, choice := range dreq.Choices {
if choice.Text != "" {
validChoices = append(validChoices, choice.Text)
}
}
if len(validChoices) < 2 {
msg := fmt.Sprintln("Must provide at least 2 choices, but you provided", len(validChoices))
http.Error(w, msg, http.StatusBadRequest)
return
}
//
// Make the decision
//
winner, err := randutil.ChoiceString(validChoices)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
//
// Save to MongoDB
//
c := mongo.C("quandaries")
ip := req.Header.Get("X-Forwarded-For")
if ip != "" {
ip = strings.Split(ip, ",")[0]
} else {
ip = strings.Split(req.RemoteAddr, ":")[0]
}
d := Decision{
Quandary: dreq.Quandary,
Choices: validChoices,
Winner: winner,
Ip: ip,
Timestamp: time.Now(),
UserAgent: req.UserAgent(),
}
err = c.Insert(&d)
if err != nil {
msg := "MongoDB Error: " + err.Error()
http.Error(w, msg, http.StatusInternalServerError)
return
}
//
// Save to Neo4j
//
//
// Generate response
//
dres := DecisionResponse{winner}
blob, err := json.Marshal(dres)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req.Header.Add("content-type", "application/json")
w.Write(blob)
}
// timeTrack logs the time since start to StatHat.
// Inspired by: http://blog.stathat.com/2012/10/15/go_http_request_time.html
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
reqtimeName := statPrefix + ":reqtime:" + name
hitsName := statPrefix + ":hits:" + name
stathat.PostEZValue(reqtimeName, ezkey, elapsed.Seconds())
stathat.PostEZCountOne(hitsName, ezkey)
}
// track logs the time take by an HTTP request to StatHat
func track(fn http.HandlerFunc, name string) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
defer timeTrack(time.Now(), name)
fn(w, req)
}
}
func main() {
log.SetFlags(log.Ltime | log.Lshortfile)
//
// Configuration
//
port := env.StringDefault("PORT", "9000")
pwd := env.StringDefault("PWD", "/app")
mongoUrl := env.StringDefault("MONGOLAB_URI", "localhost")
neoUrl := env.StringDefault("NEO4J_URL", "http://localhost:7474/db/data")
statPrefix = env.StringDefault("STATHAT_PREFIX", "")
ezkey = env.StringDefault("STATHAT_EZKEY", "")
//
// Connect to MongoDB
//
log.Println("Connecting to MongoDB on " + mongoUrl + "...")
session, err := mgo.Dial(mongoUrl)
if err != nil {
log.Panicln(err)
}
defer session.Close()
mongo = session.DB("")
_, err = mongo.CollectionNames()
if err != nil {
log.Println("Setting db name to 'decisions'.")
mongo = session.DB("decisions")
}
//
// Connect to Neo4j
//
log.Println("Connecting to Neo4j on " + neoUrl + "...")
neo, err = neo4j.Connect(neoUrl)
if err != nil {
log.Println("Cannot connect to Neo4j:")
log.Println(err)
}
//
// Routing
//
mux := pat.New()
mux.Post("/decide", track(http.HandlerFunc(Decide), "/v1/decide"))
http.Handle("/v1/", http.StripPrefix("/v1", mux))
http.Handle("/", http.FileServer(http.Dir(pwd+"/app")))
//
// Start Webserver
//
log.Println("Starting webserver on port " + port + "...")
err = http.ListenAndServe(":"+port, nil)
if err != nil {
log.Panicln(err)
}
}