-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
184 lines (152 loc) · 4.6 KB
/
main.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
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"strconv"
"time"
"github.com/patrickmn/go-cache"
)
type Lane struct {
Headers map[string]string `json:"headers"`
Content string `json:"content"`
LaneKey string `json:"-"`
}
type Config struct {
Default string `json:"default"`
Lanes map[string]Lane `json:"lanes"`
}
type ConfigWithPort struct {
*Config
Port int `json:"port"`
}
type LaneChange struct {
LaneKey string `json:"lane"`
Duration time.Duration `json:"duration"`
}
type LaneChangeResp struct {
LaneKey string `json:"lane"`
Expires time.Time `json:"expires"`
IP string `json:"ip"`
}
// https://golangcode.com/get-the-request-ip-addr/
// https://stackoverflow.com/a/33301173
func GetIP(r *http.Request) string {
forwarded := r.Header.Get("X-Real-IP")
if forwarded != "" {
return forwarded
}
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
return ip
}
func main() {
log.Println("Welcome to LaneChange, see the readme for more information")
// try to open config file
jsonData, err := ioutil.ReadFile("config.json")
if err != nil {
fmt.Println("Error:", err)
fmt.Println("Ensure that config.json exists in the current directory and is well-formatted")
return
}
// load our preferences from the config file
// and some error handling
var config ConfigWithPort
err = json.Unmarshal(jsonData, &config)
if err != nil {
fmt.Println("Error:", err)
fmt.Println("An issue was encountered trying to parse config.json")
return
}
if config.Port == 0 || config.Default == "" || len(config.Lanes) == 0 {
fmt.Println("Error: Check that \"port\", \"default\", and \"lanes\" keys are all provided in config.json!")
return
}
defaultLane, ok := config.Lanes[config.Default]
if !ok {
fmt.Printf("Error: Provided default key \"%s\" does not exist in lanes!\n", config.Default)
return
}
for laneKey, lane := range config.Lanes {
// hookup the key inside the lane for reference later
lane.LaneKey = laneKey
config.Lanes[laneKey] = lane
}
// setup the cache (drop expired every 10 min)
// TODO: load from disk in case we crashed
users := cache.New(cache.NoExpiration, 10*time.Minute)
// our main endpoint, lookup our lane for the incoming IP
// and return the expected response for that lane here
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
ip := GetIP(req)
// lookup current lane for our user
var lane *Lane
lane = &defaultLane
userLane, found := users.Get(ip)
if found {
lane = userLane.(*Lane)
}
// apply lane headers
headers := res.Header()
headers.Set("Cache-Control", "max-age=0, no-cache, no-store")
for key, value := range lane.Headers {
headers.Set(key, value)
}
// write lane content
fmt.Fprintf(res, lane.Content)
})
http.HandleFunc("/change", func(res http.ResponseWriter, req *http.Request) {
ip := GetIP(req)
headers := res.Header()
headers.Set("Access-Control-Allow-Origin", "*")
headers.Set("Access-Control-Allow-Methods", "GET, POST, DELETE")
if req.Method == http.MethodDelete {
// remove entry for IP (does nothing if doesn't exist)
users.Delete(ip)
return
}
// process incoming lane change preference
if req.Method == http.MethodPost {
var change LaneChange
err := json.NewDecoder(req.Body).Decode(&change)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return
}
userLane, ok := config.Lanes[change.LaneKey]
if ok {
// set the lane pointer for this IP, expire with their duration
// if duration is omitted, will be 0, which will go to the default expiry
users.Set(ip, &userLane, change.Duration*time.Second)
return
}
http.Error(res, "Error with request (Lane key invalid?)", http.StatusBadRequest)
return
}
// try to lookup user (GET)
userLane, expiration, found := users.GetWithExpiration(ip)
if found {
var change LaneChangeResp
change.LaneKey = userLane.(*Lane).LaneKey
change.Expires = expiration
change.IP = ip
output, _ := json.MarshalIndent(change, "", "\t")
res.Write(output)
return
}
res.WriteHeader(http.StatusNotFound)
})
http.HandleFunc("/config", func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Access-Control-Allow-Origin", "*")
// respond with our config, but don't expose port
var configNoPort Config
configNoPort.Lanes = config.Lanes
configNoPort.Default = config.Default
output, _ := json.MarshalIndent(configNoPort, "", "\t")
res.Write(output)
})
log.Printf("Listening on localhost:%d\n", config.Port)
log.Fatal(http.ListenAndServe(":"+strconv.Itoa(config.Port), nil))
}