go.uber.org/yarpc@v1.72.1/encoding/thrift/outbound_nowire.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 28 "go.uber.org/thriftrw/protocol/binary" 29 "go.uber.org/thriftrw/protocol/stream" 30 "go.uber.org/thriftrw/wire" 31 "go.uber.org/yarpc" 32 encodingapi "go.uber.org/yarpc/api/encoding" 33 "go.uber.org/yarpc/api/transport" 34 "go.uber.org/yarpc/encoding/thrift/internal" 35 "go.uber.org/yarpc/pkg/encoding" 36 "go.uber.org/yarpc/pkg/errors" 37 "go.uber.org/yarpc/pkg/procedure" 38 ) 39 40 // NoWireClient is a generic Thrift client for encoding/decoding using 41 // ThriftRW's "streaming" mechanisms. The body of the provided request 42 // ('reqBody') will be written out through its method 'Encode', 43 // while the body of the response ('resBody') is read out through its method 44 // 'Decode'. 45 // It speaks in raw Thrift payloads. 46 // 47 // Users should use the client generated by the code generator rather than 48 // using this directly. 49 type NoWireClient interface { 50 // Call the given Thrift method. 51 Call(ctx context.Context, reqBody stream.Enveloper, resBody stream.BodyReader, opts ...yarpc.CallOption) error 52 CallOneway(ctx context.Context, reqBody stream.Enveloper, opts ...yarpc.CallOption) (transport.Ack, error) 53 54 // Enabled returns whether or not this client is enabled through a 55 // ClientOption. This ClientOption is toggled through the 'NoWire(bool)' 56 // option. 57 Enabled() bool 58 } 59 60 // NewNoWire creates a new Thrift client that leverages ThriftRW's "streaming" 61 // implementation. 62 func NewNoWire(c Config, opts ...ClientOption) NoWireClient { 63 // Code generated for Thrift client instantiation will probably be something 64 // like this: 65 // 66 // func New(cc transport.ClientConfig, opts ...ClientOption) *MyServiceClient { 67 // c := thrift.NewNoWire(thrift.Config{ 68 // Service: "MyService", 69 // ClientConfig: cc, 70 // Protocol: binary.Default, 71 // }, opts...) 72 // return &MyServiceClient{client: c} 73 // } 74 // 75 // So Config is really the internal config as far as consumers of the 76 // generated client are concerned. 77 78 // default NoWire to true because this is the our final state to achieve 79 // but we still allow users to opt out by overriding NoWire to false. 80 cc := clientConfig{NoWire: true} 81 for _, opt := range opts { 82 opt.applyClientOption(&cc) 83 } 84 85 var p stream.Protocol = binary.Default 86 if cc.Protocol != nil { 87 if val, ok := cc.Protocol.(stream.Protocol); ok { 88 p = val 89 } else { 90 panic(fmt.Sprintf( 91 "Protocol config option provided, NewNoWire expects provided protocol %T to implement stream.Protocol", cc.Protocol)) 92 } 93 } 94 95 svc := c.Service 96 if cc.ServiceName != "" { 97 svc = cc.ServiceName 98 } 99 100 if cc.Multiplexed { 101 p = multiplexedOutboundNoWireProtocol{ 102 Protocol: p, 103 Service: svc, 104 } 105 } 106 107 return noWireThriftClient{ 108 p: p, 109 cc: c.ClientConfig, 110 thriftService: svc, 111 Enveloping: cc.Enveloping, 112 NoWire: cc.NoWire, 113 } 114 } 115 116 type noWireThriftClient struct { 117 cc transport.ClientConfig 118 p stream.Protocol 119 120 // name of the Thrift service 121 thriftService string 122 Enveloping bool 123 NoWire bool 124 } 125 126 func (c noWireThriftClient) Call(ctx context.Context, reqBody stream.Enveloper, resBody stream.BodyReader, opts ...yarpc.CallOption) error { 127 // Code generated for Thrift client calls will probably be something like 128 // this: 129 // 130 // func (c *MyServiceClient) someMethod(ctx context.Context, arg1 Arg1Type, arg2 arg2Type, opts ...yarpc.CallOption) (returnValue, error) { 131 // var result myservice.SomeMethodResult 132 // args := myservice.SomeMethodHelper.Args(arg1, arg2) 133 // err := c.client.Call(ctx, args, result, opts...) 134 // 135 // success, err := myservice.SomeMethodHelper.UnwrapResponse(&result) 136 // return success, err 137 // } 138 139 out := c.cc.GetUnaryOutbound() 140 141 treq, proto, err := c.buildTransportRequest(reqBody) 142 if err != nil { 143 return err 144 } 145 146 call := encodingapi.NewOutboundCall(encoding.FromOptions(opts)...) 147 ctx, err = call.WriteToRequest(ctx, treq) 148 if err != nil { 149 return err 150 } 151 152 tres, err := out.Call(ctx, treq) 153 if err != nil { 154 return err 155 } 156 defer tres.Body.Close() 157 158 if _, err := call.ReadFromResponse(ctx, tres); err != nil { 159 return err 160 } 161 162 sr := proto.Reader(tres.Body) 163 defer sr.Close() 164 165 envelope, err := sr.ReadEnvelopeBegin() 166 if err != nil { 167 return errors.ResponseBodyDecodeError(treq, err) 168 } 169 170 switch envelope.Type { 171 case wire.Reply: 172 if err := resBody.Decode(sr); err != nil { 173 return err 174 } 175 return sr.ReadEnvelopeEnd() 176 case wire.Exception: 177 var exc internal.TApplicationException 178 if err := exc.Decode(sr); err != nil { 179 return errors.ResponseBodyDecodeError(treq, err) 180 } 181 defer func() { 182 _ = sr.ReadEnvelopeEnd() 183 }() 184 185 return thriftException{ 186 Service: treq.Service, 187 Procedure: treq.Procedure, 188 Reason: &exc, 189 } 190 default: 191 return errors.ResponseBodyDecodeError( 192 treq, errUnexpectedEnvelopeType(envelope.Type)) 193 } 194 } 195 196 func (c noWireThriftClient) CallOneway(ctx context.Context, reqBody stream.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 noWireThriftClient) Enabled() bool { 214 return c.NoWire 215 } 216 217 func (c noWireThriftClient) buildTransportRequest(reqBody stream.Enveloper) (*transport.Request, stream.Protocol, error) { 218 proto := c.p 219 if !c.Enveloping { 220 proto = disableEnvelopingNoWireProtocol{ 221 Protocol: proto, 222 Type: wire.Reply, // we only decode replies with this instance 223 } 224 } 225 226 treq := transport.Request{ 227 Caller: c.cc.Caller(), 228 Service: c.cc.Service(), 229 Encoding: Encoding, 230 Procedure: procedure.ToName(c.thriftService, reqBody.MethodName()), 231 } 232 233 envType := reqBody.EnvelopeType() 234 if envType != wire.Call && envType != wire.OneWay { 235 return nil, nil, errors.RequestBodyEncodeError( 236 &treq, errUnexpectedEnvelopeType(envType), 237 ) 238 } 239 240 var buffer bytes.Buffer 241 sw := proto.Writer(&buffer) 242 defer sw.Close() 243 244 if err := sw.WriteEnvelopeBegin(stream.EnvelopeHeader{ 245 Name: reqBody.MethodName(), 246 Type: envType, 247 SeqID: 1, // don't care 248 }); err != nil { 249 return nil, nil, errors.RequestBodyEncodeError(&treq, err) 250 } 251 252 if err := reqBody.Encode(sw); err != nil { 253 return nil, nil, errors.RequestBodyEncodeError(&treq, err) 254 } 255 256 if err := sw.WriteEnvelopeEnd(); err != nil { 257 return nil, nil, errors.RequestBodyEncodeError(&treq, err) 258 } 259 260 treq.Body = &buffer 261 treq.BodySize = buffer.Len() 262 return &treq, proto, nil 263 }