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