kratos: add application info (#968)

* add application info

* add base context

* add client target
pull/970/head
Tony Chen 4 years ago committed by GitHub
parent 7f835db398
commit 149fc0195e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 33
      app.go
  2. 23
      context.go
  3. 115
      internal/context/context.go
  4. 20
      options.go
  5. 2
      transport/grpc/client.go
  6. 1
      transport/grpc/context.go
  7. 24
      transport/grpc/server.go
  8. 8
      transport/grpc/server_test.go
  9. 5
      transport/http/client.go
  10. 1
      transport/http/context.go
  11. 16
      transport/http/server.go
  12. 15
      transport/http/server_test.go
  13. 4
      transport/transport.go

@ -52,24 +52,29 @@ func (a *App) Run() error {
a.log.Infow( a.log.Infow(
"service_id", a.opts.id, "service_id", a.opts.id,
"service_name", a.opts.name, "service_name", a.opts.name,
"version", a.opts.version, "service_version", a.opts.version,
) )
instance, err := buildInstance(a.opts) instance, err := a.buildInstance()
if err != nil { if err != nil {
return err return err
} }
eg, ctx := errgroup.WithContext(a.ctx) ctx := NewContext(a.ctx, AppInfo{
ID: a.opts.id,
Name: a.opts.name,
Version: a.opts.version,
})
eg, ctx := errgroup.WithContext(ctx)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for _, srv := range a.opts.servers { for _, srv := range a.opts.servers {
srv := srv srv := srv
eg.Go(func() error { eg.Go(func() error {
<-ctx.Done() // wait for stop signal <-ctx.Done() // wait for stop signal
return srv.Stop() return srv.Stop(ctx)
}) })
wg.Add(1) wg.Add(1)
eg.Go(func() error { eg.Go(func() error {
wg.Done() wg.Done()
return srv.Start() return srv.Start(ctx)
}) })
} }
wg.Wait() wg.Wait()
@ -110,23 +115,23 @@ func (a *App) Stop() error {
return nil return nil
} }
func buildInstance(o options) (*registry.ServiceInstance, error) { func (a *App) buildInstance() (*registry.ServiceInstance, error) {
if len(o.endpoints) == 0 { if len(a.opts.endpoints) == 0 {
for _, srv := range o.servers { for _, srv := range a.opts.servers {
if r, ok := srv.(transport.Endpointer); ok { if r, ok := srv.(transport.Endpointer); ok {
e, err := r.Endpoint() e, err := r.Endpoint()
if err != nil { if err != nil {
return nil, err return nil, err
} }
o.endpoints = append(o.endpoints, e) a.opts.endpoints = append(a.opts.endpoints, e)
} }
} }
} }
return &registry.ServiceInstance{ return &registry.ServiceInstance{
ID: o.id, ID: a.opts.id,
Name: o.name, Name: a.opts.name,
Version: o.version, Version: a.opts.version,
Metadata: o.metadata, Metadata: a.opts.metadata,
Endpoints: o.endpoints, Endpoints: a.opts.endpoints,
}, nil }, nil
} }

@ -0,0 +1,23 @@
package kratos
import "context"
// AppInfo is application context value.
type AppInfo struct {
ID string
Name string
Version string
}
type appKey struct{}
// NewContext returns a new Context that carries value.
func NewContext(ctx context.Context, s AppInfo) context.Context {
return context.WithValue(ctx, appKey{}, s)
}
// FromContext returns the Transport value stored in ctx, if any.
func FromContext(ctx context.Context) (s AppInfo, ok bool) {
s, ok = ctx.Value(appKey{}).(AppInfo)
return
}

@ -0,0 +1,115 @@
package context
import (
"context"
"sync"
"sync/atomic"
"time"
)
type mergeCtx struct {
parent1, parent2 context.Context
done chan struct{}
doneMark uint32
doneOnce sync.Once
doneErr error
cancelCh chan struct{}
cancelOnce sync.Once
}
// Merge merges two contexts into one.
func Merge(parent1, parent2 context.Context) (context.Context, context.CancelFunc) {
mc := &mergeCtx{
parent1: parent1,
parent2: parent2,
done: make(chan struct{}),
cancelCh: make(chan struct{}),
}
select {
case <-parent1.Done():
mc.finish(parent1.Err())
case <-parent2.Done():
mc.finish(parent2.Err())
default:
go mc.wait()
}
return mc, mc.cancel
}
func (mc *mergeCtx) finish(err error) error {
mc.doneOnce.Do(func() {
mc.doneErr = err
atomic.StoreUint32(&mc.doneMark, 1)
close(mc.done)
})
return mc.doneErr
}
func (mc *mergeCtx) wait() {
var err error
select {
case <-mc.parent1.Done():
err = mc.parent1.Err()
case <-mc.parent2.Done():
err = mc.parent2.Err()
case <-mc.cancelCh:
err = context.Canceled
}
mc.finish(err)
}
func (mc *mergeCtx) cancel() {
mc.cancelOnce.Do(func() {
close(mc.cancelCh)
})
}
// Done implements context.Context.
func (mc *mergeCtx) Done() <-chan struct{} {
return mc.done
}
// Err implements context.Context.
func (mc *mergeCtx) Err() error {
if atomic.LoadUint32(&mc.doneMark) != 0 {
return mc.doneErr
}
var err error
select {
case <-mc.parent1.Done():
err = mc.parent1.Err()
case <-mc.parent2.Done():
err = mc.parent2.Err()
case <-mc.cancelCh:
err = context.Canceled
default:
return nil
}
return mc.finish(err)
}
// Deadline implements context.Context.
func (mc *mergeCtx) Deadline() (time.Time, bool) {
d1, ok1 := mc.parent1.Deadline()
d2, ok2 := mc.parent2.Deadline()
switch {
case !ok1:
return d2, ok2
case !ok2:
return d1, ok1
case d1.Before(d2):
return d1, true
default:
return d2, true
}
}
// Value implements context.Context.
func (mc *mergeCtx) Value(key interface{}) interface{} {
if v := mc.parent1.Value(key); v != nil {
return v
}
return mc.parent2.Value(key)
}

@ -59,22 +59,22 @@ func Context(ctx context.Context) Option {
return func(o *options) { o.ctx = ctx } return func(o *options) { o.ctx = ctx }
} }
// Signal with exit signals.
func Signal(sigs ...os.Signal) Option {
return func(o *options) { o.sigs = sigs }
}
// Logger with service logger. // Logger with service logger.
func Logger(logger log.Logger) Option { func Logger(logger log.Logger) Option {
return func(o *options) { o.logger = logger } return func(o *options) { o.logger = logger }
} }
// Registrar with service registry.
func Registrar(r registry.Registrar) Option {
return func(o *options) { o.registrar = r }
}
// Server with transport servers. // Server with transport servers.
func Server(srv ...transport.Server) Option { func Server(srv ...transport.Server) Option {
return func(o *options) { o.servers = srv } return func(o *options) { o.servers = srv }
} }
// Signal with exit signals.
func Signal(sigs ...os.Signal) Option {
return func(o *options) { o.sigs = sigs }
}
// Registrar with service registry.
func Registrar(r registry.Registrar) Option {
return func(o *options) { o.registrar = r }
}

@ -103,7 +103,7 @@ func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.Clien
func unaryClientInterceptor(m middleware.Middleware, timeout time.Duration) grpc.UnaryClientInterceptor { func unaryClientInterceptor(m middleware.Middleware, timeout time.Duration) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindGRPC}) ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindGRPC})
ctx = NewClientContext(ctx, ClientInfo{FullMethod: method}) ctx = NewClientContext(ctx, ClientInfo{FullMethod: method, Target: cc.Target()})
if timeout > 0 { if timeout > 0 {
var cancel context.CancelFunc var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, timeout) ctx, cancel = context.WithTimeout(ctx, timeout)

@ -27,6 +27,7 @@ func FromServerContext(ctx context.Context) (info ServerInfo, ok bool) {
type ClientInfo struct { type ClientInfo struct {
// FullMethod is the full RPC method string, i.e., /package.service/method. // FullMethod is the full RPC method string, i.e., /package.service/method.
FullMethod string FullMethod string
Target string
} }
type clientKey struct{} type clientKey struct{}

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/go-kratos/kratos/v2/api/metadata" "github.com/go-kratos/kratos/v2/api/metadata"
ic "github.com/go-kratos/kratos/v2/internal/context"
"github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/internal/host"
"github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/middleware"
@ -70,6 +71,7 @@ func Options(opts ...grpc.ServerOption) ServerOption {
// Server is a gRPC server wrapper. // Server is a gRPC server wrapper.
type Server struct { type Server struct {
*grpc.Server *grpc.Server
ctx context.Context
lis net.Listener lis net.Listener
network string network string
address string address string
@ -86,7 +88,7 @@ func NewServer(opts ...ServerOption) *Server {
srv := &Server{ srv := &Server{
network: "tcp", network: "tcp",
address: ":0", address: ":0",
timeout: time.Second, timeout: 1 * time.Second,
middleware: middleware.Chain( middleware: middleware.Chain(
recovery.Recovery(), recovery.Recovery(),
), ),
@ -98,7 +100,7 @@ func NewServer(opts ...ServerOption) *Server {
} }
var grpcOpts = []grpc.ServerOption{ var grpcOpts = []grpc.ServerOption{
grpc.ChainUnaryInterceptor( grpc.ChainUnaryInterceptor(
unaryServerInterceptor(srv.middleware, srv.timeout), srv.unaryServerInterceptor(),
), ),
} }
if len(srv.grpcOpts) > 0 { if len(srv.grpcOpts) > 0 {
@ -128,11 +130,13 @@ func (s *Server) Endpoint() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
s.address = addr
return fmt.Sprintf("grpc://%s", addr), nil return fmt.Sprintf("grpc://%s", addr), nil
} }
// Start start the gRPC server. // Start start the gRPC server.
func (s *Server) Start() error { func (s *Server) Start(ctx context.Context) error {
s.ctx = ctx
if s.lis == nil { if s.lis == nil {
lis, err := net.Listen(s.network, s.address) lis, err := net.Listen(s.network, s.address)
if err != nil { if err != nil {
@ -146,27 +150,29 @@ func (s *Server) Start() error {
} }
// Stop stop the gRPC server. // Stop stop the gRPC server.
func (s *Server) Stop() error { func (s *Server) Stop(ctx context.Context) error {
s.GracefulStop() s.GracefulStop()
s.health.Shutdown() s.health.Shutdown()
s.log.Info("[gRPC] server stopping") s.log.Info("[gRPC] server stopping")
return nil return nil
} }
func unaryServerInterceptor(m middleware.Middleware, timeout time.Duration) grpc.UnaryServerInterceptor { func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ctx, cancel := ic.Merge(ctx, s.ctx)
defer cancel()
ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindGRPC}) ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindGRPC})
ctx = NewServerContext(ctx, ServerInfo{Server: info.Server, FullMethod: info.FullMethod}) ctx = NewServerContext(ctx, ServerInfo{Server: info.Server, FullMethod: info.FullMethod})
if timeout > 0 { if s.timeout > 0 {
var cancel context.CancelFunc var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, timeout) ctx, cancel = context.WithTimeout(ctx, s.timeout)
defer cancel() defer cancel()
} }
h := func(ctx context.Context, req interface{}) (interface{}, error) { h := func(ctx context.Context, req interface{}) (interface{}, error) {
return handler(ctx, req) return handler(ctx, req)
} }
if m != nil { if s.middleware != nil {
h = m(h) h = s.middleware(h)
} }
return h(ctx, req) return h(ctx, req)
} }

@ -9,7 +9,11 @@ import (
"github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/internal/host"
) )
type testKey struct{}
func TestServer(t *testing.T) { func TestServer(t *testing.T) {
ctx := context.Background()
ctx = context.WithValue(ctx, testKey{}, "test")
srv := NewServer() srv := NewServer()
if e, err := srv.Endpoint(); err != nil || e == "" { if e, err := srv.Endpoint(); err != nil || e == "" {
t.Fatal(e, err) t.Fatal(e, err)
@ -17,13 +21,13 @@ func TestServer(t *testing.T) {
go func() { go func() {
// start server // start server
if err := srv.Start(); err != nil { if err := srv.Start(ctx); err != nil {
panic(err) panic(err)
} }
}() }()
time.Sleep(time.Second) time.Sleep(time.Second)
testClient(t, srv) testClient(t, srv)
srv.Stop() srv.Stop(ctx)
} }
func testClient(t *testing.T, srv *Server) { func testClient(t *testing.T, srv *Server) {

@ -27,6 +27,7 @@ type Client struct {
b balancer.Balancer b balancer.Balancer
scheme string scheme string
endpoint string
target Target target Target
userAgent string userAgent string
middleware middleware.Middleware middleware middleware.Middleware
@ -148,7 +149,7 @@ func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) {
options := &clientOptions{ options := &clientOptions{
ctx: ctx, ctx: ctx,
scheme: "http", scheme: "http",
timeout: 1 * time.Second, timeout: 500 * time.Millisecond,
encoder: DefaultRequestEncoder, encoder: DefaultRequestEncoder,
decoder: DefaultResponseDecoder, decoder: DefaultResponseDecoder,
errorDecoder: DefaultErrorDecoder, errorDecoder: DefaultErrorDecoder,
@ -196,6 +197,7 @@ func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) {
userAgent: options.userAgent, userAgent: options.userAgent,
target: target, target: target,
scheme: options.scheme, scheme: options.scheme,
endpoint: options.endpoint,
discovery: options.discovery, discovery: options.discovery,
b: options.balancer, b: options.balancer,
}, nil }, nil
@ -240,6 +242,7 @@ func (client *Client) Invoke(ctx context.Context, path string, args interface{},
ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindHTTP}) ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindHTTP})
ctx = NewClientContext(ctx, ClientInfo{ ctx = NewClientContext(ctx, ClientInfo{
Target: client.endpoint,
PathPattern: c.pathPattern, PathPattern: c.pathPattern,
Request: req, Request: req,
}) })

@ -28,6 +28,7 @@ func FromServerContext(ctx context.Context) (info ServerInfo, ok bool) {
type ClientInfo struct { type ClientInfo struct {
Request *http.Request Request *http.Request
PathPattern string PathPattern string
Target string
} }
type clientKey struct{} type clientKey struct{}

@ -9,6 +9,7 @@ import (
"strings" "strings"
"time" "time"
ic "github.com/go-kratos/kratos/v2/internal/context"
"github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/internal/host"
"github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport"
@ -53,6 +54,7 @@ func Logger(logger log.Logger) ServerOption {
// Server is an HTTP server wrapper. // Server is an HTTP server wrapper.
type Server struct { type Server struct {
*http.Server *http.Server
ctx context.Context
lis net.Listener lis net.Listener
network string network string
address string address string
@ -66,7 +68,7 @@ func NewServer(opts ...ServerOption) *Server {
srv := &Server{ srv := &Server{
network: "tcp", network: "tcp",
address: ":0", address: ":0",
timeout: time.Second, timeout: 1 * time.Second,
log: log.NewHelper(log.DefaultLogger), log: log.NewHelper(log.DefaultLogger),
} }
for _, o := range opts { for _, o := range opts {
@ -94,10 +96,14 @@ func (s *Server) HandleFunc(path string, h http.HandlerFunc) {
// ServeHTTP should write reply headers and data to the ResponseWriter and then return. // ServeHTTP should write reply headers and data to the ResponseWriter and then return.
func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), s.timeout) ctx, cancel := ic.Merge(req.Context(), s.ctx)
defer cancel() defer cancel()
ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindHTTP}) ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindHTTP})
ctx = NewServerContext(ctx, ServerInfo{Request: req, Response: res}) ctx = NewServerContext(ctx, ServerInfo{Request: req, Response: res})
if s.timeout > 0 {
ctx, cancel = context.WithTimeout(req.Context(), s.timeout)
defer cancel()
}
s.router.ServeHTTP(res, req.WithContext(ctx)) s.router.ServeHTTP(res, req.WithContext(ctx))
} }
@ -116,11 +122,13 @@ func (s *Server) Endpoint() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
s.address = addr
return fmt.Sprintf("http://%s", addr), nil return fmt.Sprintf("http://%s", addr), nil
} }
// Start start the HTTP server. // Start start the HTTP server.
func (s *Server) Start() error { func (s *Server) Start(ctx context.Context) error {
s.ctx = ctx
if s.lis == nil { if s.lis == nil {
lis, err := net.Listen(s.network, s.address) lis, err := net.Listen(s.network, s.address)
if err != nil { if err != nil {
@ -136,7 +144,7 @@ func (s *Server) Start() error {
} }
// Stop stop the HTTP server. // Stop stop the HTTP server.
func (s *Server) Stop() error { func (s *Server) Stop(ctx context.Context) error {
s.log.Info("[HTTP] server stopping") s.log.Info("[HTTP] server stopping")
return s.Shutdown(context.Background()) return s.Shutdown(context.Background())
} }

@ -12,6 +12,8 @@ import (
"github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/internal/host"
) )
type testKey struct{}
type testData struct { type testData struct {
Path string `json:"path"` Path string `json:"path"`
} }
@ -20,7 +22,13 @@ func TestServer(t *testing.T) {
fn := func(w http.ResponseWriter, r *http.Request) { fn := func(w http.ResponseWriter, r *http.Request) {
data := &testData{Path: r.RequestURI} data := &testData{Path: r.RequestURI}
json.NewEncoder(w).Encode(data) json.NewEncoder(w).Encode(data)
if r.Context().Value(testKey{}) != "test" {
w.WriteHeader(500)
}
} }
ctx := context.Background()
ctx = context.WithValue(ctx, testKey{}, "test")
srv := NewServer() srv := NewServer()
srv.HandleFunc("/index", fn) srv.HandleFunc("/index", fn)
@ -29,13 +37,13 @@ func TestServer(t *testing.T) {
} }
go func() { go func() {
if err := srv.Start(); err != nil { if err := srv.Start(ctx); err != nil {
panic(err) panic(err)
} }
}() }()
time.Sleep(time.Second) time.Sleep(time.Second)
testClient(t, srv) testClient(t, srv)
srv.Stop() srv.Stop(ctx)
} }
func testClient(t *testing.T, srv *Server) { func testClient(t *testing.T, srv *Server) {
@ -68,6 +76,9 @@ func testClient(t *testing.T, srv *Server) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if resp.StatusCode != 200 {
t.Fatalf("http status got %d", resp.StatusCode)
}
content, err := ioutil.ReadAll(resp.Body) content, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
t.Fatalf("read resp error %v", err) t.Fatalf("read resp error %v", err)

@ -12,8 +12,8 @@ import (
// Server is transport server. // Server is transport server.
type Server interface { type Server interface {
Start() error Start(context.Context) error
Stop() error Stop(context.Context) error
} }
// Endpointer is registry endpoint. // Endpointer is registry endpoint.

Loading…
Cancel
Save