-
Notifications
You must be signed in to change notification settings - Fork 0
/
routes.go
129 lines (111 loc) · 2.92 KB
/
routes.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
package router
import (
"math/rand"
"net/http"
"net/http/httputil"
"time"
)
// Routes is the config expected to be loaded
type Routes struct {
Routes []Route `json:"routes"`
// TODO: default route
}
// Route describes a single route which is matched
// on Request and if so, will return the Response
type Route struct {
Request Request `json:"request"`
Response Response `json:"response"`
ProxyURL URL `json:"proxy_url"`
Priority int `json:"priority"` // 0 is highest. Used for ordering routes
Weight float64 `json:"weight"` // percentage weight between 0 and 1.0
Type string `json:"type"` // proxy or response. Response is default
}
// Request describes the expected request and will
// attempt to match all fields specified
type Request struct {
Method string `json:"method"`
Header map[string]string `json:"header"`
Host string `json:"host"`
Path string `json:"path"`
Query map[string]string `json:"query"`
// TODO: RemoteAddr, Body
}
// Response is put into the http.Response for a Request
type Response struct {
Status string `json:"status"`
StatusCode int `json:"status_code"`
Header map[string]string `json:"header"`
Body []byte `json:"body"`
}
type URL struct {
Scheme string `json:"scheme"`
Host string `json:"host"`
Path string `json:"path"`
}
func init() {
rand.Seed(time.Now().UnixNano())
}
func (r Route) Match(req *http.Request) bool {
// bail on nil
if len(r.Request.Method) == 0 || len(r.Request.Path) == 0 {
return false
}
// just for ease
rq := r.Request
// first level match, quick and dirty
if (rq.Method == req.Method) && (rq.Host == req.Host) && (rq.Path == req.URL.Path) {
// skip
} else {
return false
}
// match headers
for k, v := range rq.Header {
// does it match?
if rv := req.Header.Get(k); rv != v {
return false
}
}
// match query
vals := req.URL.Query()
for k, v := range rq.Query {
// does it match?
if rv := vals.Get(k); rv != v {
return false
}
}
// Now weight it. If already set to 0.0 then return
// Otherwise rand.Float64
if r.Weight == 0.0 || r.Weight < rand.Float64() {
return false
}
// we got a match!
return true
}
func (r Route) Write(w http.ResponseWriter, req *http.Request) {
// Type: proxy then proxy the request to whatever response is
if r.Type == "proxy" {
p := &httputil.ReverseProxy{
Director: func(rr *http.Request) {
rr.URL.Host = r.ProxyURL.Host
rr.URL.Scheme = r.ProxyURL.Scheme
rr.URL.Path = r.ProxyURL.Path
rr.Host = r.ProxyURL.Host
},
}
p.ServeHTTP(w, req)
return
}
// Type: response or none then set the response
// set headers
for k, v := range r.Response.Header {
w.Header().Set(k, v)
}
// set status code
w.WriteHeader(r.Response.StatusCode)
// set response
if len(r.Response.Body) > 0 {
w.Write(r.Response.Body)
} else {
w.Write([]byte(r.Response.Status))
}
}