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  }