trpc.group/trpc-go/trpc-go@v1.0.2/client/stream.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  package client
    15  
    16  import (
    17  	"context"
    18  
    19  	"trpc.group/trpc-go/trpc-go/codec"
    20  	"trpc.group/trpc-go/trpc-go/errs"
    21  	icodec "trpc.group/trpc-go/trpc-go/internal/codec"
    22  	"trpc.group/trpc-go/trpc-go/internal/report"
    23  	"trpc.group/trpc-go/trpc-go/transport"
    24  )
    25  
    26  // Stream is the interface that performs streaming RPCs.
    27  type Stream interface {
    28  	// Send sends stream messages.
    29  	Send(ctx context.Context, m interface{}) error
    30  	// Recv receives stream messages.
    31  	Recv(ctx context.Context) ([]byte, error)
    32  	// Init initiates all stream related options.
    33  	Init(ctx context.Context, opt ...Option) (*Options, error)
    34  	// Invoke initiates the lower layer connection to build the stream.
    35  	Invoke(ctx context.Context) error
    36  	// Close closes the stream.
    37  	Close(ctx context.Context) error
    38  }
    39  
    40  // DefaultStream is the default client Stream.
    41  var DefaultStream = NewStream()
    42  
    43  // NewStream is the function that returns a Stream.
    44  var NewStream = func() Stream {
    45  	return &stream{}
    46  }
    47  
    48  // stream is an implementation of Stream.
    49  type stream struct {
    50  	opts *Options
    51  	client
    52  }
    53  
    54  // SendControl is the interface used for sender's flow control.
    55  type SendControl interface {
    56  	GetWindow(uint32) error
    57  	UpdateWindow(uint32)
    58  }
    59  
    60  // RecvControl is the interface used for receiver's flow control.
    61  type RecvControl interface {
    62  	OnRecv(n uint32) error
    63  }
    64  
    65  // Send implements Stream.
    66  // It serializes the message and sends it to server through stream transport.
    67  // It's safe to call Recv and Send in different goroutines concurrently, but calling
    68  // Send in different goroutines concurrently is not thread-safe.
    69  func (s *stream) Send(ctx context.Context, m interface{}) error {
    70  	msg := codec.Message(ctx)
    71  	reqBodyBuf, err := serializeAndCompress(ctx, msg, m, s.opts)
    72  	if err != nil {
    73  		s.opts.StreamTransport.Close(ctx)
    74  		return err
    75  	}
    76  
    77  	// if m != nil, m is Data frame and sender flow control is needed.
    78  	if m != nil && s.opts.SControl != nil {
    79  		if err := s.opts.SControl.GetWindow(uint32(len(reqBodyBuf))); err != nil {
    80  			return err
    81  		}
    82  	}
    83  	// encode reqBodyBuf
    84  	reqBuf, err := s.opts.Codec.Encode(msg, reqBodyBuf)
    85  	if err != nil {
    86  		return errs.NewFrameError(errs.RetClientEncodeFail, "client codec Encode: "+err.Error())
    87  	}
    88  
    89  	if err := s.opts.StreamTransport.Send(ctx, reqBuf); err != nil {
    90  		s.opts.StreamTransport.Close(ctx)
    91  		return err
    92  	}
    93  	return nil
    94  }
    95  
    96  // Recv implements Stream.
    97  // It decodes and decompresses the message and leaves serialization to upper layer.
    98  // It's safe to call Recv and Send in different goroutines concurrently, but calling
    99  // Send in different goroutines concurrently is not thread-safe.
   100  func (s *stream) Recv(ctx context.Context) ([]byte, error) {
   101  	rspBuf, err := s.opts.StreamTransport.Recv(ctx)
   102  	if err != nil {
   103  		s.opts.StreamTransport.Close(ctx)
   104  		return nil, err
   105  	}
   106  	msg := codec.Message(ctx)
   107  	rspBodyBuf, err := s.opts.Codec.Decode(msg, rspBuf)
   108  	if err != nil {
   109  		s.opts.StreamTransport.Close(ctx)
   110  		return nil, errs.NewFrameError(errs.RetClientDecodeFail, "client codec Decode: "+err.Error())
   111  	}
   112  	if len(rspBodyBuf) > 0 {
   113  		compressType := msg.CompressType()
   114  		if icodec.IsValidCompressType(s.opts.CurrentCompressType) {
   115  			compressType = s.opts.CurrentCompressType
   116  		}
   117  		// decompress
   118  		if icodec.IsValidCompressType(compressType) && compressType != codec.CompressTypeNoop {
   119  			rspBodyBuf, err = codec.Decompress(compressType, rspBodyBuf)
   120  			if err != nil {
   121  				s.opts.StreamTransport.Close(ctx)
   122  				return nil,
   123  					errs.NewFrameError(errs.RetClientDecodeFail, "client codec Decompress: "+err.Error())
   124  			}
   125  		}
   126  	}
   127  	return rspBodyBuf, nil
   128  }
   129  
   130  // Close implements Stream.
   131  func (s *stream) Close(ctx context.Context) error {
   132  	// Send Close message.
   133  	return s.Send(ctx, nil)
   134  }
   135  
   136  // Init implements Stream.
   137  func (s *stream) Init(ctx context.Context, opt ...Option) (*Options, error) {
   138  	// The generic message structure data of the current request is retrieved from the context,
   139  	// and each backend call uses a new msg generated by the client stub code.
   140  	msg := codec.Message(ctx)
   141  
   142  	// Get options.
   143  	opts, err := s.getOptions(msg, opt...)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	// Update msg.
   149  	s.updateMsg(msg, opts)
   150  
   151  	// Select a node of backend service.
   152  	node, err := selectNode(ctx, msg, opts)
   153  	if err != nil {
   154  		report.SelectNodeFail.Incr()
   155  		return nil, err
   156  	}
   157  	ensureMsgRemoteAddr(msg, findFirstNonEmpty(node.Network, opts.Network), node.Address)
   158  	const invalidCost = -1
   159  	opts.Node.set(node, node.Address, invalidCost)
   160  	if opts.Codec == nil {
   161  		report.ClientCodecEmpty.Incr()
   162  		return nil, errs.NewFrameError(errs.RetClientEncodeFail, "client: codec empty")
   163  	}
   164  	opts.CallOptions = append(opts.CallOptions, transport.WithMsg(msg))
   165  	s.opts = opts
   166  	return s.opts, nil
   167  }
   168  
   169  func findFirstNonEmpty(ss ...string) string {
   170  	for _, s := range ss {
   171  		if s != "" {
   172  			return s
   173  		}
   174  	}
   175  	return ""
   176  }
   177  
   178  // Invoke implements Stream.
   179  func (s *stream) Invoke(ctx context.Context) error {
   180  	return s.opts.StreamTransport.Init(ctx, s.opts.CallOptions...)
   181  }