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), ¶ms) 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, ¶ms) 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 }