github.com/erda-project/erda-infra@v1.0.9/providers/httpserver/provider.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package httpserver 16 17 import ( 18 "reflect" 19 "sync" 20 21 "github.com/go-playground/validator" 22 "github.com/labstack/echo" 23 24 "github.com/erda-project/erda-infra/base/logs" 25 "github.com/erda-project/erda-infra/base/servicehub" 26 "github.com/erda-project/erda-infra/providers/httpserver/interceptors" 27 "github.com/erda-project/erda-infra/providers/httpserver/server" 28 ) 29 30 // config . 31 type config struct { 32 Addr string `file:"addr" default:":8080" desc:"http address to listen"` 33 PrintRoutes bool `file:"print_routes" default:"true" desc:"print http routes"` 34 AllowCORS bool `file:"allow_cors" default:"false" desc:"allow cors"` 35 Reloadable bool `file:"reloadable" default:"false" desc:"routes reloadable"` 36 37 Debug bool `file:"debug" default:"false"` 38 Log LogConfig `file:"log"` 39 } 40 41 // LogConfig . 42 type LogConfig struct { 43 MaxBodySizeBytes int `file:"max_body_size_bytes" default:"1024" desc:"max body size in bytes"` 44 } 45 46 type provider struct { 47 Cfg *config 48 Log logs.Logger 49 50 server server.Server 51 lock sync.Mutex 52 routes map[routeKey]*route 53 err error 54 55 startedChan chan struct{} 56 } 57 58 // Init . 59 func (p *provider) Init(ctx servicehub.Context) error { 60 p.server = server.New(p.Cfg.Reloadable, &dataBinder{}, &structValidator{validator: validator.New()}) 61 p.startedChan = make(chan struct{}) 62 63 //p.server.Use(interceptors.Recover(p.Log).(func(echo.HandlerFunc) echo.HandlerFunc)) 64 p.server.Use(interceptors.SimpleRecord(p.getInterceptorOption())) 65 p.server.Use(interceptors.CORS(p.Cfg.AllowCORS)) 66 p.server.Use(interceptors.InjectRequestID()) 67 p.server.Use(interceptors.DetailLog(p.getInterceptorOption())) 68 p.server.Use(interceptors.BodyDump(p.getInterceptorOption(), p.Cfg.Log.MaxBodySizeBytes)) 69 p.server.Use(interceptors.PassThroughDebugFlag()) 70 p.server.Use(p.wrapContext()) 71 72 return nil 73 } 74 75 func (p *provider) wrapContext() echo.MiddlewareFunc { 76 return func(next echo.HandlerFunc) echo.HandlerFunc { 77 return func(c echo.Context) error { 78 ctx := &context{Context: c} 79 err := next(ctx) 80 p.logFailure(c, err) 81 return err 82 } 83 } 84 } 85 86 func (p *provider) getInterceptorOption() interceptors.Option { 87 funcs := []interceptors.EnableFetchFunc{ 88 func(c echo.Context) bool { 89 return p.Cfg.Debug 90 }, 91 } 92 return interceptors.NewOption(funcs, p.Log) 93 } 94 95 func (p *provider) logFailure(c server.Context, err error) { 96 if err != nil || c.Response().Status/100 != 2 { 97 p.Log.Errorf("(%s) err: %v, status: %d, url method: %s, path: %s, matcherPath: %s, ip: %s, header: %v", 98 interceptors.GetRequestID(c), err, c.Response().Status, c.Request().Method, c.Request().URL.Path, c.Path(), c.RealIP(), c.Request().Header) 99 } 100 } 101 102 // Start . 103 func (p *provider) Start() error { 104 if p.err != nil { 105 return p.err 106 } 107 if p.Cfg.PrintRoutes { 108 if p.Cfg.Reloadable { 109 p.lock.Lock() 110 } 111 p.printRoutes(p.routes) 112 if p.Cfg.Reloadable { 113 p.lock.Unlock() 114 } 115 } 116 p.Log.Infof("starting http server at %s", p.Cfg.Addr) 117 close(p.startedChan) 118 return p.server.Start(p.Cfg.Addr) 119 } 120 121 // Close . 122 func (p *provider) Close() error { 123 if p.server == nil { 124 return nil 125 } 126 return p.server.Close() 127 } 128 129 // Provide . 130 func (p *provider) Provide(ctx servicehub.DependencyContext, args ...interface{}) interface{} { 131 if ctx.Service() == "http-router-manager" || ctx.Type() == routerManagerType { 132 return p.newRouterManager(true, ctx.Caller(), args...) 133 } else if p.Cfg.Reloadable && (ctx.Service() != "http-router-tx" || ctx.Type() == routerType) { 134 return &autoCommitRouter{ 135 tx: p.newRouterManager(false, ctx.Caller(), args...), 136 } 137 } 138 return p.newRouterTx(true, ctx.Caller(), args...) 139 } 140 141 func (p *provider) newRouterManager(reset bool, group string, opts ...interface{}) RouterManager { 142 return &routerManager{ 143 group: group, 144 reset: reset, 145 opts: opts, 146 p: p, 147 } 148 } 149 150 func (p *provider) newRouterTx(reset bool, group string, opts ...interface{}) RouterTx { 151 interceptors := getInterceptors(opts) 152 r := &router{ 153 tx: p.server.NewRouter(), 154 group: group, 155 interceptors: interceptors, 156 } 157 r.pathFormater = r.getPathFormater(opts) 158 if p.Cfg.Reloadable { 159 r.lock = &p.lock 160 r.lock.Lock() 161 r.routes = make(map[routeKey]*route) 162 for key, route := range p.routes { 163 if !reset || route.group != r.group { 164 r.routes[key] = route 165 if route.handler != nil { 166 r.tx.Add(route.method, route.path, route.handler) 167 } 168 } 169 } 170 r.reportError = func(err error) {} 171 r.updateRoutes = func(routes map[routeKey]*route) { 172 p.routes = routes 173 diff := make(map[routeKey]*route) 174 for key, route := range p.routes { 175 if route.group == r.group { 176 diff[key] = route 177 } 178 } 179 if p.Cfg.PrintRoutes { 180 p.printRoutes(diff) 181 } 182 } 183 } else { 184 r.routes = p.routes 185 r.reportError = func(err error) { 186 p.err = err 187 } 188 } 189 return r 190 } 191 192 var ( 193 routerType = reflect.TypeOf((*Router)(nil)).Elem() 194 routerTxType = reflect.TypeOf((*RouterTx)(nil)).Elem() 195 routerManagerType = reflect.TypeOf((*RouterManager)(nil)).Elem() 196 ) 197 198 func init() { 199 servicehub.Register("http-server", &servicehub.Spec{ 200 Services: []string{"http-server", "http-router", "http-router-manager", "http-router-tx"}, 201 Types: []reflect.Type{ 202 routerType, 203 routerTxType, 204 routerManagerType, 205 }, 206 Description: "http server", 207 ConfigFunc: func() interface{} { return &config{} }, 208 Creator: func() servicehub.Provider { 209 return &provider{ 210 routes: make(map[routeKey]*route), 211 } 212 }, 213 }) 214 }