-
Notifications
You must be signed in to change notification settings - Fork 617
/
parse_new.go
252 lines (221 loc) · 7.5 KB
/
parse_new.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
package route
import (
"bufio"
"bytes"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
var (
reRouteAdd = regexp.MustCompile(`^route\s+add`)
reRouteDel = regexp.MustCompile(`^route\s+del`)
reRouteWeight = regexp.MustCompile(`^route\s+weight`)
reComment = regexp.MustCompile(`^(#|//)`)
reBlankLine = regexp.MustCompile(`^\s*$`)
)
const Commands = `
Route commands can have the following form:
route add <svc> <src> <dst>[ weight <w>][ tags "<t1>,<t2>,..."][ opts "k1=v1 k2=v2 ..."]
- Add route for service svc from src to dst with optional weight, tags and options.
Valid options are:
strip=/path : forward '/path/to/file' as '/to/file'
prepend=/prefix : forward '/path/to/file' as '/prefix/path/to/file'
proto=tcp : upstream service is TCP, dst is ':port'
proto=https : upstream service is HTTPS
tlsskipverify=true : disable TLS cert validation for HTTPS upstream
host=name : set the Host header to 'name'. If 'name == "dst"' then the 'Host' header will be set to the registered upstream host name
register=name : register fabio as new service 'name'. Useful for registering hostnames for host specific routes.
auth=name : name of the auth scheme to use (defined in proxy.auth)
route del <svc>[ <src>[ <dst>]]
- Remove route matching svc, src and/or dst
route del <svc> tags "<t1>,<t2>,..."
- Remove all routes of service matching svc and tags
route del tags "<t1>,<t2>,..."
- Remove all routes matching tags
route weight <svc> <src> weight <w> tags "<t1>,<t2>,..."
- Route w% of traffic to all services matching svc, src and tags
route weight <src> weight <w> tags "<t1>,<t2>,..."
- Route w% of traffic to all services matching src and tags
route weight <svc> <src> weight <w>
- Route w% of traffic to all services matching svc and src
route weight service host/path weight w tags "tag1,tag2"
- Route w% of traffic to all services matching service, host/path and tags
w is a float > 0 describing a percentage, e.g. 0.5 == 50%
w <= 0: means no fixed weighting. Traffic is evenly distributed
w > 0: route will receive n% of traffic. If sum(w) > 1 then w is normalized.
sum(w) >= 1: only matching services will receive traffic
Note that the total sum of traffic sent to all matching routes is w%.
`
// Parse loads a routing table from a set of route commands.
//
// The commands are parsed in order and order matters.
// Deleting a route that has not been created yet yields
// a different result than the other way around.
func Parse(in *bytes.Buffer) (defs []*RouteDef, err error) {
var def *RouteDef
var i int
scanner := bufio.NewScanner(in)
for scanner.Scan() {
def, err = nil, nil
result := strings.TrimSpace(scanner.Text())
i++
switch {
case reComment.MatchString(result) || reBlankLine.MatchString(result):
continue
case reRouteAdd.MatchString(result):
def, err = parseRouteAdd(result)
case reRouteDel.MatchString(result):
def, err = parseRouteDel(result)
case reRouteWeight.MatchString(result):
def, err = parseRouteWeight(result)
default:
err = errors.New("syntax error: 'route' expected")
}
if err != nil {
return nil, fmt.Errorf("line %d: %s", i, err)
}
defs = append(defs, def)
}
return defs, nil
}
// ParseAliases scans a set of route commands for the "register" option and
// returns a list of services which should be registered by the backend.
func ParseAliases(in string) (names []string, err error) {
var defs []*RouteDef
var def *RouteDef
for i, s := range strings.Split(in, "\n") {
def, err = nil, nil
s = strings.TrimSpace(s)
switch {
case reComment.MatchString(s) || reBlankLine.MatchString(s):
continue
case reRouteAdd.MatchString(s):
def, err = parseRouteAdd(s)
case reRouteDel.MatchString(s):
def, err = parseRouteDel(s)
case reRouteWeight.MatchString(s):
def, err = parseRouteWeight(s)
default:
err = errors.New("syntax error: 'route' expected")
}
if err != nil {
return nil, fmt.Errorf("line %d: %s", i+1, err)
}
defs = append(defs, def)
}
var aliases []string
for _, d := range defs {
registerName, ok := d.Opts["register"]
if ok {
aliases = append(aliases, registerName)
}
}
return aliases, nil
}
// route add <svc> <src> <dst>[ weight <w>][ tags "<t1>,<t2>,..."][ opts "k=v k=v ..."]
// 1: service 2: src 3: dst 4: weight expr 5: weight val 6: tags expr 7: tags val 8: opts expr 9: opts val
var reAdd = mustCompileWithFlexibleSpace(`^route add (\S+) (\S+) (\S+)( weight (\S+))?( tags "([^"]*)")?( opts "([^"]*)")?$`)
func parseRouteAdd(s string) (*RouteDef, error) {
if m := reAdd.FindStringSubmatch(s); m != nil {
w, err := parseWeight(m[5])
return &RouteDef{
Cmd: RouteAddCmd,
Service: m[1],
Src: m[2],
Dst: m[3],
Weight: w,
Tags: parseTags(m[7]),
Opts: parseOpts(m[9]),
}, err
}
return nil, errors.New("syntax error: 'route add' invalid")
}
// route del <svc>[ <src>][ <dst>]
// 1: service 2: src expr 3: src 4: dst expr 5: dst
var reDel = mustCompileWithFlexibleSpace(`^route del (\S+)( (\S+)( (\S+))?)?$`)
// route del <svc> tags "<t1>,<t2>,..."
// 1: service 2: tags
var reDelSvcTags = mustCompileWithFlexibleSpace(`^route del (\S+) tags "([^"]*)"$`)
// route del tags "<t1>,<t2>,..."
// 2: tags
var reDelTags = mustCompileWithFlexibleSpace(`^route del tags "([^"]*)"$`)
func parseRouteDel(s string) (*RouteDef, error) {
if m := reDelSvcTags.FindStringSubmatch(s); m != nil {
return &RouteDef{Cmd: RouteDelCmd, Service: m[1], Tags: parseTags(m[2])}, nil
}
if m := reDelTags.FindStringSubmatch(s); m != nil {
return &RouteDef{Cmd: RouteDelCmd, Tags: parseTags(m[1])}, nil
}
if m := reDel.FindStringSubmatch(s); m != nil {
return &RouteDef{Cmd: RouteDelCmd, Service: m[1], Src: m[3], Dst: m[5]}, nil
}
return nil, errors.New("syntax error: 'route del' invalid")
}
// route weight <svc> <src> weight <w>[ tags "<t1>,<t2>,..."]
// 1: service 2: src 3: weight val 4: tags expr 5: tags val
var reWeightSvc = mustCompileWithFlexibleSpace(`^route weight (\S+) (\S+) weight (\S+)( tags "([^"]*)")?$`)
// route weight <src> weight <w> tags "<t1>,<t2>,..."
// 1: src 2: weight val 3: tags val
var reWeightSrc = mustCompileWithFlexibleSpace(`^route weight (\S+) weight (\S+) tags "([^"]*)"$`)
func parseRouteWeight(s string) (*RouteDef, error) {
if m := reWeightSvc.FindStringSubmatch(s); m != nil {
w, err := parseWeight(m[3])
return &RouteDef{
Cmd: RouteWeightCmd,
Service: m[1],
Src: m[2],
Weight: w,
Tags: parseTags(m[5]),
}, err
}
if m := reWeightSrc.FindStringSubmatch(s); m != nil {
w, err := parseWeight(m[2])
return &RouteDef{
Cmd: RouteWeightCmd,
Src: m[1],
Weight: w,
Tags: parseTags(m[3]),
}, err
}
return nil, errors.New("syntax error: 'route weight' invalid")
}
func mustCompileWithFlexibleSpace(re string) *regexp.Regexp {
return regexp.MustCompile(strings.Replace(re, " ", "\\s+", -1))
}
func parseWeight(s string) (float64, error) {
if s == "" {
return 0, nil
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0.0, errors.New("syntax error: weight value invalid")
}
return f, nil
}
func parseTags(s string) []string {
if s == "" {
return nil
}
tags := strings.Split(s, ",")
for i, t := range tags {
tags[i] = strings.TrimSpace(t)
}
return tags
}
func parseOpts(s string) map[string]string {
if s == "" {
return nil
}
m := make(map[string]string)
for _, f := range strings.Fields(s) {
p := strings.SplitN(f, "=", 2)
if len(p) == 1 {
m[f] = ""
} else {
m[p[0]] = p[1]
}
}
return m
}