go.uber.org/yarpc@v1.72.1/encoding/protobuf/v2/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 v2 22 23 import ( 24 "bytes" 25 "context" 26 27 "go.uber.org/yarpc" 28 apiencoding "go.uber.org/yarpc/api/encoding" 29 "go.uber.org/yarpc/api/transport" 30 "go.uber.org/yarpc/pkg/encoding" 31 "go.uber.org/yarpc/pkg/errors" 32 "go.uber.org/yarpc/pkg/procedure" 33 "go.uber.org/yarpc/yarpcerrors" 34 "google.golang.org/protobuf/proto" 35 ) 36 37 type client struct { 38 serviceName string 39 outboundConfig *transport.OutboundConfig 40 encoding transport.Encoding 41 codec *codec 42 } 43 44 func newClient(serviceName string, clientConfig transport.ClientConfig, anyResolver AnyResolver, options ...ClientOption) *client { 45 outboundConfig := toOutboundConfig(clientConfig) 46 client := &client{ 47 serviceName: serviceName, 48 outboundConfig: outboundConfig, 49 encoding: Encoding, 50 codec: newCodec(anyResolver), 51 } 52 for _, option := range options { 53 option.apply(client) 54 } 55 return client 56 } 57 58 func toOutboundConfig(cc transport.ClientConfig) *transport.OutboundConfig { 59 if outboundConfig, ok := cc.(*transport.OutboundConfig); ok { 60 return outboundConfig 61 } 62 // If the config is not an *OutboundConfig we assume the only Outbound is 63 // unary and create our own outbound config. 64 // If there is no unary outbound, this function will panic, but, we're kinda 65 // stuck with that. (and why the hell are you passing a oneway-only client 66 // config to protobuf anyway?). 67 return &transport.OutboundConfig{ 68 CallerName: cc.Caller(), 69 Outbounds: transport.Outbounds{ 70 ServiceName: cc.Service(), 71 Unary: cc.GetUnaryOutbound(), 72 }, 73 } 74 } 75 76 func (c *client) Call( 77 ctx context.Context, 78 requestMethodName string, 79 request proto.Message, 80 newResponse func() proto.Message, 81 options ...yarpc.CallOption, 82 ) (proto.Message, error) { 83 ctx, call, transportRequest, cleanup, err := c.buildTransportRequest(ctx, requestMethodName, request, options) 84 if cleanup != nil { 85 defer cleanup() 86 } 87 if err != nil { 88 return nil, err 89 } 90 unaryOutbound := c.outboundConfig.Outbounds.Unary 91 if unaryOutbound == nil { 92 return nil, yarpcerrors.InternalErrorf("no unary outbounds for OutboundConfig %s", c.outboundConfig.CallerName) 93 } 94 transportResponse, appErr := unaryOutbound.Call(ctx, transportRequest) 95 appErr = convertFromYARPCError(transportRequest.Encoding, appErr, c.codec) 96 if transportResponse == nil { 97 return nil, appErr 98 } 99 if transportResponse.Body != nil { 100 // thrift is not checking the error, should be consistent 101 defer transportResponse.Body.Close() 102 } 103 if _, err := call.ReadFromResponse(ctx, transportResponse); err != nil { 104 return nil, err 105 } 106 var response proto.Message 107 if transportResponse.Body != nil { 108 response = newResponse() 109 if err := unmarshal(transportRequest.Encoding, transportResponse.Body, response, c.codec); err != nil { 110 return nil, errors.ResponseBodyDecodeError(transportRequest, err) 111 } 112 } 113 return response, appErr 114 } 115 116 func (c *client) CallOneway( 117 ctx context.Context, 118 requestMethodName string, 119 request proto.Message, 120 options ...yarpc.CallOption, 121 ) (transport.Ack, error) { 122 ctx, _, transportRequest, cleanup, err := c.buildTransportRequest(ctx, requestMethodName, request, options) 123 if cleanup != nil { 124 defer cleanup() 125 } 126 if err != nil { 127 return nil, err 128 } 129 onewayOutbound := c.outboundConfig.Outbounds.Oneway 130 if onewayOutbound == nil { 131 return nil, yarpcerrors.InternalErrorf("no oneway outbounds for OutboundConfig %s", c.outboundConfig.CallerName) 132 } 133 ack, err := onewayOutbound.CallOneway(ctx, transportRequest) 134 return ack, convertFromYARPCError(transportRequest.Encoding, err, c.codec) 135 } 136 137 func (c *client) buildTransportRequest(ctx context.Context, requestMethodName string, request proto.Message, options []yarpc.CallOption) (context.Context, *apiencoding.OutboundCall, *transport.Request, func(), error) { 138 transportRequest := &transport.Request{ 139 Caller: c.outboundConfig.CallerName, 140 Service: c.outboundConfig.Outbounds.ServiceName, 141 Procedure: procedure.ToName(c.serviceName, requestMethodName), 142 Encoding: c.encoding, 143 } 144 call := apiencoding.NewOutboundCall(encoding.FromOptions(options)...) 145 ctx, err := call.WriteToRequest(ctx, transportRequest) 146 if err != nil { 147 return nil, nil, nil, nil, err 148 } 149 if transportRequest.Encoding != Encoding && transportRequest.Encoding != JSONEncoding { 150 return nil, nil, nil, nil, yarpcerrors.Newf(yarpcerrors.CodeInternal, "can only use encodings %q or %q, but %q was specified", Encoding, JSONEncoding, transportRequest.Encoding) 151 } 152 if request != nil { 153 requestData, cleanup, err := marshal(transportRequest.Encoding, request, c.codec) 154 if err != nil { 155 return nil, nil, nil, cleanup, errors.RequestBodyEncodeError(transportRequest, err) 156 } 157 if requestData != nil { 158 transportRequest.Body = bytes.NewReader(requestData) 159 transportRequest.BodySize = len(requestData) 160 } 161 return ctx, call, transportRequest, cleanup, nil 162 } 163 return ctx, call, transportRequest, nil, nil 164 } 165 166 func (c *client) CallStream( 167 ctx context.Context, 168 requestMethodName string, 169 opts ...yarpc.CallOption, 170 ) (*ClientStream, error) { 171 streamRequest := &transport.StreamRequest{ 172 Meta: &transport.RequestMeta{ 173 Caller: c.outboundConfig.CallerName, 174 Service: c.outboundConfig.Outbounds.ServiceName, 175 Procedure: procedure.ToName(c.serviceName, requestMethodName), 176 Encoding: c.encoding, 177 }, 178 } 179 call, err := apiencoding.NewStreamOutboundCall(encoding.FromOptions(opts)...) 180 if err != nil { 181 return nil, err 182 } 183 ctx, err = call.WriteToRequestMeta(ctx, streamRequest.Meta) 184 if err != nil { 185 return nil, err 186 } 187 if streamRequest.Meta.Encoding != Encoding && streamRequest.Meta.Encoding != JSONEncoding { 188 return nil, yarpcerrors.InternalErrorf("can only use encodings %q or %q, but %q was specified", Encoding, JSONEncoding, streamRequest.Meta.Encoding) 189 } 190 streamOutbound := c.outboundConfig.Outbounds.Stream 191 if streamOutbound == nil { 192 return nil, yarpcerrors.InternalErrorf("no stream outbounds for OutboundConfig %s", c.outboundConfig.CallerName) 193 } 194 stream, err := streamOutbound.CallStream(ctx, streamRequest) 195 if err != nil { 196 return nil, convertFromYARPCError(streamRequest.Meta.Encoding, err, c.codec) 197 } 198 return &ClientStream{stream: stream, codec: c.codec}, nil 199 }