blob: ed8ab4d5dbd440fd085207d40ff2d00626b7126d [file] [log] [blame]
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
}