package warden import ( "context" "flag" "fmt" "math" "net" "os" "sync" "time" "github.com/bilibili/kratos/pkg/conf/dsn" "github.com/bilibili/kratos/pkg/log" nmd "github.com/bilibili/kratos/pkg/net/metadata" "github.com/bilibili/kratos/pkg/net/rpc/warden/ratelimiter" "github.com/bilibili/kratos/pkg/net/trace" xtime "github.com/bilibili/kratos/pkg/time" //this package is for json format response _ "github.com/bilibili/kratos/pkg/net/rpc/warden/internal/encoding/json" "github.com/bilibili/kratos/pkg/net/rpc/warden/internal/status" "github.com/pkg/errors" "google.golang.org/grpc" _ "google.golang.org/grpc/encoding/gzip" // NOTE: use grpc gzip by header grpc-accept-encoding "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" ) var ( _grpcDSN string _defaultSerConf = &ServerConfig{ Network: "tcp", Addr: "0.0.0.0:9000", Timeout: xtime.Duration(time.Second), IdleTimeout: xtime.Duration(time.Second * 60), MaxLifeTime: xtime.Duration(time.Hour * 2), ForceCloseWait: xtime.Duration(time.Second * 20), KeepAliveInterval: xtime.Duration(time.Second * 60), KeepAliveTimeout: xtime.Duration(time.Second * 20), } _abortIndex int8 = math.MaxInt8 / 2 ) // ServerConfig is rpc server conf. type ServerConfig struct { // Network is grpc listen network,default value is tcp Network string `dsn:"network"` // Addr is grpc listen addr,default value is 0.0.0.0:9000 Addr string `dsn:"address"` // Timeout is context timeout for per rpc call. Timeout xtime.Duration `dsn:"query.timeout"` // IdleTimeout is a duration for the amount of time after which an idle connection would be closed by sending a GoAway. // Idleness duration is defined since the most recent time the number of outstanding RPCs became zero or the connection establishment. IdleTimeout xtime.Duration `dsn:"query.idleTimeout"` // MaxLifeTime is a duration for the maximum amount of time a connection may exist before it will be closed by sending a GoAway. // A random jitter of +/-10% will be added to MaxConnectionAge to spread out connection storms. MaxLifeTime xtime.Duration `dsn:"query.maxLife"` // ForceCloseWait is an additive period after MaxLifeTime after which the connection will be forcibly closed. ForceCloseWait xtime.Duration `dsn:"query.closeWait"` // KeepAliveInterval is after a duration of this time if the server doesn't see any activity it pings the client to see if the transport is still alive. KeepAliveInterval xtime.Duration `dsn:"query.keepaliveInterval"` // KeepAliveTimeout is After having pinged for keepalive check, the server waits for a duration of Timeout and if no activity is seen even after that // the connection is closed. KeepAliveTimeout xtime.Duration `dsn:"query.keepaliveTimeout"` // LogFlag to control log behaviour. e.g. LogFlag: warden.LogFlagDisableLog. // Disable: 1 DisableArgs: 2 DisableInfo: 4 LogFlag int8 `dsn:"query.logFlag"` } // Server is the framework's server side instance, it contains the GrpcServer, interceptor and interceptors. // Create an instance of Server, by using NewServer(). type Server struct { conf *ServerConfig mutex sync.RWMutex server *grpc.Server handlers []grpc.UnaryServerInterceptor } // handle return a new unary server interceptor for OpenTracing\Logging\LinkTimeout. func (s *Server) handle() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, args *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { var ( cancel func() addr string ) s.mutex.RLock() conf := s.conf s.mutex.RUnlock() // get derived timeout from grpc context, // compare with the warden configured, // and use the minimum one timeout := time.Duration(conf.Timeout) if dl, ok := ctx.Deadline(); ok { ctimeout := time.Until(dl) if ctimeout-time.Millisecond*20 > 0 { ctimeout = ctimeout - time.Millisecond*20 } if timeout > ctimeout { timeout = ctimeout } } ctx, cancel = context.WithTimeout(ctx, timeout) defer cancel() // get grpc metadata(trace & remote_ip & color) var t trace.Trace cmd := nmd.MD{} if gmd, ok := metadata.FromIncomingContext(ctx); ok { for key, vals := range gmd { if nmd.IsIncomingKey(key) { cmd[key] = vals[0] } } } if t == nil { t = trace.New(args.FullMethod) } else { t.SetTitle(args.FullMethod) } if pr, ok := peer.FromContext(ctx); ok { addr = pr.Addr.String() t.SetTag(trace.String(trace.TagAddress, addr)) } defer t.Finish(&err) // use common meta data context instead of grpc context ctx = nmd.NewContext(ctx, cmd) ctx = trace.NewContext(ctx, t) resp, err = handler(ctx, req) return resp, status.FromError(err).Err() } } func init() { addFlag(flag.CommandLine) } func addFlag(fs *flag.FlagSet) { v := os.Getenv("GRPC") if v == "" { v = "tcp://0.0.0.0:9000/?timeout=1s&idle_timeout=60s" } fs.StringVar(&_grpcDSN, "grpc", v, "listen grpc dsn, or use GRPC env variable.") fs.Var(&_grpcTarget, "grpc.target", "usage: -grpc.target=seq.service=127.0.0.1:9000 -grpc.target=fav.service=192.168.10.1:9000") } func parseDSN(rawdsn string) *ServerConfig { conf := new(ServerConfig) d, err := dsn.Parse(rawdsn) if err != nil { panic(errors.WithMessage(err, fmt.Sprintf("warden: invalid dsn: %s", rawdsn))) } if _, err = d.Bind(conf); err != nil { panic(errors.WithMessage(err, fmt.Sprintf("warden: invalid dsn: %s", rawdsn))) } return conf } // NewServer returns a new blank Server instance with a default server interceptor. func NewServer(conf *ServerConfig, opt ...grpc.ServerOption) (s *Server) { if conf == nil { if !flag.Parsed() { fmt.Fprint(os.Stderr, "[warden] please call flag.Parse() before Init warden server, some configure may not effect\n") } conf = parseDSN(_grpcDSN) } else { fmt.Fprintf(os.Stderr, "[warden] config is Deprecated, argument will be ignored. please use -grpc flag or GRPC env to configure warden server.\n") } s = new(Server) if err := s.SetConfig(conf); err != nil { panic(errors.Errorf("warden: set config failed!err: %s", err.Error())) } keepParam := grpc.KeepaliveParams(keepalive.ServerParameters{ MaxConnectionIdle: time.Duration(s.conf.IdleTimeout), MaxConnectionAgeGrace: time.Duration(s.conf.ForceCloseWait), Time: time.Duration(s.conf.KeepAliveInterval), Timeout: time.Duration(s.conf.KeepAliveTimeout), MaxConnectionAge: time.Duration(s.conf.MaxLifeTime), }) opt = append(opt, keepParam, grpc.UnaryInterceptor(s.interceptor)) s.server = grpc.NewServer(opt...) s.Use(s.recovery(), s.handle(), serverLogging(conf.LogFlag), s.stats(), s.validate()) s.Use(ratelimiter.New(nil).Limit()) return } // SetConfig hot reloads server config func (s *Server) SetConfig(conf *ServerConfig) (err error) { if conf == nil { conf = _defaultSerConf } if conf.Timeout <= 0 { conf.Timeout = xtime.Duration(time.Second) } if conf.IdleTimeout <= 0 { conf.IdleTimeout = xtime.Duration(time.Second * 60) } if conf.MaxLifeTime <= 0 { conf.MaxLifeTime = xtime.Duration(time.Hour * 2) } if conf.ForceCloseWait <= 0 { conf.ForceCloseWait = xtime.Duration(time.Second * 20) } if conf.KeepAliveInterval <= 0 { conf.KeepAliveInterval = xtime.Duration(time.Second * 60) } if conf.KeepAliveTimeout <= 0 { conf.KeepAliveTimeout = xtime.Duration(time.Second * 20) } if conf.Addr == "" { conf.Addr = "0.0.0.0:9000" } if conf.Network == "" { conf.Network = "tcp" } s.mutex.Lock() s.conf = conf s.mutex.Unlock() return nil } // interceptor is a single interceptor out of a chain of many interceptors. // Execution is done in left-to-right order, including passing of context. // For example ChainUnaryServer(one, two, three) will execute one before two before three, and three // will see context changes of one and two. func (s *Server) interceptor(ctx context.Context, req interface{}, args *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { var ( i int chain grpc.UnaryHandler ) n := len(s.handlers) if n == 0 { return handler(ctx, req) } chain = func(ic context.Context, ir interface{}) (interface{}, error) { if i == n-1 { return handler(ic, ir) } i++ return s.handlers[i](ic, ir, args, chain) } return s.handlers[0](ctx, req, args, chain) } // Server return the grpc server for registering service. func (s *Server) Server() *grpc.Server { return s.server } // Use attachs a global inteceptor to the server. // For example, this is the right place for a rate limiter or error management inteceptor. func (s *Server) Use(handlers ...grpc.UnaryServerInterceptor) *Server { finalSize := len(s.handlers) + len(handlers) if finalSize >= int(_abortIndex) { panic("warden: server use too many handlers") } mergedHandlers := make([]grpc.UnaryServerInterceptor, finalSize) copy(mergedHandlers, s.handlers) copy(mergedHandlers[len(s.handlers):], handlers) s.handlers = mergedHandlers return s } // Run create a tcp listener and start goroutine for serving each incoming request. // Run will return a non-nil error unless Stop or GracefulStop is called. func (s *Server) Run(addr string) error { lis, err := net.Listen("tcp", addr) if err != nil { err = errors.WithStack(err) log.Error("failed to listen: %v", err) return err } reflection.Register(s.server) return s.Serve(lis) } // RunUnix create a unix listener and start goroutine for serving each incoming request. // RunUnix will return a non-nil error unless Stop or GracefulStop is called. func (s *Server) RunUnix(file string) error { lis, err := net.Listen("unix", file) if err != nil { err = errors.WithStack(err) log.Error("failed to listen: %v", err) return err } reflection.Register(s.server) return s.Serve(lis) } // Start create a new goroutine run server with configured listen addr // will panic if any error happend // return server itself func (s *Server) Start() (*Server, error) { lis, err := net.Listen(s.conf.Network, s.conf.Addr) if err != nil { return nil, err } log.Info("warden: start grpc listen addr: %v", lis.Addr()) reflection.Register(s.server) go func() { if err := s.Serve(lis); err != nil { panic(err) } }() return s, nil } // Serve accepts incoming connections on the listener lis, creating a new // ServerTransport and service goroutine for each. // Serve will return a non-nil error unless Stop or GracefulStop is called. func (s *Server) Serve(lis net.Listener) error { return s.server.Serve(lis) } // Shutdown stops the server gracefully. It stops the server from // accepting new connections and RPCs and blocks until all the pending RPCs are // finished or the context deadline is reached. func (s *Server) Shutdown(ctx context.Context) (err error) { ch := make(chan struct{}) go func() { s.server.GracefulStop() close(ch) }() select { case <-ctx.Done(): s.server.Stop() err = ctx.Err() case <-ch: } return }