github.com/vicanso/pike@v1.0.1-0.20210630235453-9099e041f6ec/server/admin.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 server
    24  
    25  import (
    26  	"bytes"
    27  	"encoding/json"
    28  	"net/http"
    29  	"time"
    30  
    31  	"github.com/vicanso/elton"
    32  	jwt "github.com/vicanso/elton-jwt"
    33  	"github.com/vicanso/elton/middleware"
    34  	"github.com/vicanso/pike/app"
    35  	"github.com/vicanso/pike/asset"
    36  	"github.com/vicanso/pike/cache"
    37  	"github.com/vicanso/pike/config"
    38  	"github.com/vicanso/pike/log"
    39  	"github.com/vicanso/pike/upstream"
    40  	"github.com/vicanso/pike/util"
    41  	"go.uber.org/zap"
    42  	"gopkg.in/yaml.v2"
    43  )
    44  
    45  type (
    46  	AdminServerConfig struct {
    47  		Addr     string
    48  		User     string
    49  		Password string
    50  		Prefix   string
    51  	}
    52  	loginParams struct {
    53  		Account  string `json:"account,omitempty"`
    54  		Password string `json:"password,omitempty"`
    55  	}
    56  	userInfo struct {
    57  		Account string `json:"account,omitempty"`
    58  	}
    59  
    60  	// applicationInfo application info
    61  	applicationInfo struct {
    62  		*app.Info
    63  		Processing map[string]int32 `json:"processing,omitempty"`
    64  	}
    65  )
    66  
    67  var userNotLogin = util.NewError("Please login first", http.StatusUnauthorized)
    68  
    69  var accountOrPasswordIsWrong = util.NewError("Account or password is wrong", http.StatusBadRequest)
    70  
    71  var cacheKeyIsNil = util.NewError("The key of cache can't be null", http.StatusBadRequest)
    72  
    73  const jwtCookie = "pike"
    74  
    75  var webAsset = middleware.NewEmbedStaticFS(asset.GetFS(), "web")
    76  
    77  func sendFile(c *elton.Context, file string) (err error) {
    78  	data, err := webAsset.Get(file)
    79  	if err != nil {
    80  		return
    81  	}
    82  	c.SetContentTypeByExt(file)
    83  	c.CacheMaxAge(5 * time.Minute)
    84  	c.BodyBuffer = bytes.NewBuffer(data)
    85  	return
    86  }
    87  
    88  func getUserAccount(c *elton.Context) string {
    89  	data := c.GetString(jwt.DefaultKey)
    90  	params := userInfo{}
    91  	_ = json.Unmarshal([]byte(data), &params)
    92  
    93  	return params.Account
    94  }
    95  
    96  func newIsLoginHandler(user string) elton.Handler {
    97  	return func(c *elton.Context) error {
    98  		account := getUserAccount(c)
    99  		if account == "" {
   100  			return userNotLogin
   101  		}
   102  		return c.Next()
   103  	}
   104  }
   105  
   106  func newLoginHandler(ttlToken *jwt.TTLToken, account, password string) elton.Handler {
   107  	return func(c *elton.Context) (err error) {
   108  		params := loginParams{}
   109  		err = json.Unmarshal(c.RequestBody, &params)
   110  		if err != nil {
   111  			return
   112  		}
   113  		if params.Account != account || params.Password != password {
   114  			err = accountOrPasswordIsWrong
   115  			return
   116  		}
   117  		data, _ := json.Marshal(&userInfo{
   118  			Account: account,
   119  		})
   120  		c.Set(jwt.DefaultKey, string(data))
   121  
   122  		c.Body = &userInfo{
   123  			Account: account,
   124  		}
   125  		return
   126  	}
   127  }
   128  
   129  func newUserMeHandler(user string) elton.Handler {
   130  	return func(c *elton.Context) (err error) {
   131  		account := "anonymous"
   132  		if user != "" {
   133  			account = getUserAccount(c)
   134  		}
   135  		c.Body = &userInfo{
   136  			Account: account,
   137  		}
   138  		return nil
   139  	}
   140  }
   141  
   142  func updateServerStatus(conf *config.PikeConfig) {
   143  	// 数据需要复制
   144  	upstreamServers := make([]config.UpstreamConfig, len(conf.Upstreams))
   145  	for i, item := range conf.Upstreams {
   146  		up := upstream.Get(item.Name)
   147  		if len(item.Servers) == 0 || up == nil {
   148  			upstreamServers[i] = item
   149  			continue
   150  		}
   151  		p := &item
   152  		statusList := up.GetServerStatusList()
   153  		servers := make([]config.UpstreamServerConfig, len(p.Servers))
   154  		// 填充upstream server的状态
   155  		for j, server := range p.Servers {
   156  			for _, status := range statusList {
   157  				if server.Addr == status.Addr {
   158  					server.Healthy = status.Healthy
   159  				}
   160  			}
   161  			servers[j] = server
   162  		}
   163  		p.Servers = servers
   164  		upstreamServers[i] = *p
   165  	}
   166  	conf.Upstreams = upstreamServers
   167  }
   168  
   169  // getConfig 获取config配置
   170  func getConfig(c *elton.Context) (err error) {
   171  	conf, err := config.Read()
   172  	if err != nil {
   173  		return
   174  	}
   175  	updateServerStatus(conf)
   176  	c.Body = conf
   177  	return nil
   178  }
   179  
   180  // saveConfig 保存config配置
   181  func saveConfig(c *elton.Context) (err error) {
   182  	conf := config.PikeConfig{}
   183  	err = json.Unmarshal(c.RequestBody, &conf)
   184  	if err != nil {
   185  		return
   186  	}
   187  	if conf.YAML != "" {
   188  		err = yaml.Unmarshal([]byte(conf.YAML), &conf)
   189  		if err != nil {
   190  			return
   191  		}
   192  	}
   193  	err = config.Write(&conf)
   194  	if err != nil {
   195  		return
   196  	}
   197  	data, _ := yaml.Marshal(conf)
   198  	conf.YAML = string(data)
   199  	// 简单的等待1秒后再更新状态
   200  	// 这样有可能检测到配置有更新,重新加载
   201  	delay := c.QueryParam("delay")
   202  	if delay != "" {
   203  		v, _ := time.ParseDuration(delay)
   204  		if v == 0 || v > 5*time.Second {
   205  			v = time.Second
   206  		}
   207  		time.Sleep(v)
   208  	}
   209  	updateServerStatus(&conf)
   210  	// 因为yaml部分要根据配置数据重新生成,因此重新读取返回
   211  	c.Body = conf
   212  	return
   213  }
   214  
   215  // getApplicationInfo 获取应用信息
   216  func getApplicationInfo(c *elton.Context) (err error) {
   217  	processing := make(map[string]int32)
   218  	defaultServers.m.Range(func(key, value interface{}) bool {
   219  		if key == nil || value == nil {
   220  			return true
   221  		}
   222  		name, ok := key.(string)
   223  		if !ok {
   224  			return true
   225  		}
   226  		s, ok := value.(*server)
   227  		if !ok {
   228  			return true
   229  		}
   230  		processing[name] = s.processing.Load()
   231  		return true
   232  	})
   233  	c.Body = &applicationInfo{
   234  		Info:       app.GetInfo(),
   235  		Processing: processing,
   236  	}
   237  	return
   238  }
   239  
   240  // removeCache 删除缓存
   241  func removeCache(c *elton.Context) (err error) {
   242  	key := c.QueryParam("key")
   243  	if key == "" {
   244  		err = cacheKeyIsNil
   245  		return
   246  	}
   247  	cache.RemoveHTTPCache(c.QueryParam("cache"), []byte(key))
   248  	c.NoContent()
   249  	return
   250  }
   251  
   252  // StartAdminServer start admin server
   253  func StartAdminServer(config AdminServerConfig) (err error) {
   254  	logger := log.Default()
   255  	ttlToken := &jwt.TTLToken{
   256  		TTL: 24 * time.Hour,
   257  		// 密钥用于加密数据,需保密
   258  		Secret: []byte(config.Password),
   259  		// CookieName: jwtCookie,
   260  	}
   261  
   262  	// Passthrough为false,会校验token是否正确
   263  	jwtNormal := jwt.NewJWT(jwt.Config{
   264  		CookieName: jwtCookie,
   265  		TTLToken:   ttlToken,
   266  		// Decode:     ttlToken.Decode,
   267  	})
   268  	// 用于初始化创建token使用(此时可能token还没有或者已过期)
   269  	jwtPassthrough := jwt.NewJWT(jwt.Config{
   270  		CookieName:  jwtCookie,
   271  		TTLToken:    ttlToken,
   272  		Passthrough: true,
   273  	})
   274  
   275  	e := elton.New()
   276  	e.Use(func(c *elton.Context) error {
   277  		// 全局设置为不可缓存,后续可覆盖
   278  		c.NoCache()
   279  
   280  		// cors
   281  		c.SetHeader("Access-Control-Allow-Credentials", "true")
   282  		c.SetHeader("Access-Control-Allow-Origin", "http://127.0.0.1:3123")
   283  		c.SetHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
   284  		c.SetHeader("Access-Control-Allow-Headers", "Content-Type, Accept")
   285  		c.SetHeader("Access-Control-Max-Age", "86400")
   286  		return c.Next()
   287  	})
   288  
   289  	e.Use(middleware.NewError(middleware.ErrorConfig{
   290  		ResponseType: "json",
   291  	}))
   292  	e.Use(middleware.NewStats(middleware.StatsConfig{
   293  		OnStats: func(info *middleware.StatsInfo, _ *elton.Context) {
   294  			logger.Info("access log",
   295  				zap.String("ip", info.IP),
   296  				zap.String("method", info.Method),
   297  				zap.String("uri", info.URI),
   298  				zap.Int("status", info.Status),
   299  				zap.String("consuming", info.Consuming.String()),
   300  				zap.Int("bytes", info.Size),
   301  			)
   302  		},
   303  	}))
   304  
   305  	e.Use(middleware.NewDefaultCompress())
   306  	e.Use(middleware.NewDefaultBodyParser())
   307  	e.Use(middleware.NewDefaultResponder())
   308  
   309  	// 获取、更新配置
   310  	var isLogin elton.Handler
   311  	if config.User != "" {
   312  		isLogin = elton.Compose(jwtNormal, newIsLoginHandler(config.User))
   313  	} else {
   314  		isLogin = func(c *elton.Context) error {
   315  			return c.Next()
   316  		}
   317  	}
   318  	e.GET("/config", isLogin, getConfig)
   319  	e.PUT("/config", isLogin, saveConfig)
   320  
   321  	// 登录
   322  	e.POST("/login", jwtPassthrough, newLoginHandler(ttlToken, config.User, config.Password))
   323  	// 用户信息
   324  	e.GET("/me", jwtPassthrough, newUserMeHandler(config.User))
   325  
   326  	e.GET("/application-info", getApplicationInfo)
   327  
   328  	// 缓存
   329  	e.DELETE("/cache", removeCache)
   330  
   331  	e.GET("/ping", func(c *elton.Context) error {
   332  		c.BodyBuffer = bytes.NewBufferString("pong")
   333  		return nil
   334  	})
   335  	// 静态文件
   336  	e.GET("/", func(c *elton.Context) error {
   337  		return sendFile(c, "index.html")
   338  	})
   339  	e.GET("/*", middleware.NewStaticServe(webAsset, middleware.StaticServeConfig{
   340  		// 客户端缓存一年
   341  		MaxAge: 365 * 24 * time.Hour,
   342  		// 缓存服务器缓存一个小时
   343  		SMaxAge:             time.Hour,
   344  		DisableLastModified: true,
   345  	}))
   346  
   347  	// cors设置
   348  	e.OPTIONS("/*", func(c *elton.Context) error {
   349  		c.NoContent()
   350  		return nil
   351  	})
   352  	logger.Info("start admin server",
   353  		zap.String("addr", config.Addr),
   354  	)
   355  	return e.ListenAndServe(config.Addr)
   356  }