github.com/kaydxh/golang@v0.0.131/pkg/webserver/config.go (about) 1 /* 2 *Copyright (c) 2022, kaydxh 3 * 4 *Permission is hereby granted, free of charge, to any person obtaining a copy 5 *of this software and associated documentation files (the "Software"), to deal 6 *in the Software without restriction, including without limitation the rights 7 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 *copies of the Software, and to permit persons to whom the Software is 9 *furnished to do so, subject to the following conditions: 10 * 11 *The above copyright notice and this permission notice shall be included in all 12 *copies or substantial portions of the Software. 13 * 14 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 *SOFTWARE. 21 */ 22 package webserver 23 24 import ( 25 "context" 26 "fmt" 27 "math" 28 "time" 29 30 "github.com/gin-gonic/gin" 31 "github.com/go-playground/validator/v10" 32 33 // interceptorratetimer_ "github.com/kaydxh/golang/pkg/middleware/grpc-middleware/timer" 34 viper_ "github.com/kaydxh/golang/pkg/viper" 35 "google.golang.org/grpc" 36 37 errors_ "github.com/kaydxh/golang/go/errors" 38 gw_ "github.com/kaydxh/golang/pkg/grpc-gateway" 39 healthz_ "github.com/kaydxh/golang/pkg/webserver/controller/healthz" 40 profiler_ "github.com/kaydxh/golang/pkg/webserver/controller/profiler" 41 "github.com/spf13/viper" 42 ) 43 44 const ( 45 defaultBindAddress = ":80" 46 defaultExternalAddress = ":80" 47 defaultShutdownDelayDuration = time.Duration(0) 48 defaultShutdownTimeoutDuration = 5 * time.Second 49 50 defaultMaxReceiveMessageSize = math.MaxInt32 51 defaultMaxSendMessageSize = math.MaxInt32 52 ) 53 54 type Config struct { 55 Proto Web 56 Validator *validator.Validate 57 opts struct { 58 // If set, overrides params below 59 viper *viper.Viper 60 bindAddress string 61 // ExternalAddress is the address (hostname or IP and port) that should be used in 62 // external (public internet) URLs for this GenericWebServer. 63 externalAddress string 64 // ShutdownDelayDuration allows to block shutdown for some time, e.g. until endpoints pointing to this API server 65 // have converged on all node. During this time, the API server keeps serving, /healthz will return 200, 66 // but /readyz will return failure. 67 shutdownDelayDuration time.Duration 68 //shutdownTimeoutDuration force shutdonw server after some time 69 shutdownTimeoutDuration time.Duration 70 gatewayOptions []gw_.GRPCGatewayOption 71 } 72 } 73 74 type completedConfig struct { 75 *Config 76 completeError error 77 } 78 79 type CompletedConfig struct { 80 // Embed a private pointer that cannot be instantiated outside of this package. 81 *completedConfig 82 } 83 84 // Validate checks Config. 85 func (c *completedConfig) Validate() error { 86 return c.Validator.Struct(c) 87 } 88 89 func (c *completedConfig) New(ctx context.Context, opts ...gw_.GRPCGatewayOption) (*GenericWebServer, error) { 90 if c.completeError != nil { 91 return nil, c.completeError 92 } 93 err := c.Validate() 94 if err != nil { 95 return nil, err 96 } 97 98 return c.install(ctx, opts...) 99 } 100 101 func (c *completedConfig) install(ctx context.Context, opts ...gw_.GRPCGatewayOption) (*GenericWebServer, error) { 102 c.Config.opts.gatewayOptions = append(c.Config.opts.gatewayOptions, c.installGrpcMessageSizeOptions()...) 103 c.Config.opts.gatewayOptions = append(c.Config.opts.gatewayOptions, c.installHttpMiddlewareChain()...) 104 c.Config.opts.gatewayOptions = append(c.Config.opts.gatewayOptions, c.installGrpcMiddlewareChain()...) 105 106 //append opts... 107 c.Config.opts.gatewayOptions = append(c.Config.opts.gatewayOptions, opts...) 108 grpcBackend := gw_.NewGRPCGateWay(c.opts.bindAddress, c.Config.opts.gatewayOptions...) 109 //grpcBackend.ApplyOptions() 110 gin.SetMode(gin.ReleaseMode) 111 ginBackend := gin.New() 112 fmt.Printf(" - listen address[%s]\n", c.opts.bindAddress) 113 114 ws := &GenericWebServer{ 115 ginBackend: ginBackend, 116 grpcBackend: grpcBackend, 117 postStartHooks: map[string]postStartHookEntry{}, 118 preShutdownHooks: map[string]preShutdownHookEntry{}, 119 readinessStopCh: make(chan struct{}), 120 } 121 122 var errs []error 123 if c.Proto.GetDebug().GetEnableProfiling() { 124 fmt.Printf("- install debug handler") 125 ws.InstallWebHandlers(profiler_.NewController()) 126 } 127 err := c.installDefaultHander(ws) 128 if err != nil { 129 errs = append(errs, err) 130 } 131 132 return ws, errors_.NewAggregate(errs) 133 } 134 135 func (c *completedConfig) installDefaultHander(ws *GenericWebServer) error { 136 ws.InstallWebHandlers(healthz_.NewController()) 137 return nil 138 } 139 140 // Complete set default ServerRunOptions. 141 func (c *Config) Complete() CompletedConfig { 142 err := c.loadViper() 143 if err != nil { 144 return CompletedConfig{&completedConfig{ 145 Config: c, 146 completeError: err, 147 }} 148 } 149 c.parseViper() 150 151 if c.Validator == nil { 152 c.Validator = validator.New() 153 } 154 155 return CompletedConfig{&completedConfig{Config: c}} 156 } 157 158 func (c *Config) installGrpcMessageSizeOptions() []gw_.GRPCGatewayOption { 159 maxRecvMsgSize := defaultMaxReceiveMessageSize 160 maxSendMsgSize := defaultMaxSendMessageSize 161 162 var opts []gw_.GRPCGatewayOption 163 // request 164 // http -> grpc client -> grpc server 165 // --------------------------------- -> gin server 166 if c.Proto.GetGrpc().GetMaxReceiveMessageSize() > 0 { 167 168 maxRecvMsgSize = int(c.Proto.GetGrpc().GetMaxReceiveMessageSize()) 169 } 170 opts = append( 171 opts, 172 gw_.WithServerOptions(grpc.MaxRecvMsgSize(maxRecvMsgSize)), 173 ) 174 opts = append( 175 opts, 176 gw_.WithClientDialOptions( 177 grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(maxRecvMsgSize)), 178 ), 179 ) 180 181 // response 182 // http <- grpc client <- grpc server 183 if c.Proto.GetGrpc().GetMaxSendMessageSize() > 0 { 184 maxSendMsgSize = int(c.Proto.GetGrpc().GetMaxSendMessageSize()) 185 } 186 opts = append( 187 opts, 188 gw_.WithServerOptions(grpc.MaxSendMsgSize(maxSendMsgSize)), 189 ) 190 opts = append( 191 opts, 192 gw_.WithClientDialOptions(grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxSendMsgSize))), 193 ) 194 195 return opts 196 } 197 198 // use LocalMiddlewareWrap instead of request id, metric, cost time, print request and response middleware 199 func (c *Config) installHttpMiddlewareChain() []gw_.GRPCGatewayOption { 200 201 httpConfig := c.Proto.GetHttp() 202 var opts []gw_.GRPCGatewayOption 203 opts = append( 204 opts, 205 // request id 206 gw_.WithHttpHandlerInterceptorRequestIDOptions(), 207 208 // http recoverer 209 gw_.WithHttpHandlerInterceptorRecoveryOptions(), 210 211 // clean path 212 gw_.WithHttpHandlerInterceptorCleanPathOptions(), 213 214 // http body proto 215 gw_.WithServerInterceptorsHttpBodyProtoOptions(), 216 217 gw_.WithHttpHandlerInterceptorsTimerOptions(), 218 219 // limit rate 220 gw_.WithHttpHandlerInterceptorsLimitAllOptions( 221 int(httpConfig.GetMaxConcurrency()), 222 ), 223 ) 224 225 //options 226 /* 227 different api formatter return http error response: 228 examples: 229 Web_Http_tcloud_api_v30: 230 { 231 "Response": { 232 "Error": { 233 "Code": "InvalidArgument", 234 "Message": "InvalidArgument" 235 }, 236 "RequestId": "22679249-b2e5-4970-8f55-3b5a31e70eb4" 237 } 238 } 239 240 Web_Http_trivial_api_v10: 241 { 242 "errorcode": 3, 243 "errormsg": "InvalidArgument", 244 "session_id": "efd82a7e-cb2f-4bd6-8bf2-d357fb4e1032" 245 } 246 247 Web_Http_trivial_api_v20: 248 { 249 "Error": { 250 "Code": "InvalidArgument", 251 "Message": "InvalidArgument" 252 }, 253 "RequestId": "96755708-9561-45b8-9bb7-da0bf777f155" 254 } 255 256 Web_Http_api_noop : {"code":3,"message":"InvalidArgument","details":[]} 257 */ 258 switch httpConfig.GetApiFormatter() { 259 case Web_Http_tcloud_api_v30: 260 opts = append(opts, 261 gw_.WithServerInterceptorsTCloud30HTTPResponseOptions(), 262 gw_.WithServerInterceptorsTCloud30HttpErrorOptions(), 263 ) 264 case Web_Http_trivial_api_v10: 265 opts = append(opts, 266 gw_.WithServerInterceptorsTrivialV1HTTPResponseOptions(), 267 gw_.WithServerInterceptorsTrivialV1HttpErrorOptions(), 268 ) 269 case Web_Http_trivial_api_v20: 270 opts = append(opts, 271 gw_.WithServerInterceptorsTrivialV2HTTPResponseOptions(), 272 gw_.WithServerInterceptorsTrivialV2HttpErrorOptions(), 273 ) 274 275 default: 276 } 277 278 return opts 279 } 280 281 func (c *Config) installGrpcMiddlewareChain() []gw_.GRPCGatewayOption { 282 283 grpcConfig := c.Proto.GetGrpc() 284 var opts []gw_.GRPCGatewayOption 285 opts = append( 286 opts, 287 288 // requestId 289 gw_.WithServerUnaryInterceptorsRequestIdOptions(), 290 291 // recovery 292 gw_.WithServerInterceptorsRecoveryOptions(), 293 294 // limit rate 295 gw_.WithServerInterceptorsLimitRateOptions( 296 int(grpcConfig.GetMaxConcurrencyUnary()), 297 int(grpcConfig.GetMaxConcurrencyStream()), 298 ), 299 300 // total req, fail req, cost time metrics, errorcode ip dims 301 gw_.WithServerUnaryMetricInterceptorOptions(), 302 303 // print input and output body 304 gw_.WithServerUnaryInterceptorsInOutPacketOptions(), 305 //gw_.WithServerInterceptorTimeoutOptions(grpcConfig.GetTimeout().AsDuration()), 306 ) 307 308 return opts 309 } 310 311 func (c *Config) WithWebConfigOptions(opts ...ConfigOption) { 312 c.ApplyOptions(opts...) 313 } 314 315 func (c *Config) AppendGRPCGatewayOptions(opts ...gw_.GRPCGatewayOption) { 316 c.opts.gatewayOptions = append(c.opts.gatewayOptions, opts...) 317 } 318 319 func (c *Config) parseViper() { 320 c.opts.bindAddress = c.Proto.GetBindHostPort() 321 } 322 323 func (c *Config) loadViper() error { 324 if c.opts.viper != nil { 325 return viper_.UnmarshalProtoMessageWithJsonPb(c.opts.viper, &c.Proto) 326 } 327 328 return nil 329 } 330 331 func (c *Config) GetBindAddress() string { 332 return c.opts.bindAddress 333 } 334 335 // default bind port 80 336 func NewConfig(options ...ConfigOption) *Config { 337 c := &Config{} 338 c.ApplyOptions(options...) 339 340 if c.opts.bindAddress == "" { 341 c.opts.bindAddress = defaultBindAddress 342 } 343 344 if c.opts.externalAddress == "" { 345 c.opts.externalAddress = defaultExternalAddress 346 } 347 348 return c 349 }