| package remap |
| |
| /* |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| "net" |
| "net/http" |
| "net/url" |
| "os" |
| "strings" |
| "time" |
| |
| "github.com/apache/trafficcontrol/grove/chash" |
| "github.com/apache/trafficcontrol/grove/icache" |
| "github.com/apache/trafficcontrol/grove/plugin" |
| "github.com/apache/trafficcontrol/grove/remapdata" |
| "github.com/apache/trafficcontrol/grove/web" |
| |
| "github.com/apache/trafficcontrol/lib/go-log" |
| "github.com/apache/trafficcontrol/lib/go-rfc" |
| ) |
| |
| type HTTPRequestRemapper interface { |
| // Remap returns the remapped request, the matched rule name, whether the requestor's IP is allowed, whether to connection close, whether a match was found, and any error. |
| // Remap(r *http.Request, scheme string, failures int) Remapping |
| Rules() []remapdata.RemapRule |
| RemappingProducer(r *http.Request, scheme string) (*RemappingProducer, error) |
| StatRules() remapdata.RemapRulesStats |
| PluginCfg() map[string]interface{} // global plugins, outside the individual remap rules |
| // PluginSharedCfg returns the plugins_shared, for every remap rule. This gives plugins a chance on startup to precompute data for each remap rule, store it in the Context, and save computation during requests. |
| PluginSharedCfg() map[string]map[string]json.RawMessage |
| } |
| |
| type simpleHTTPRequestRemapper struct { |
| remapper Remapper |
| stats *remapdata.RemapRulesStats |
| } |
| |
| func (hr simpleHTTPRequestRemapper) Rules() []remapdata.RemapRule { return hr.remapper.Rules() } |
| func (hr simpleHTTPRequestRemapper) StatRules() remapdata.RemapRulesStats { return *hr.stats } |
| func (hr simpleHTTPRequestRemapper) PluginCfg() map[string]interface{} { |
| return hr.remapper.PluginCfg() |
| } |
| func (hr simpleHTTPRequestRemapper) PluginSharedCfg() map[string]map[string]json.RawMessage { |
| return hr.remapper.PluginSharedCfg() |
| } |
| |
| // getFQDN returns the FQDN. It tries to get the FQDN from a Remap Rule. Remap Rules should always begin with the scheme, e.g. `http://`. If the given rule does not begin with a valid scheme, behavior is undefined. |
| // TODO test |
| func getFQDN(rule string) string { |
| schemeStr := "://" |
| schemePos := strings.Index(rule, schemeStr) |
| if schemePos == -1 { |
| return rule // invalid rule, doesn't start with a scheme |
| } |
| schemePos += len(schemeStr) |
| rule = rule[schemePos:] |
| |
| slashPos := strings.Index(rule, "/") |
| if slashPos == -1 { |
| return rule // rule is just the scheme+FQDN, perfectly normal |
| } |
| rule = rule[:slashPos] // strip off the path |
| return rule |
| } |
| |
| func NewRemappingTransport(reqTimeout time.Duration, reqKeepAlive time.Duration, reqMaxIdleConns int, reqIdleConnTimeout time.Duration) *http.Transport { |
| return &http.Transport{ |
| DialContext: (&net.Dialer{ |
| Timeout: reqTimeout, |
| KeepAlive: reqKeepAlive, |
| DualStack: true, |
| }).DialContext, |
| MaxIdleConns: reqMaxIdleConns, |
| IdleConnTimeout: reqIdleConnTimeout, |
| TLSHandshakeTimeout: 10 * time.Second, |
| ExpectContinueTimeout: 1 * time.Second, |
| Dial: func(network, address string) (net.Conn, error) { |
| d := net.Dialer{DualStack: true, FallbackDelay: time.Millisecond * 50} |
| return d.Dial(network, address) |
| }, |
| } |
| } |
| |
| type Remapping struct { |
| Request *http.Request |
| ProxyURL *url.URL |
| Name string |
| CacheKey string |
| ConnectionClose bool |
| Timeout time.Duration |
| RetryNum int |
| RetryCodes map[int]struct{} |
| Cache icache.Cache |
| Transport *http.Transport |
| } |
| |
| // RemappingProducer takes an HTTP Request and returns a Remapping to be used for that request. |
| // TODO rename? interface? |
| type RemappingProducer struct { |
| oldURI string |
| rule remapdata.RemapRule |
| cacheKey string |
| failures int |
| } |
| |
| func (p *RemappingProducer) CacheKey() string { return p.cacheKey } |
| func (p *RemappingProducer) OverrideCacheKey(newKey string) { p.cacheKey = newKey } |
| func (p *RemappingProducer) ConnectionClose() bool { return p.rule.ConnectionClose } |
| func (p *RemappingProducer) Name() string { return p.rule.Name } |
| func (p *RemappingProducer) DSCP() int { return p.rule.DSCP } |
| func (p *RemappingProducer) PluginCfg() map[string]interface{} { return p.rule.Plugins } |
| func (p *RemappingProducer) Cache() icache.Cache { return p.rule.Cache } |
| func (p *RemappingProducer) FirstFQDN() string { |
| // TODO verify To is not allowed to be constructed with < 1 element |
| return strings.TrimPrefix(strings.TrimPrefix(p.rule.To[0].URL, "http://"), "https://") |
| } |
| func (p *RemappingProducer) ProxyStr() string { |
| if p.rule.To[0].ProxyURL != nil && p.rule.To[0].ProxyURL.Host != "" { |
| return p.rule.To[0].ProxyURL.Host |
| } |
| return "NONE" // TODO const? |
| } |
| |
| var ErrRuleNotFound = errors.New("remap rule not found") |
| var ErrIPNotAllowed = errors.New("IP not allowed") |
| var ErrNoMoreRetries = errors.New("retry num exceeded") |
| |
| // RequestURI returns the URI of the given request. This must be used, because Go does not populate the scheme of requests that come in from clients. |
| func RequestURI(r *http.Request, scheme string) string { |
| return scheme + "://" + r.Host + r.RequestURI |
| } |
| func (hr simpleHTTPRequestRemapper) RemappingProducer(r *http.Request, scheme string) (*RemappingProducer, error) { |
| uri := RequestURI(r, scheme) |
| rule, ok := hr.remapper.Remap(uri) |
| if !ok { |
| return nil, ErrRuleNotFound |
| } |
| |
| if ip, err := web.GetIP(r); err != nil { |
| return nil, fmt.Errorf("parsing client IP: %v", err) |
| } else if !rule.Allowed(ip) { |
| return nil, ErrIPNotAllowed |
| } else { |
| log.Debugf("Allowed %v\n", ip) |
| } |
| |
| cacheKey := rule.CacheKey(r.Method, uri) |
| |
| return &RemappingProducer{ |
| rule: rule, |
| oldURI: uri, |
| cacheKey: cacheKey, |
| }, nil |
| } |
| |
| // GetNext returns the remapping to use to request, whether retries are allowed (i.e. if this is the last retry), or any error |
| func (p *RemappingProducer) GetNext(r *http.Request) (Remapping, bool, error) { |
| if *p.rule.RetryNum < p.failures { |
| return Remapping{}, false, ErrNoMoreRetries |
| } |
| |
| newURI, proxyURL, transport := p.rule.URI(p.oldURI, r.URL.Path, r.URL.RawQuery, p.failures) |
| p.failures++ |
| newReq, err := http.NewRequest(r.Method, newURI, nil) |
| if err != nil { |
| return Remapping{}, false, fmt.Errorf("creating new request: %v\n", err) |
| } |
| web.CopyHeaderTo(r.Header, &newReq.Header) |
| |
| log.Debugf("GetNext oldUri: %v, Host: %v\n", p.oldURI, newReq.Header.Get("Host")) |
| log.Debugf("GetNext newURI: %v, fqdn: %v\n", newURI, getFQDN(newURI)) |
| log.Debugf("GetNext rule name: %v\n", p.rule.Name) |
| |
| newReq.Header.Set("Host", getFQDN(newURI)) |
| |
| retryAllowed := *p.rule.RetryNum < p.failures |
| return Remapping{ |
| Request: newReq, |
| ProxyURL: proxyURL, |
| Name: p.rule.Name, |
| CacheKey: p.cacheKey, |
| ConnectionClose: p.rule.ConnectionClose, |
| Timeout: *p.rule.Timeout, |
| RetryNum: *p.rule.RetryNum, |
| RetryCodes: p.rule.RetryCodes, |
| Cache: p.rule.Cache, |
| Transport: transport, |
| }, retryAllowed, nil |
| } |
| |
| func RemapperToHTTP(r Remapper, statRules *remapdata.RemapRulesStats) HTTPRequestRemapper { |
| return simpleHTTPRequestRemapper{remapper: r, stats: statRules} |
| } |
| |
| func NewHTTPRequestRemapper(remap []remapdata.RemapRule, plugins map[string]interface{}, statRules *remapdata.RemapRulesStats) HTTPRequestRemapper { |
| return RemapperToHTTP(NewLiteralPrefixRemapper(remap, plugins), statRules) |
| } |
| |
| // Remapper provides a function which takes strings and maps them to other strings. This is designed for URL prefix remapping, for a reverse proxy. |
| type Remapper interface { |
| // Remap returns the given string remapped, the unique name of the rule found, and whether a remap rule was found |
| Remap(uri string) (remapdata.RemapRule, bool) |
| // Rules returns the unique names of every remap rule. |
| Rules() []remapdata.RemapRule |
| // PluginCfg returns the global plugins, outside the individual remap rules |
| PluginCfg() map[string]interface{} |
| PluginSharedCfg() map[string]map[string]json.RawMessage |
| } |
| |
| // TODO change to use a prefix tree, for speed |
| type literalPrefixRemapper struct { |
| remap []remapdata.RemapRule |
| plugins map[string]interface{} |
| } |
| |
| func (r literalPrefixRemapper) PluginCfg() map[string]interface{} { return r.plugins } |
| |
| // PluginSharedCfg returns a map of remap rule names, to a map of keys to arbitrary JSON values. |
| // For example, if a JSON rule object with the name "foo" contains the key and value `"plugins_shared": {"bar": "baz"}`, the returned map will contain m["foo"]["bar"]"baz". The value may be any JSON type. |
| func (r literalPrefixRemapper) PluginSharedCfg() map[string]map[string]json.RawMessage { |
| rules := r.Rules() |
| cfg := make(map[string]map[string]json.RawMessage, len(rules)) |
| for _, rule := range rules { |
| cfg[rule.Name] = rule.PluginsShared |
| } |
| return cfg |
| } |
| |
| // Remap returns the remapped string, the remap rule name, the remap rule's options, and whether a remap was found |
| func (r literalPrefixRemapper) Remap(s string) (remapdata.RemapRule, bool) { |
| for _, rule := range r.remap { |
| if strings.HasPrefix(s, rule.From) { |
| return rule, true |
| } |
| } |
| return remapdata.RemapRule{}, false |
| } |
| |
| func (r literalPrefixRemapper) Rules() []remapdata.RemapRule { |
| rules := make([]remapdata.RemapRule, len(r.remap)) |
| for _, rule := range r.remap { |
| rules = append(rules, rule) |
| } |
| return rules |
| } |
| |
| func NewLiteralPrefixRemapper(remap []remapdata.RemapRule, plugins map[string]interface{}) Remapper { |
| return literalPrefixRemapper{remap: remap, plugins: plugins} |
| } |
| |
| type RemapRulesStatsJSON struct { |
| Allow []string `json:"allow"` |
| Deny []string `json:"deny"` |
| } |
| |
| type RemapRulesBase struct { |
| RetryNum *int `json:"retry_num"` |
| PluginsShared map[string]json.RawMessage `json:"plugins_shared"` |
| } |
| |
| type RemapRulesJSON struct { |
| RemapRulesBase |
| Rules []RemapRuleJSON `json:"rules"` |
| RetryCodes *[]int `json:"retry_codes"` |
| TimeoutMS *int `json:"timeout_ms"` |
| ParentSelection *string `json:"parent_selection"` |
| Stats RemapRulesStatsJSON `json:"stats"` |
| Plugins map[string]json.RawMessage `json:"plugins"` |
| } |
| |
| type RemapRules struct { |
| RemapRulesBase |
| Rules []remapdata.RemapRule |
| RetryCodes map[int]struct{} |
| Timeout *time.Duration |
| ParentSelection *remapdata.ParentSelectionType |
| Stats remapdata.RemapRulesStats |
| Plugins map[string]interface{} |
| Cache icache.Cache |
| } |
| |
| type RemapRuleToJSON struct { |
| remapdata.RemapRuleToBase |
| ProxyURL *string `json:"proxy_url"` |
| TimeoutMS *int `json:"timeout_ms"` |
| RetryCodes *[]int `json:"retry_codes"` |
| } |
| |
| type HdrModder interface { |
| Mod(h *http.Header) |
| } |
| |
| type RemapRuleJSON struct { |
| remapdata.RemapRuleBase |
| TimeoutMS *int `json:"timeout_ms"` |
| ParentSelection *string `json:"parent_selection"` |
| To []RemapRuleToJSON `json:"to"` |
| Allow []string `json:"allow"` |
| Deny []string `json:"deny"` |
| RetryCodes *[]int `json:"retry_codes"` |
| CacheName *string `json:"cache_name"` |
| Plugins map[string]json.RawMessage `json:"plugins"` |
| } |
| |
| // LoadRemapRules returns the loaded rules, the global plugins, the Stats remap rules, and any error |
| func LoadRemapRules(path string, pluginConfigLoaders map[string]plugin.LoadFunc, caches map[string]icache.Cache, baseTransport *http.Transport) ([]remapdata.RemapRule, map[string]interface{}, *remapdata.RemapRulesStats, error) { |
| fmt.Println(time.Now().Format(time.RFC3339Nano) + " Loading Remap Rules") |
| defer func() { |
| fmt.Println(time.Now().Format(time.RFC3339Nano) + " Loaded Remap Rules") |
| }() |
| file, err := os.Open(path) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| defer file.Close() |
| |
| remapRulesJSON := RemapRulesJSON{} |
| if err := json.NewDecoder(file).Decode(&remapRulesJSON); err != nil { |
| return nil, nil, nil, fmt.Errorf("decoding JSON: %s", err) |
| } |
| |
| remapRules := RemapRules{RemapRulesBase: remapRulesJSON.RemapRulesBase} |
| |
| if remapRulesJSON.RetryCodes != nil { |
| remapRules.RetryCodes = make(map[int]struct{}, len(*remapRulesJSON.RetryCodes)) |
| for _, code := range *remapRulesJSON.RetryCodes { |
| if _, ok := rfc.ValidHTTPCodes[code]; !ok { |
| return nil, nil, nil, fmt.Errorf("error parsing rules: retry code invalid: %v", code) |
| } |
| remapRules.RetryCodes[code] = struct{}{} |
| } |
| } |
| if remapRulesJSON.TimeoutMS != nil { |
| t := time.Duration(*remapRulesJSON.TimeoutMS) * time.Millisecond |
| if remapRules.Timeout = &t; *remapRules.Timeout < 0 { |
| return nil, nil, nil, fmt.Errorf("error parsing rules: timeout must be positive: %v", remapRules.Timeout) |
| } |
| } |
| if remapRulesJSON.ParentSelection != nil { |
| ps := remapdata.ParentSelectionTypeFromString(*remapRulesJSON.ParentSelection) |
| if remapRules.ParentSelection = &ps; *remapRules.ParentSelection == remapdata.ParentSelectionTypeInvalid { |
| return nil, nil, nil, fmt.Errorf("error parsing rules: parent selection invalid: '%v'", remapRulesJSON.ParentSelection) |
| } |
| } |
| if remapRulesJSON.Stats.Allow != nil { |
| if remapRules.Stats.Allow, err = makeIPNets(remapRulesJSON.Stats.Allow); err != nil { |
| return nil, nil, nil, fmt.Errorf("error parsing rules allows: %v", err) |
| } |
| } |
| if remapRulesJSON.Stats.Deny != nil { |
| if remapRules.Stats.Deny, err = makeIPNets(remapRulesJSON.Stats.Deny); err != nil { |
| return nil, nil, nil, fmt.Errorf("error parsing rules denys: %v", err) |
| } |
| } |
| |
| remapRules.Plugins = make(map[string]interface{}, len(remapRulesJSON.Plugins)) |
| for name, b := range remapRulesJSON.Plugins { |
| if loadF := pluginConfigLoaders[name]; loadF != nil { |
| remapRules.Plugins[name] = loadF(b) |
| } |
| } |
| |
| rules := make([]remapdata.RemapRule, len(remapRulesJSON.Rules)) |
| for i, jsonRule := range remapRulesJSON.Rules { |
| fmt.Println(time.Now().Format(time.RFC3339Nano) + " Creating Remap Rule " + jsonRule.Name) |
| rule := remapdata.RemapRule{RemapRuleBase: jsonRule.RemapRuleBase} |
| |
| rule.Plugins = make(map[string]interface{}, len(jsonRule.Plugins)) |
| for name, b := range jsonRule.Plugins { |
| if loadF := pluginConfigLoaders[name]; loadF != nil { |
| rule.Plugins[name] = loadF(b) |
| } |
| } |
| for name, loader := range remapRules.Plugins { |
| if _, ok := rule.Plugins[name]; !ok { |
| rule.Plugins[name] = loader |
| } |
| } |
| |
| if jsonRule.RetryCodes != nil { |
| rule.RetryCodes = make(map[int]struct{}, len(*jsonRule.RetryCodes)) |
| for _, code := range *jsonRule.RetryCodes { |
| if _, ok := rfc.ValidHTTPCodes[code]; !ok { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v retry code invalid: %v", rule.Name, code) |
| } |
| rule.RetryCodes[code] = struct{}{} |
| } |
| } else { |
| rule.RetryCodes = remapRules.RetryCodes |
| } |
| |
| if jsonRule.TimeoutMS != nil { |
| t := time.Duration(*jsonRule.TimeoutMS) * time.Millisecond |
| if rule.Timeout = &t; *rule.Timeout < 0 { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v timeout must be positive: %v", rule.Name, rule.Timeout) |
| } |
| } else { |
| rule.Timeout = remapRules.Timeout |
| } |
| |
| if rule.RetryNum == nil { |
| rule.RetryNum = remapRules.RetryNum |
| } |
| |
| if rule.PluginsShared == nil { |
| rule.PluginsShared = remapRules.PluginsShared |
| } |
| |
| cacheName := "" // default string is the default cache |
| if jsonRule.CacheName != nil { |
| cacheName = *jsonRule.CacheName |
| } |
| ok := false |
| if rule.Cache, ok = caches[cacheName]; !ok { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v: cache name %v not found", rule.Name, cacheName) |
| } |
| |
| if rule.Allow, err = makeIPNets(jsonRule.Allow); err != nil { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v allows: %v", rule.Name, err) |
| } |
| if rule.Deny, err = makeIPNets(jsonRule.Deny); err != nil { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v denys: %v", rule.Name, err) |
| } |
| if rule.To, err = makeTo(jsonRule.To, rule, baseTransport); err != nil { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v to: %v", rule.Name, err) |
| } |
| if jsonRule.ParentSelection != nil { |
| ps := remapdata.ParentSelectionTypeFromString(*jsonRule.ParentSelection) |
| if rule.ParentSelection = &ps; *rule.ParentSelection == remapdata.ParentSelectionTypeInvalid { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v parent selection invalid: '%v'", rule.Name, jsonRule.ParentSelection) |
| } |
| } else { |
| rule.ParentSelection = remapRules.ParentSelection |
| } |
| |
| if rule.ParentSelection == nil { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v - no parent_selection - must be set at rules or rule level", rule.Name) |
| } |
| |
| if len(rule.To) == 0 { |
| return nil, nil, nil, fmt.Errorf("error parsing rule %v - no to - must have at least one parent", rule.Name) |
| } |
| |
| if *rule.ParentSelection == remapdata.ParentSelectionTypeConsistentHash { |
| rule.ConsistentHash = makeRuleHash(rule) |
| } else { |
| } |
| rules[i] = rule |
| } |
| |
| return rules, remapRules.Plugins, &remapRules.Stats, nil |
| } |
| |
| const DefaultReplicas = 1024 |
| |
| func makeRuleHash(rule remapdata.RemapRule) chash.ATSConsistentHash { |
| h := chash.NewSimpleATSConsistentHash(DefaultReplicas) |
| for _, to := range rule.To { |
| h.Insert(&chash.ATSConsistentHashNode{Name: to.URL, ProxyURL: to.ProxyURL, Transport: to.Transport}, *to.Weight) |
| } |
| if h.First() == nil { |
| fmt.Println(time.Now().Format(time.RFC3339Nano) + " ERROR makeRuleHash " + rule.Name + " NodeMap empty!") |
| } |
| |
| return h |
| } |
| |
| func makeTo(tosJSON []RemapRuleToJSON, rule remapdata.RemapRule, baseTransport *http.Transport) ([]remapdata.RemapRuleTo, error) { |
| tos := make([]remapdata.RemapRuleTo, len(tosJSON)) |
| for i, toJSON := range tosJSON { |
| if toJSON.Weight == nil { |
| w := 1.0 |
| toJSON.Weight = &w |
| } |
| to := remapdata.RemapRuleTo{RemapRuleToBase: toJSON.RemapRuleToBase} |
| |
| to.Transport = baseTransport |
| if toJSON.ProxyURL != nil { |
| proxyURL, err := url.Parse(*toJSON.ProxyURL) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing to %v proxy_url: %v", to.URL, toJSON.ProxyURL) |
| } |
| to.ProxyURL = proxyURL |
| // See b7953cc239 for the reasoning behind copying the Transport. |
| newTransport := *baseTransport.Clone() |
| if proxyURL != nil && proxyURL.Host != "" { |
| newTransport.Proxy = http.ProxyURL(proxyURL) |
| } |
| to.Transport = &newTransport |
| } |
| |
| if toJSON.TimeoutMS != nil { |
| t := time.Duration(*toJSON.TimeoutMS) * time.Millisecond |
| if to.Timeout = &t; *to.Timeout < 0 { |
| return nil, fmt.Errorf("error parsing to %v timeout must be positive: %v", to.URL, to.Timeout) |
| } |
| } else { |
| to.Timeout = rule.Timeout |
| } |
| if toJSON.RetryCodes != nil { |
| to.RetryCodes = make(map[int]struct{}, len(*toJSON.RetryCodes)) |
| for _, code := range *toJSON.RetryCodes { |
| if _, ok := rfc.ValidHTTPCodes[code]; !ok { |
| return nil, fmt.Errorf("error parsing to %v retry code invalid: %v", to.URL, code) |
| } |
| to.RetryCodes[code] = struct{}{} |
| } |
| } else { |
| to.RetryCodes = rule.RetryCodes |
| } |
| if to.RetryNum == nil { |
| to.RetryNum = rule.RetryNum |
| } |
| if to.RetryNum == nil { |
| return nil, fmt.Errorf("error parsing to %v - no retry_num - must be set at rules, rule, or to level", to.URL) |
| } else if to.Timeout == nil { |
| return nil, fmt.Errorf("error parsing to %v - no timeout_ms - must be set at rules, rule, or to level", to.URL) |
| } else if to.RetryCodes == nil { |
| return nil, fmt.Errorf("error parsing to %v - no retry_codes - must be set at rules, rule, or to level", to.URL) |
| } |
| tos[i] = to |
| } |
| return tos, nil |
| } |
| |
| func makeIPNets(netStrs []string) ([]*net.IPNet, error) { |
| nets := make([]*net.IPNet, 0, len(netStrs)) |
| for _, netStr := range netStrs { |
| netStr = strings.TrimSpace(netStr) |
| if netStr == "" { |
| continue |
| } |
| net, err := makeIPNet(netStr) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing CIDR %v: %v", netStr, err) |
| } |
| nets = append(nets, net) |
| } |
| return nets, nil |
| } |
| |
| func makeIPNet(cidr string) (*net.IPNet, error) { |
| _, cidrnet, err := net.ParseCIDR(cidr) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing CIDR '%s': %v", cidr, err) |
| } |
| return cidrnet, nil |
| } |
| |
| func LoadRemapper(path string, pluginConfigLoaders map[string]plugin.LoadFunc, caches map[string]icache.Cache, baseTransport *http.Transport) (HTTPRequestRemapper, error) { |
| rules, plugins, statRules, err := LoadRemapRules(path, pluginConfigLoaders, caches, baseTransport) |
| if err != nil { |
| return nil, err |
| } |
| return NewHTTPRequestRemapper(rules, plugins, statRules), nil |
| } |
| |
| func RemapRulesToJSON(r RemapRules) (RemapRulesJSON, error) { |
| j := RemapRulesJSON{RemapRulesBase: r.RemapRulesBase} |
| if r.Timeout != nil { |
| i := int(0) |
| j.TimeoutMS = &i |
| *j.TimeoutMS = int(*r.Timeout / time.Millisecond) |
| } |
| if len(r.RetryCodes) > 0 { |
| rcs := []int{} |
| j.RetryCodes = &rcs |
| for code := range r.RetryCodes { |
| *j.RetryCodes = append(*j.RetryCodes, code) |
| } |
| } |
| if r.ParentSelection != nil { |
| s := "" |
| j.ParentSelection = &s |
| *j.ParentSelection = string(*r.ParentSelection) |
| } |
| for _, deny := range r.Stats.Deny { |
| j.Stats.Deny = append(j.Stats.Deny, deny.String()) |
| } |
| for _, allow := range r.Stats.Allow { |
| j.Stats.Allow = append(j.Stats.Allow, allow.String()) |
| } |
| |
| for _, rule := range r.Rules { |
| j.Rules = append(j.Rules, buildRemapRuleToJSON(rule)) |
| } |
| j.Plugins = make(map[string]json.RawMessage) |
| for name, plugin := range r.Plugins { |
| bts, err := json.Marshal(plugin) |
| if err != nil { |
| return RemapRulesJSON{}, errors.New("error marshalling plugin '" + name + "': " + err.Error()) |
| } |
| j.Plugins[name] = bts |
| } |
| return j, nil |
| } |
| |
| func buildRemapRuleToJSON(r remapdata.RemapRule) RemapRuleJSON { |
| j := RemapRuleJSON{RemapRuleBase: r.RemapRuleBase} |
| if r.Timeout != nil { |
| t := int(0) |
| j.TimeoutMS = &t |
| *j.TimeoutMS = int(*r.Timeout / time.Millisecond) |
| } |
| if r.ParentSelection != nil { |
| ps := "" |
| j.ParentSelection = &ps |
| *j.ParentSelection = string(*r.ParentSelection) |
| } |
| for _, to := range r.To { |
| j.To = append(j.To, RemapRuleToToJSON(to)) |
| } |
| for _, deny := range r.Deny { |
| j.Deny = append(j.Deny, deny.String()) |
| } |
| for _, allow := range r.Allow { |
| j.Allow = append(j.Allow, allow.String()) |
| } |
| if r.RetryCodes != nil { |
| rc := []int{} |
| j.RetryCodes = &rc |
| for retryCode := range r.RetryCodes { |
| *j.RetryCodes = append(*j.RetryCodes, retryCode) |
| } |
| } |
| j.Plugins = make(map[string]json.RawMessage) |
| for name, plugin := range r.Plugins { |
| clientHeadersJSONBytes, _ := json.Marshal(plugin) |
| j.Plugins[name] = clientHeadersJSONBytes |
| } |
| return j |
| } |
| |
| func RemapRuleToToJSON(r remapdata.RemapRuleTo) RemapRuleToJSON { |
| j := RemapRuleToJSON{RemapRuleToBase: r.RemapRuleToBase} |
| if r.ProxyURL != nil { |
| s := "" |
| j.ProxyURL = &s |
| *j.ProxyURL = r.ProxyURL.String() |
| } |
| if r.Timeout != nil { |
| t := int(0) |
| j.TimeoutMS = &t |
| *j.TimeoutMS = int(*r.Timeout / time.Millisecond) |
| } |
| if r.RetryCodes != nil { |
| rc := []int{} |
| j.RetryCodes = &rc |
| for retryCode := range r.RetryCodes { |
| *j.RetryCodes = append(*j.RetryCodes, retryCode) |
| } |
| } |
| return j |
| } |