github.com/vicanso/pike@v1.0.1-0.20210630235453-9099e041f6ec/config/config.go (about)

     1  // MIT License
     2  
     3  // Copyright (c) 2020 Tree Xie
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in all
    13  // copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    21  // SOFTWARE.
    22  
    23  package config
    24  
    25  import (
    26  	"errors"
    27  	"strings"
    28  
    29  	"github.com/vicanso/pike/app"
    30  	"github.com/vicanso/pike/log"
    31  	"go.uber.org/zap"
    32  	"gopkg.in/yaml.v2"
    33  )
    34  
    35  type (
    36  	// Client client interface
    37  	Client interface {
    38  		// Get get the data of key
    39  		Get() (data []byte, err error)
    40  		// Set set the data of key
    41  		Set(data []byte) (err error)
    42  		// Watch watch change
    43  		Watch(OnChange)
    44  		// Close close client
    45  		Close() error
    46  	}
    47  	OnChange func()
    48  
    49  	// PikeConfig pike config
    50  	PikeConfig struct {
    51  		// YAML 界面展示之用,不需要保存
    52  		YAML string `json:"yaml,omitempty" yaml:"-"`
    53  		// Version 程序版本
    54  		Version    string           `json:"version,omitempty" yaml:"version,omitempty" `
    55  		Admin      AdminConfig      `json:"admin,omitempty" yaml:"admin,omitempty" validate:"omitempty,dive"`
    56  		Compresses []CompressConfig `json:"compresses,omitempty" yaml:"compresses,omitempty" validate:"omitempty,dive"`
    57  		Caches     []CacheConfig    `json:"caches,omitempty" yaml:"caches,omitempty" validate:"omitempty,dive"`
    58  		Upstreams  []UpstreamConfig `json:"upstreams,omitempty" yaml:"upstreams,omitempty" validate:"omitempty,dive"`
    59  		Locations  []LocationConfig `json:"locations,omitempty" yaml:"locations,omitempty" validate:"omitempty,dive"`
    60  		Servers    []ServerConfig   `json:"servers,omitempty" yaml:"servers,omitempty" validate:"omitempty,dive"`
    61  	}
    62  	// AdminConfig admin config
    63  	AdminConfig struct {
    64  		User     string `json:"user,omitempty" yaml:"user,omitempty" validate:"omitempty,min=3"`
    65  		Password string `json:"password,omitempty" yaml:"password,omitempty" validate:"omitempty,min=6"`
    66  		Remark   string `json:"remark,omitempty" yaml:"remark,omitempty"`
    67  	}
    68  	// CompressConfig compress config
    69  	CompressConfig struct {
    70  		Name   string          `json:"name,omitempty" yaml:"name,omitempty" validate:"required,xName"`
    71  		Levels map[string]uint `json:"levels,omitempty" yaml:"levels,omitempty"`
    72  		Remark string          `json:"remark,omitempty" yaml:"remark,omitempty"`
    73  	}
    74  	// CacheConfig cache config
    75  	CacheConfig struct {
    76  		Name       string `json:"name,omitempty" yaml:"name,omitempty" validate:"required,xName"`
    77  		Size       int    `json:"size,omitempty" yaml:"size,omitempty" validate:"required,gt=0" `
    78  		HitForPass string `json:"hitForPass,omitempty" yaml:"hitForPass,omitempty" validate:"required,xDuration"`
    79  		Store      string `json:"store,omitempty" yaml:"store,omitempty" validate:"omitempty,url"`
    80  		Remark     string `json:"remark,omitempty" yaml:"remark,omitempty"`
    81  	}
    82  	// UpstreamServerConfig upstream server config
    83  	UpstreamServerConfig struct {
    84  		Addr   string `json:"addr,omitempty" yaml:"addr,omitempty" validate:"required,xAddr"`
    85  		Backup bool   `json:"backup,omitempty" yaml:"backup,omitempty"`
    86  		// Healthy 界面展示使用,不需要保存
    87  		Healthy bool `json:"healthy,omitempty" yaml:"-"`
    88  	}
    89  	// UpstreamConfig upstream config
    90  	UpstreamConfig struct {
    91  		Name           string                 `json:"name,omitempty" yaml:"name,omitempty" validate:"required,xName"`
    92  		HealthCheck    string                 `json:"healthCheck,omitempty" yaml:"healthCheck,omitempty" validate:"omitempty,xURLPath"`
    93  		Policy         string                 `json:"policy,omitempty" yaml:"policy,omitempty" validate:"omitempty,xPolicy"`
    94  		EnableH2C      bool                   `json:"enableH2C,omitempty" yaml:"enableH2C,omitempty"`
    95  		AcceptEncoding string                 `json:"acceptEncoding,omitempty" yaml:"acceptEncoding,omitempty" validate:"omitempty,ascii"`
    96  		Servers        []UpstreamServerConfig `json:"servers,omitempty" yaml:"servers,omitempty" validate:"required,dive"`
    97  		Remark         string                 `json:"remark,omitempty" yaml:"remark,omitempty"`
    98  	}
    99  	// LocationConfig location config
   100  	LocationConfig struct {
   101  		Name         string   `json:"name,omitempty" yaml:"name,omitempty" validate:"required,xName"`
   102  		Upstream     string   `json:"upstream,omitempty" yaml:"upstream,omitempty" validate:"required,xName"`
   103  		Prefixes     []string `json:"prefixes,omitempty" yaml:"prefixes,omitempty" validate:"omitempty,dive,xURLPath"`
   104  		Rewrites     []string `json:"rewrites,omitempty" yaml:"rewrites,omitempty" validate:"omitempty,dive,xDivide"`
   105  		QueryStrings []string `json:"queryStrings,omitempty" yaml:"queryStrings,omitempty" validate:"omitempty,dive,xDivide"`
   106  		RespHeaders  []string `json:"respHeaders,omitempty" yaml:"respHeaders,omitempty" validate:"omitempty,dive,xDivide"`
   107  		ReqHeaders   []string `json:"reqHeaders,omitempty" yaml:"reqHeaders,omitempty" validate:"omitempty,dive,xDivide"`
   108  		Hosts        []string `json:"hosts,omitempty" yaml:"hosts,omitempty" validate:"omitempty,dive,hostname"`
   109  		ProxyTimeout string   `json:"proxyTimeout,omitempty" yaml:"proxyTimeout,omitempty" validate:"omitempty,xDuration"`
   110  		Remark       string   `json:"remark,omitempty" yaml:"remark,omitempty"`
   111  	}
   112  	// ServerConfig server config
   113  	ServerConfig struct {
   114  		LogFormat string   `json:"logFormat,omitempty" yaml:"logFormat,omitempty"`
   115  		Addr      string   `json:"addr,omitempty" yaml:"addr,omitempty" validate:"required,ascii"`
   116  		Locations []string `json:"locations,omitempty" yaml:"locations,omitempty" validate:"required,dive,xName"`
   117  		Cache     string   `json:"cache,omitempty" yaml:"cache,omitempty" validate:"required,xName"`
   118  		Compress  string   `json:"compress,omitempty" yaml:"compress,omitempty" validate:"omitempty"`
   119  		// 最小压缩长度
   120  		CompressMinLength string `json:"compressMinLength,omitempty" yaml:"compressMinLength,omitempty" validate:"omitempty,xSize"`
   121  		// 压缩数据类型
   122  		CompressContentTypeFilter string `json:"compressContentTypeFilter,omitempty" yaml:"compressContentTypeFilter,omitempty" validate:"omitempty,xFilter"`
   123  		Remark                    string `json:"remark,omitempty" yaml:"remark,omitempty"`
   124  	}
   125  )
   126  
   127  var defaultClient Client
   128  
   129  var (
   130  	ErrUpstreamNotFound = errors.New("upstream of location not found")
   131  	ErrLocationNotFound = errors.New("location of server not found")
   132  	ErrCacheNotFound    = errors.New("cache of server not found")
   133  	ErrCompressNotFound = errors.New("compress of server not found")
   134  )
   135  
   136  // InitDefaultClient init default client
   137  func InitDefaultClient(url string) (err error) {
   138  	if defaultClient != nil {
   139  		// 如果关闭出错,仅输出日志
   140  		e := defaultClient.Close()
   141  		if e != nil {
   142  			log.Default().Error("close config client fail",
   143  				zap.Error(e),
   144  			)
   145  		}
   146  	}
   147  	defaultClient = nil
   148  	if strings.HasPrefix(url, "etcd://") {
   149  		c, err := NewEtcdClient(url)
   150  		if err != nil {
   151  			return err
   152  		}
   153  		defaultClient = c
   154  		return nil
   155  	}
   156  
   157  	c, err := NewFileClient(url)
   158  	if err != nil {
   159  		return
   160  	}
   161  	defaultClient = c
   162  	return
   163  }
   164  
   165  func (c *PikeConfig) Validate() error {
   166  	err := defaultValidator.Struct(c)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	// 判断location中设置的upstream是否存在
   171  	for _, l := range c.Locations {
   172  		found := false
   173  		for _, upstream := range c.Upstreams {
   174  			if l.Upstream == upstream.Name {
   175  				found = true
   176  			}
   177  		}
   178  		if !found {
   179  			return ErrUpstreamNotFound
   180  		}
   181  	}
   182  	// 校验server中的location, cache 以及 compress 是否正确设置
   183  	for _, s := range c.Servers {
   184  		for _, item := range s.Locations {
   185  			notFound := true
   186  			for _, l := range c.Locations {
   187  				if item == l.Name {
   188  					notFound = false
   189  				}
   190  			}
   191  			if notFound {
   192  				return ErrLocationNotFound
   193  			}
   194  		}
   195  
   196  		foundCache := s.Cache == ""
   197  		for _, cacheConfig := range c.Caches {
   198  			if cacheConfig.Name == s.Cache {
   199  				foundCache = true
   200  			}
   201  		}
   202  		if !foundCache {
   203  			return ErrCacheNotFound
   204  		}
   205  
   206  		foundCompress := s.Compress == ""
   207  		for _, compressConfig := range c.Compresses {
   208  			if compressConfig.Name == s.Compress {
   209  				foundCompress = true
   210  			}
   211  		}
   212  		if !foundCompress {
   213  			return ErrCompressNotFound
   214  		}
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // GetAdminConfig get admin config
   221  func (p *PikeConfig) GetAdminConfig() AdminConfig {
   222  	return p.Admin
   223  }
   224  
   225  // Read read pike config
   226  func Read() (config *PikeConfig, err error) {
   227  	data, err := defaultClient.Get()
   228  	if err != nil {
   229  		return
   230  	}
   231  	config = &PikeConfig{}
   232  	err = yaml.Unmarshal(data, config)
   233  	if err != nil {
   234  		return
   235  	}
   236  	config.YAML = string(data)
   237  	return
   238  }
   239  
   240  // Write write pike config
   241  func Write(config *PikeConfig) (err error) {
   242  	err = config.Validate()
   243  	if err != nil {
   244  		return
   245  	}
   246  	config.Version = app.GetVersion()
   247  	data, err := yaml.Marshal(config)
   248  	if err != nil {
   249  		return
   250  	}
   251  	return defaultClient.Set(data)
   252  }
   253  
   254  // Close close the client
   255  func Close() (err error) {
   256  	if defaultClient != nil {
   257  		err = defaultClient.Close()
   258  	}
   259  	defaultClient = nil
   260  	return
   261  }
   262  
   263  // Watch watch the change
   264  func Watch(onChange OnChange) {
   265  	defaultClient.Watch(onChange)
   266  }