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 }