/
ethical_ad_block_server.go
394 lines (341 loc) · 10.1 KB
/
ethical_ad_block_server.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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
// Copyright (c) 2015 Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
// This software is licensed under GPL v3 or later
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
"runtime"
"ethical_ad_block/helpers"
)
const (
AD_UNKNOWN = 0
AD_GOOD = 1
AD_FRAUDULENT = 2
AD_TAXING = 3
AD_MALICIOUS = 4
StatusUnprocessableEntity = 422
)
type AdData struct {
votes_good *helpers.FileBackedMap
votes_fraudulent *helpers.FileBackedMap
votes_taxing *helpers.FileBackedMap
votes_malicious *helpers.FileBackedMap
voted_ad_type *helpers.FileBackedMap
}
func NewAdData() *AdData {
self := new(AdData)
var err error
self.votes_good, err = helpers.NewFileBackedMap("votes_good", 1024)
if err != nil {
panic(err)
}
self.votes_fraudulent, err = helpers.NewFileBackedMap("votes_fraudulent", 1024)
if err != nil {
panic(err)
}
self.votes_taxing, err = helpers.NewFileBackedMap("votes_taxing", 1024)
if err != nil {
panic(err)
}
self.votes_malicious, err = helpers.NewFileBackedMap("votes_malicious", 1024)
if err != nil {
panic(err)
}
self.voted_ad_type, err = helpers.NewFileBackedMap("voted_ad_type", 1048576)
if err != nil {
panic(err)
}
return self
}
func (self *AdData) AllMaps() []*helpers.FileBackedMap {
return []*helpers.FileBackedMap{
self.votes_good,
self.votes_fraudulent,
self.votes_taxing,
self.votes_malicious,
self.voted_ad_type,
}
}
var g_user_ads map[string]*helpers.FileBackedMap
var g_all_ads *AdData
var g_user_ids map[string]time.Time
func hasParameters(r *http.Request, keys... string) bool {
parameters := r.URL.Query()
for _, key := range keys {
value, ok := parameters[key]
if ! ok || value == nil || len(value) == 0 {
return false
}
}
return true
}
func validateRequest(method string, w http.ResponseWriter, r *http.Request, keys... string) (map[string]string, bool) {
// Reply with a HTTP "Method Not Allowed" if the wrong HTTP Method is used
if r.Method != method {
http.Error(w, "Expected HTTP Method '" + r.Method + "'", http.StatusMethodNotAllowed)
return nil, false
}
// Copy all the values and make sure they are valid
parameters := r.URL.Query()
validated_parameters := make(map[string]string)
has_valid_params := true
for _, key := range keys {
value, has_value := parameters[key]
if has_value && value != nil && len(value) > 0 && value[0] != "null" && helpers.IsAlphaNumeric(value[0]) {
validated_parameters[key] = value[0]
} else {
has_valid_params = false
break
}
}
// Reply with a HTTP "Unprocessable Entity" if the params are missing or invalid
if ! has_valid_params {
http.Error(w, "Invalid parameters", StatusUnprocessableEntity)
return nil, false
}
return validated_parameters, true
}
func httpCB(w http.ResponseWriter, r *http.Request) {
// fmt.Printf("request: %v\n", r.URL)
// All requests with a path should 404
if r.URL.Path != "/" {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
// Set the server headers
/*
epoch := "Thu, 01 Jan 1970 00:00:00 UTC"
header := w.Header()
header.Set("Server", "Ethical Ad Block Server 0.1")
header.Set("Pragma", "no-cache")
header.Set("Cache-Control", "public, no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
header.Set("Expires", "0")
header.Set("Last-Modified", epoch)
header.Set("If-Modified-Since", epoch)
*/
// Check which type the ad is
if hasParameters(r, "voted_ad_type") {
if parameters, ok := validateRequest("GET", w, r, "voted_ad_type"); ok {
responseVotedAdType(w, parameters)
}
// Vote for ad
} else if hasParameters(r, "vote_ad", "ad_type", "user_id") {
// FIXME: Make voting use HTTP POST
if parameters, ok := validateRequest("GET", w, r, "vote_ad", "ad_type", "user_id"); ok {
responseVoteForAd(w, parameters)
}
// List ads
} else if hasParameters(r, "list") {
responseListAds(w)
// Show memory
} else if hasParameters(r, "memory") {
responseShowMemory(w)
// Clear all data
} else if hasParameters(r, "clear") {
responseClear(w)
// Write all data to disk
} else if hasParameters(r, "save") {
responseSave(w)
// Unexpected request
} else {
http.Error(w, "Unexpected request", http.StatusBadRequest)
}
}
func responseClear(w http.ResponseWriter) {
// Clear the overall votes
has_error := false
for _, m := range g_all_ads.AllMaps() {
if err := m.RemoveAll(); err != nil {
has_error = true
log.Printf("%v\n", err)
}
}
// Clear the user votes
for _, user_ads := range g_user_ads {
if err := user_ads.RemoveAll(); err != nil {
has_error = true
log.Printf("%v\n", err)
}
}
if has_error {
http.Error(w, "Failed to clear ads", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "All data cleared\n")
}
func responseVotedAdType(w http.ResponseWriter, parameters map[string]string) {
// Get the arguments
ad_id := parameters["voted_ad_type"]
// Get the voted ad type
voted_ad_type, _ := g_all_ads.voted_ad_type.Get(ad_id)
fmt.Fprintf(w, "%d\n", voted_ad_type)
}
func responseVoteForAd(w http.ResponseWriter, parameters map[string]string) {
// Get the arguments
ad_id := parameters["vote_ad"]
ad_type := parameters["ad_type"]
user_id := parameters["user_id"]
// Figure out which type of vote it will be
var all_ads *helpers.FileBackedMap
var user_vote_type uint64 = AD_UNKNOWN
switch ad_type {
case "good":
all_ads = g_all_ads.votes_good
user_vote_type = AD_GOOD
case "fraudulent":
all_ads = g_all_ads.votes_fraudulent
user_vote_type = AD_FRAUDULENT
case "taxing":
all_ads = g_all_ads.votes_taxing
user_vote_type = AD_TAXING
case "malicious":
all_ads = g_all_ads.votes_malicious
user_vote_type = AD_MALICIOUS
default:
http.Error(w, "Invalid ad_type", http.StatusBadRequest)
return
}
// Initialize space for this user's ads
user_ads, ok := g_user_ads[user_id]
if ! ok {
user_ads, _ = helpers.NewFileBackedMap("user_" + user_id, 1024)
g_user_ads[user_id] = user_ads
}
// Remove the previous vote, if there already is one for this ad
if vote_type, ok := user_ads.Get(ad_id); ok {
// FIXME: We don't really need to remove this, since we Set it again on the new vote.
err := user_ads.Remove(ad_id)
if err != nil {
http.Error(w, "Failed to remove ad", http.StatusInternalServerError)
log.Printf("%v\n", err)
return
}
switch vote_type {
case AD_GOOD:
g_all_ads.votes_good.Decrement(ad_id)
case AD_FRAUDULENT:
g_all_ads.votes_fraudulent.Decrement(ad_id)
case AD_TAXING:
g_all_ads.votes_taxing.Decrement(ad_id)
case AD_MALICIOUS:
g_all_ads.votes_malicious.Decrement(ad_id)
}
}
// Cast the new vote
votes := all_ads.Increment(ad_id)
err := user_ads.Set(ad_id, user_vote_type)
if err != nil {
http.Error(w, "Failed to set ad", http.StatusInternalServerError)
log.Printf("%v\n", err)
return
}
// Update the voted ad type
err = updateVotedAdType(ad_id)
if err != nil {
http.Error(w, "Failed to update ad voted type", http.StatusInternalServerError)
log.Printf("%v\n", err)
return
}
// Save the time that the user voted
g_user_ids[user_id] = time.Now()
// Return the response
fmt.Fprintf(w, "ad_id:%s, ad_type:%s, votes:%d\n", ad_id, ad_type, votes)
}
func responseListAds(w http.ResponseWriter) {
// Print the values of all the ad maps
fmt.Fprintf(w, "good:\n")
g_all_ads.votes_good.Each(func(ad_id string, votes uint64) {
fmt.Fprintf(w, " %s : %d\n", ad_id, votes)
})
fmt.Fprintf(w, "fraudulent:\n")
g_all_ads.votes_fraudulent.Each(func(ad_id string, votes uint64) {
fmt.Fprintf(w, " %s : %d\n", ad_id, votes)
})
fmt.Fprintf(w, "votes_taxing:\n")
g_all_ads.votes_taxing.Each(func(ad_id string, votes uint64) {
fmt.Fprintf(w, " %s : %d\n", ad_id, votes)
})
fmt.Fprintf(w, "malicious:\n")
g_all_ads.votes_malicious.Each(func(ad_id string, votes uint64) {
fmt.Fprintf(w, " %s : %d\n", ad_id, votes)
})
fmt.Fprintf(w, "voted_ad_type:\n")
g_all_ads.voted_ad_type.Each(func(ad_id string, votes uint64) {
fmt.Fprintf(w, " %s : %d\n", ad_id, votes)
})
}
func responseShowMemory(w http.ResponseWriter) {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
fmt.Fprintf(w, "mem.Alloc: %d\n", mem.Alloc)
fmt.Fprintf(w, "mem.TotalAlloc: %d\n", mem.TotalAlloc)
fmt.Fprintf(w, "mem.HeapAlloc: %d\n", mem.HeapAlloc)
fmt.Fprintf(w, "mem.HeapSys: %d\n", mem.HeapSys)
}
func responseSave(w http.ResponseWriter) {
// Write the overall votes to disk
g_all_ads.votes_good.SaveToDisk()
g_all_ads.votes_fraudulent.SaveToDisk()
g_all_ads.votes_taxing.SaveToDisk()
g_all_ads.votes_malicious.SaveToDisk()
g_all_ads.voted_ad_type.SaveToDisk()
// Write the user votes to disk
for _, user_ads := range g_user_ads {
user_ads.SaveToDisk()
}
fmt.Fprintf(w, "All data saved\n")
}
func updateVotedAdType(ad_id string) (error) {
// Get the number of times this ad is counted for each category
good_count, _ := g_all_ads.votes_good.Get(ad_id)
fraudulent_count, _ := g_all_ads.votes_fraudulent.Get(ad_id)
taxing_count, _ := g_all_ads.votes_taxing.Get(ad_id)
malicious_count, _ := g_all_ads.votes_malicious.Get(ad_id)
largest_count := helpers.Larger(good_count, fraudulent_count, taxing_count, malicious_count)
// Figure out the top type of votes
var ad_type uint64
if largest_count == 0 {
ad_type = AD_UNKNOWN
} else if good_count >= largest_count {
ad_type = AD_GOOD
} else if fraudulent_count >= largest_count {
ad_type = AD_FRAUDULENT
} else if taxing_count >= largest_count {
ad_type = AD_TAXING
} else if malicious_count >= largest_count {
ad_type = AD_MALICIOUS
}
// Save the top ad type in the cache
err := g_all_ads.voted_ad_type.Set(ad_id, ad_type)
if err != nil {
return err
}
return nil
}
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
// Initialize all the maps
g_user_ads = make(map[string]*helpers.FileBackedMap)
g_all_ads = NewAdData()
g_user_ids = make(map[string]time.Time)
// Get the port number
var err error
var port int64 = 9000
if len(os.Args) >= 1 {
port, err = strconv.ParseInt(os.Args[1], 10, 0)
if err != nil {
log.Fatal(err)
}
}
// Run the server
server_address := fmt.Sprintf("127.0.0.1:%v", port)
http.HandleFunc("/", httpCB)
err = http.ListenAndServe(server_address, nil)
if err != nil {
log.Fatal(err)
}
}