go.uber.org/yarpc@v1.72.1/encoding/json/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 json 22 23 import ( 24 "bytes" 25 "context" 26 "encoding/json" 27 28 "go.uber.org/yarpc" 29 encodingapi "go.uber.org/yarpc/api/encoding" 30 "go.uber.org/yarpc/api/transport" 31 "go.uber.org/yarpc/pkg/encoding" 32 "go.uber.org/yarpc/pkg/errors" 33 ) 34 35 // Client makes JSON requests to a single service. 36 type Client interface { 37 // Call performs an outbound JSON request. 38 // 39 // resBodyOut is a pointer to a value that can be filled with 40 // json.Unmarshal. 41 // 42 // Returns the response or an error if the request failed. 43 Call(ctx context.Context, procedure string, reqBody interface{}, resBodyOut interface{}, opts ...yarpc.CallOption) error 44 CallOneway(ctx context.Context, procedure string, reqBody interface{}, opts ...yarpc.CallOption) (transport.Ack, error) 45 } 46 47 // New builds a new JSON client. 48 func New(c transport.ClientConfig) Client { 49 return jsonClient{cc: c} 50 } 51 52 func init() { 53 yarpc.RegisterClientBuilder(New) 54 } 55 56 type jsonClient struct { 57 cc transport.ClientConfig 58 } 59 60 func (c jsonClient) Call(ctx context.Context, procedure string, reqBody interface{}, resBodyOut interface{}, opts ...yarpc.CallOption) error { 61 call := encodingapi.NewOutboundCall(encoding.FromOptions(opts)...) 62 treq := transport.Request{ 63 Caller: c.cc.Caller(), 64 Service: c.cc.Service(), 65 Procedure: procedure, 66 Encoding: Encoding, 67 } 68 69 ctx, err := call.WriteToRequest(ctx, &treq) 70 if err != nil { 71 return err 72 } 73 74 encoded, err := json.Marshal(reqBody) 75 if err != nil { 76 return errors.RequestBodyEncodeError(&treq, err) 77 } 78 79 treq.Body = bytes.NewReader(encoded) 80 treq.BodySize = len(encoded) 81 82 tres, appErr := c.cc.GetUnaryOutbound().Call(ctx, &treq) 83 if tres == nil { 84 return appErr 85 } 86 87 // we want to return the appErr if it exists as this is what 88 // the previous behavior was so we deprioritize this error 89 var decodeErr error 90 if _, err = call.ReadFromResponse(ctx, tres); err != nil { 91 decodeErr = err 92 } 93 if tres.Body != nil { 94 if err := json.NewDecoder(tres.Body).Decode(resBodyOut); err != nil && decodeErr == nil { 95 decodeErr = errors.ResponseBodyDecodeError(&treq, err) 96 } 97 if err := tres.Body.Close(); err != nil && decodeErr == nil { 98 decodeErr = err 99 } 100 } 101 102 if appErr != nil { 103 return appErr 104 } 105 return decodeErr 106 } 107 108 func (c jsonClient) CallOneway(ctx context.Context, procedure string, reqBody interface{}, opts ...yarpc.CallOption) (transport.Ack, error) { 109 call := encodingapi.NewOutboundCall(encoding.FromOptions(opts)...) 110 treq := transport.Request{ 111 Caller: c.cc.Caller(), 112 Service: c.cc.Service(), 113 Procedure: procedure, 114 Encoding: Encoding, 115 } 116 117 ctx, err := call.WriteToRequest(ctx, &treq) 118 if err != nil { 119 return nil, err 120 } 121 122 var buff bytes.Buffer 123 if err := json.NewEncoder(&buff).Encode(reqBody); err != nil { 124 return nil, errors.RequestBodyEncodeError(&treq, err) 125 } 126 treq.Body = &buff 127 128 return c.cc.GetOnewayOutbound().CallOneway(ctx, &treq) 129 }