go.uber.org/yarpc@v1.72.1/encoding/thrift/outbound.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package thrift 22 23 import ( 24 "bytes" 25 "context" 26 "fmt" 27 "io" 28 29 "go.uber.org/thriftrw/envelope" 30 "go.uber.org/thriftrw/protocol" 31 "go.uber.org/thriftrw/protocol/binary" 32 "go.uber.org/thriftrw/wire" 33 "go.uber.org/yarpc" 34 encodingapi "go.uber.org/yarpc/api/encoding" 35 "go.uber.org/yarpc/api/transport" 36 "go.uber.org/yarpc/encoding/thrift/internal" 37 "go.uber.org/yarpc/pkg/encoding" 38 "go.uber.org/yarpc/pkg/errors" 39 "go.uber.org/yarpc/pkg/procedure" 40 ) 41 42 // Client is a generic Thrift client. It speaks in raw Thrift payloads. 43 // 44 // Users should use the client generated by the code generator rather than 45 // using this directly. 46 type Client interface { 47 // Call the given Thrift method. 48 Call(ctx context.Context, reqBody envelope.Enveloper, opts ...yarpc.CallOption) (wire.Value, error) 49 CallOneway(ctx context.Context, reqBody envelope.Enveloper, opts ...yarpc.CallOption) (transport.Ack, error) 50 } 51 52 // Config contains the configuration for the Client. 53 type Config struct { 54 // Name of the Thrift service. This is the name used in the Thrift file 55 // with the 'service' keyword. 56 Service string 57 58 // ClientConfig through which requests will be sent. Required. 59 ClientConfig transport.ClientConfig 60 } 61 62 // New creates a new Thrift client. 63 func New(c Config, opts ...ClientOption) Client { 64 // Code generated for Thrift client instantiation will probably be something 65 // like this: 66 // 67 // func New(cc transport.ClientConfig, opts ...ClientOption) *MyServiceClient { 68 // c := thrift.New(thrift.Config{ 69 // Service: "MyService", 70 // ClientConfig: cc, 71 // Protocol: protocol.Binary, 72 // }, opts...) 73 // return &MyServiceClient{client: c} 74 // } 75 // 76 // So Config is really the internal config as far as consumers of the 77 // generated client are concerned. 78 79 var cc clientConfig 80 for _, opt := range opts { 81 opt.applyClientOption(&cc) 82 } 83 84 var p protocol.Protocol = binary.Default 85 if cc.Protocol != nil { 86 p = cc.Protocol 87 } 88 89 svc := c.Service 90 if cc.ServiceName != "" { 91 svc = cc.ServiceName 92 } 93 94 if cc.Multiplexed { 95 p = multiplexedOutboundProtocol{ 96 Protocol: p, 97 Service: svc, 98 } 99 } 100 101 return thriftClient{ 102 p: p, 103 cc: c.ClientConfig, 104 thriftService: svc, 105 Enveloping: cc.Enveloping, 106 } 107 } 108 109 type thriftClient struct { 110 cc transport.ClientConfig 111 p protocol.Protocol 112 113 // name of the Thrift service 114 thriftService string 115 Enveloping bool 116 } 117 118 func (c thriftClient) Call(ctx context.Context, reqBody envelope.Enveloper, opts ...yarpc.CallOption) (wire.Value, error) { 119 // Code generated for Thrift client calls will probably be something like 120 // this: 121 // 122 // func (c *MyServiceClient) someMethod(ctx context.Context, arg1 Arg1Type, arg2 arg2Type, opts ...yarpc.CallOption) (returnValue, error) { 123 // args := myservice.SomeMethodHelper.Args(arg1, arg2) 124 // resBody, err := c.client.Call(ctx, args, opts...) 125 // var result myservice.SomeMethodResult 126 // if err = result.FromWire(resBody); err != nil { 127 // return nil, err 128 // } 129 // success, err := myservice.SomeMethodHelper.UnwrapResponse(&result) 130 // return success, err 131 // } 132 133 out := c.cc.GetUnaryOutbound() 134 135 treq, proto, err := c.buildTransportRequest(reqBody) 136 if err != nil { 137 return wire.Value{}, err 138 } 139 140 call := encodingapi.NewOutboundCall(encoding.FromOptions(opts)...) 141 ctx, err = call.WriteToRequest(ctx, treq) 142 if err != nil { 143 return wire.Value{}, err 144 } 145 146 tres, err := out.Call(ctx, treq) 147 if err != nil { 148 return wire.Value{}, err 149 } 150 defer tres.Body.Close() 151 152 if _, err = call.ReadFromResponse(ctx, tres); err != nil { 153 return wire.Value{}, err 154 } 155 156 var bodyReader io.ReaderAt 157 158 // optimization for avoiding additional buffer copy as tchannel outbound 159 // already decodes the body into io.ReaderAt compatible type 160 // thrift deserializer reads sets, maps, and lists lazily which makes 161 // buffer pool unusable as response handling is out of scope of this method 162 if body, ok := tres.Body.(io.ReaderAt); ok { 163 bodyReader = body 164 } else { 165 buf := bytes.NewBuffer(make([]byte, 0, _defaultBufferSize)) 166 if _, err = buf.ReadFrom(tres.Body); err != nil { 167 return wire.Value{}, err 168 } 169 bodyReader = bytes.NewReader(buf.Bytes()) 170 } 171 172 envelope, err := proto.DecodeEnveloped(bodyReader) 173 if err != nil { 174 return wire.Value{}, errors.ResponseBodyDecodeError(treq, err) 175 } 176 177 switch envelope.Type { 178 case wire.Reply: 179 return envelope.Value, nil 180 case wire.Exception: 181 var exc internal.TApplicationException 182 if err := exc.FromWire(envelope.Value); err != nil { 183 return wire.Value{}, errors.ResponseBodyDecodeError(treq, err) 184 } 185 return wire.Value{}, thriftException{ 186 Service: treq.Service, 187 Procedure: treq.Procedure, 188 Reason: &exc, 189 } 190 default: 191 return wire.Value{}, errors.ResponseBodyDecodeError( 192 treq, errUnexpectedEnvelopeType(envelope.Type)) 193 } 194 } 195 196 func (c thriftClient) CallOneway(ctx context.Context, reqBody envelope.Enveloper, opts ...yarpc.CallOption) (transport.Ack, error) { 197 out := c.cc.GetOnewayOutbound() 198 199 treq, _, err := c.buildTransportRequest(reqBody) 200 if err != nil { 201 return nil, err 202 } 203 204 call := encodingapi.NewOutboundCall(encoding.FromOptions(opts)...) 205 ctx, err = call.WriteToRequest(ctx, treq) 206 if err != nil { 207 return nil, err 208 } 209 210 return out.CallOneway(ctx, treq) 211 } 212 213 func (c thriftClient) buildTransportRequest(reqBody envelope.Enveloper) (*transport.Request, protocol.Protocol, error) { 214 proto := c.p 215 if !c.Enveloping { 216 proto = disableEnvelopingProtocol{ 217 Protocol: proto, 218 Type: wire.Reply, // we only decode replies with this instance 219 } 220 } 221 222 treq := transport.Request{ 223 Caller: c.cc.Caller(), 224 Service: c.cc.Service(), 225 Encoding: Encoding, 226 Procedure: procedure.ToName(c.thriftService, reqBody.MethodName()), 227 } 228 229 value, err := reqBody.ToWire() 230 if err != nil { 231 // ToWire validates the request. If it failed, we should return the error 232 // as-is because it's not an encoding error. 233 return nil, nil, err 234 } 235 236 reqEnvelopeType := reqBody.EnvelopeType() 237 if reqEnvelopeType != wire.Call && reqEnvelopeType != wire.OneWay { 238 return nil, nil, errors.RequestBodyEncodeError( 239 &treq, errUnexpectedEnvelopeType(reqEnvelopeType), 240 ) 241 } 242 243 var buffer bytes.Buffer 244 err = proto.EncodeEnveloped(wire.Envelope{ 245 Name: reqBody.MethodName(), 246 Type: reqEnvelopeType, 247 SeqID: 1, // don't care 248 Value: value, 249 }, &buffer) 250 if err != nil { 251 return nil, nil, errors.RequestBodyEncodeError(&treq, err) 252 } 253 254 treq.Body = &buffer 255 treq.BodySize = buffer.Len() 256 return &treq, proto, nil 257 } 258 259 type thriftException struct { 260 Service string 261 Procedure string 262 Reason *internal.TApplicationException 263 } 264 265 func (e thriftException) Error() string { 266 return fmt.Sprintf( 267 "thrift request to procedure %q of service %q encountered an internal failure: %v", 268 e.Procedure, e.Service, e.Reason) 269 }