diff --git a/go.mod b/go.mod index 3554542..69106de 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,23 @@ module go.unistack.org/micro-server-http/v3 -go 1.22 +go 1.22.7 -toolchain go1.23.1 +toolchain go1.23.3 require ( - go.unistack.org/micro-client-http/v3 v3.9.13 + go.unistack.org/micro-client-http/v3 v3.9.14 go.unistack.org/micro-codec-yaml/v3 v3.10.2 go.unistack.org/micro-proto/v3 v3.4.1 - go.unistack.org/micro/v3 v3.10.97 - golang.org/x/net v0.30.0 + go.unistack.org/micro/v3 v3.10.108 + golang.org/x/net v0.31.0 ) require ( github.com/google/gnostic v0.7.0 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect - golang.org/x/sys v0.26.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/sys v0.27.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2e0c828..bd8c582 100644 --- a/go.sum +++ b/go.sum @@ -876,6 +876,8 @@ go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.unistack.org/micro-client-http/v3 v3.9.13 h1:KUWkDfA7p+dZEIgSsWuPjfj6yeX8rrnHYwrEjdAZvmU= go.unistack.org/micro-client-http/v3 v3.9.13/go.mod h1:WmrmeWWKohGn5ODrCr53wUp4pe/ZE6UYdXh7ECgpOH4= +go.unistack.org/micro-client-http/v3 v3.9.14 h1:26BiMcUlGpxpN+S84tpAeMetbd9rbBd+IILq1CkFP2U= +go.unistack.org/micro-client-http/v3 v3.9.14/go.mod h1:KS6qxpxGDQmcszBaJpidc1KOr528QflEKoGopl0qYJ8= go.unistack.org/micro-codec-yaml/v3 v3.10.2 h1:02I9XzhaBHqZU8Vd5e2zhf8j4foJ4muPT/x4gdR6E4c= go.unistack.org/micro-codec-yaml/v3 v3.10.2/go.mod h1:A/tYj7x9CRhuin7WxeIvnuo8bMDrZYcJkogVYN8X7rU= go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q= @@ -883,6 +885,8 @@ go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44d go.unistack.org/micro/v3 v3.10.94/go.mod h1:erMgt3Bl7vQQ0e9UpQyR5NlLiZ9pKeEJ9+1tfYFaqUg= go.unistack.org/micro/v3 v3.10.97 h1:8l7fv+i06/PjPrBBhRC/ZQkWGIOuHPg3jJN0vktYE78= go.unistack.org/micro/v3 v3.10.97/go.mod h1:YzMldzHN9Ei+zy5t/Psu7RUWDZwUfrNYiStSQtTz90g= +go.unistack.org/micro/v3 v3.10.108 h1:3L7SkilMVLtH8y3pQIPtr3jjQYrf0AMv1oAkoL3nFkE= +go.unistack.org/micro/v3 v3.10.108/go.mod h1:YzMldzHN9Ei+zy5t/Psu7RUWDZwUfrNYiStSQtTz90g= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1012,6 +1016,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1139,6 +1145,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1456,6 +1464,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1499,6 +1509,8 @@ google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3 google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1521,6 +1533,8 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/handler/health/health.go b/handler/health/health.go index e1e0dda..9731a0d 100644 --- a/handler/health/health.go +++ b/handler/health/health.go @@ -13,15 +13,30 @@ type Handler struct { opts Options } -type CheckFunc func(context.Context) error +type ( + CheckFunc func(context.Context) error + Option func(*Options) +) -type Option func(*Options) +type Stater interface { + Live() bool + Ready() bool + Health() bool +} type Options struct { - Version string - Name string - LiveChecks []CheckFunc - ReadyChecks []CheckFunc + Version string + Name string + Staters []Stater + LiveChecks []CheckFunc + ReadyChecks []CheckFunc + HealthChecks []CheckFunc +} + +func Service(s ...Stater) Option { + return func(o *Options) { + o.Staters = append(o.Staters, s...) + } } func LiveChecks(fns ...CheckFunc) Option { @@ -36,6 +51,12 @@ func ReadyChecks(fns ...CheckFunc) Option { } } +func HealthChecks(fns ...CheckFunc) Option { + return func(o *Options) { + o.HealthChecks = append(o.HealthChecks, fns...) + } +} + func Name(name string) Option { return func(o *Options) { o.Name = name @@ -56,18 +77,51 @@ func NewHandler(opts ...Option) *Handler { return &Handler{opts: options} } +func (h *Handler) Healthy(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error { + var err error + + for _, s := range h.opts.Staters { + if !s.Health() { + return errors.ServiceUnavailable(h.opts.Name, "%v", err) + } + } + + for _, fn := range h.opts.HealthChecks { + if err = fn(ctx); err != nil { + return errors.ServiceUnavailable(h.opts.Name, "%v", err) + } + } + + return nil +} + func (h *Handler) Live(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error { var err error + + for _, s := range h.opts.Staters { + if !s.Live() { + return errors.ServiceUnavailable(h.opts.Name, "%v", err) + } + } + for _, fn := range h.opts.LiveChecks { if err = fn(ctx); err != nil { return errors.ServiceUnavailable(h.opts.Name, "%v", err) } } + return nil } func (h *Handler) Ready(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error { var err error + + for _, s := range h.opts.Staters { + if !s.Ready() { + return errors.ServiceUnavailable(h.opts.Name, "%v", err) + } + } + for _, fn := range h.opts.ReadyChecks { if err = fn(ctx); err != nil { return errors.ServiceUnavailable(h.opts.Name, "%v", err) diff --git a/handler/health/health.proto b/handler/health/health.proto index 6a23f99..715e9b4 100644 --- a/handler/health/health.proto +++ b/handler/health/health.proto @@ -8,6 +8,22 @@ import "openapiv3/annotations.proto"; import "codec/frame.proto"; service HealthService { + rpc Healthy(micro.codec.Frame) returns (micro.codec.Frame) { + option (micro.openapiv3.openapiv3_operation) = { + operation_id: "Healthy"; + responses: { + default: { + reference: { + _ref: "micro.codec.Frame"; + }; + }; + }; + }; + option (micro.api.http) = { + get: "/health"; + additional_bindings: { get: "/healthz"; } + }; + }; rpc Live(micro.codec.Frame) returns (micro.codec.Frame) { option (micro.openapiv3.openapiv3_operation) = { operation_id: "Live"; @@ -19,7 +35,10 @@ service HealthService { }; }; }; - option (micro.api.http) = { get: "/live"; }; + option (micro.api.http) = { + get: "/live"; + additional_bindings: { get: "/livez"; } + }; }; rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) { option (micro.openapiv3.openapiv3_operation) = { @@ -32,7 +51,9 @@ service HealthService { }; }; }; - option (micro.api.http) = { get: "/ready"; }; + option (micro.api.http) = { get: "/ready"; + additional_bindings: { get: "/readyz"; } + }; }; rpc Version(micro.codec.Frame) returns (micro.codec.Frame) { option (micro.openapiv3.openapiv3_operation) = { diff --git a/handler/health/health_micro.pb.go b/handler/health/health_micro.pb.go index cfbb869..37ad339 100644 --- a/handler/health/health_micro.pb.go +++ b/handler/health/health_micro.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-micro. DO NOT EDIT. // versions: // - protoc-gen-go-micro v3.10.4 -// - protoc v5.26.1 +// - protoc v5.28.3 // source: health/health.proto package health_handler @@ -17,12 +17,14 @@ var ( ) type HealthServiceClient interface { + Healthy(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) Live(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) Ready(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) Version(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) } type HealthServiceServer interface { + Healthy(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error diff --git a/handler/health/health_micro_http.pb.go b/handler/health/health_micro_http.pb.go index 75e4d85..b9ff281 100644 --- a/handler/health/health_micro_http.pb.go +++ b/handler/health/health_micro_http.pb.go @@ -16,6 +16,20 @@ import ( var ( HealthServiceServerEndpoints = []v3.EndpointMetadata{ + { + Name: "HealthService.Healthy", + Path: "/health", + Method: "GET", + Body: "", + Stream: false, + }, + { + Name: "HealthService.Healthy", + Path: "/healthz", + Method: "GET", + Body: "", + Stream: false, + }, { Name: "HealthService.Live", Path: "/live", @@ -23,6 +37,13 @@ var ( Body: "", Stream: false, }, + { + Name: "HealthService.Live", + Path: "/livez", + Method: "GET", + Body: "", + Stream: false, + }, { Name: "HealthService.Ready", Path: "/ready", @@ -30,6 +51,13 @@ var ( Body: "", Stream: false, }, + { + Name: "HealthService.Ready", + Path: "/readyz", + Method: "GET", + Body: "", + Stream: false, + }, { Name: "HealthService.Version", Path: "/version", @@ -49,6 +77,24 @@ func NewHealthServiceClient(name string, c client.Client) HealthServiceClient { return &healthServiceClient{c: c, name: name} } +func (c *healthServiceClient) Healthy(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) { + errmap := make(map[string]interface{}, 1) + errmap["default"] = &codec.Frame{} + opts = append(opts, + v31.ErrorMap(errmap), + ) + opts = append(opts, + v31.Method(http.MethodGet), + v31.Path("/health"), + ) + rsp := &codec.Frame{} + err := c.c.Call(ctx, c.c.NewRequest(c.name, "HealthService.Healthy", req), rsp, opts...) + if err != nil { + return nil, err + } + return rsp, nil +} + func (c *healthServiceClient) Live(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) { errmap := make(map[string]interface{}, 1) errmap["default"] = &codec.Frame{} @@ -107,6 +153,10 @@ type healthServiceServer struct { HealthServiceServer } +func (h *healthServiceServer) Healthy(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { + return h.HealthServiceServer.Healthy(ctx, req, rsp) +} + func (h *healthServiceServer) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { return h.HealthServiceServer.Live(ctx, req, rsp) } @@ -121,6 +171,7 @@ func (h *healthServiceServer) Version(ctx context.Context, req *codec.Frame, rsp func RegisterHealthServiceServer(s server.Server, sh HealthServiceServer, opts ...server.HandlerOption) error { type healthService interface { + Healthy(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error diff --git a/handler/meter/meter_micro.pb.go b/handler/meter/meter_micro.pb.go index 1b9ed6f..fb627ef 100644 --- a/handler/meter/meter_micro.pb.go +++ b/handler/meter/meter_micro.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-micro. DO NOT EDIT. // versions: // - protoc-gen-go-micro v3.10.4 -// - protoc v5.26.1 +// - protoc v5.28.3 // source: meter/meter.proto package meter_handler diff --git a/http.go b/http.go index 4933a59..c228d22 100644 --- a/http.go +++ b/http.go @@ -12,6 +12,7 @@ import ( "sort" "strings" "sync" + "sync/atomic" "time" "go.unistack.org/micro/v3/broker" @@ -34,6 +35,9 @@ type Server struct { errorHandler func(context.Context, server.Handler, http.ResponseWriter, *http.Request, error, int) pathHandlers *rhttp.Trie opts server.Options + stateLive *atomic.Uint32 + stateReady *atomic.Uint32 + stateHealth *atomic.Uint32 registerRPC bool sync.RWMutex registered bool @@ -117,10 +121,7 @@ func (h *Server) Init(opts ...server.Option) error { h.RUnlock() return err } - if err := h.opts.Transport.Init(); err != nil { - h.RUnlock() - return err - } + h.RUnlock() h.Lock() @@ -592,9 +593,12 @@ func (h *Server) Start() error { } go func() { - if cerr := hs.Serve(ts); cerr != nil && !errors.Is(cerr, net.ErrClosed) { + if cerr := hs.Serve(ts); cerr != nil && !errors.Is(cerr, http.ErrServerClosed) { h.opts.Logger.Error(h.opts.Context, "serve error", cerr) } + h.stateLive.Store(0) + h.stateReady.Store(0) + h.stateHealth.Store(0) }() go func() { @@ -670,6 +674,10 @@ func (h *Server) Start() error { ch <- err }() + h.stateLive.Store(1) + h.stateReady.Store(1) + h.stateHealth.Store(1) + return nil } @@ -687,6 +695,18 @@ func (h *Server) Name() string { return h.opts.Name } +func (h *Server) Live() bool { + return h.stateLive.Load() == 1 +} + +func (h *Server) Ready() bool { + return h.stateReady.Load() == 1 +} + +func (h *Server) Health() bool { + return h.stateHealth.Load() == 1 +} + func NewServer(opts ...server.Option) *Server { options := server.NewOptions(opts...) eh := DefaultErrorHandler @@ -694,6 +714,9 @@ func NewServer(opts ...server.Option) *Server { eh = v } return &Server{ + stateLive: &atomic.Uint32{}, + stateReady: &atomic.Uint32{}, + stateHealth: &atomic.Uint32{}, opts: options, exit: make(chan chan error), subscribers: make(map[*httpSubscriber][]broker.Subscriber),