github.com/alwitt/goutils@v0.6.4/req_resp_driver.go (about) 1 package goutils 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 9 "github.com/apex/log" 10 ) 11 12 // RRInboundRequestHandler callback function to process a specific inbound request 13 type RRInboundRequestHandler func( 14 ctxt context.Context, request interface{}, origMsg ReqRespMessage, 15 ) (interface{}, error) 16 17 // RRMessageParser callback function to parse request-response payload into a specific 18 // data type 19 type RRMessageParser func(rawMsg []byte) (interface{}, error) 20 21 // RequestResponseDriver helper request-response driver class to simplify RR client usage 22 type RequestResponseDriver struct { 23 Component 24 Client RequestResponseClient 25 PayloadParser RRMessageParser 26 executionMap map[reflect.Type]RRInboundRequestHandler 27 } 28 29 /* 30 InstallHandler install a handler for an inbound request 31 32 @param requestType reflect.Type - request message type 33 @param handler InboundRequestHandler - request handler callback 34 */ 35 func (d *RequestResponseDriver) InstallHandler( 36 requestType reflect.Type, handler RRInboundRequestHandler, 37 ) { 38 if d.executionMap == nil { 39 d.executionMap = map[reflect.Type]RRInboundRequestHandler{} 40 } 41 d.executionMap[requestType] = handler 42 } 43 44 /* 45 ProcessInboundRequest process inbound request 46 47 @param ctxt context.Context - execution context 48 @param msg ReqRespMessage - raw request message 49 */ 50 func (d *RequestResponseDriver) ProcessInboundRequest( 51 ctxt context.Context, msg ReqRespMessage, 52 ) error { 53 logTag := d.GetLogTagsForContext(ctxt) 54 55 // Parse the message to determine the request 56 parsed, err := d.PayloadParser(msg.Payload) 57 if err != nil { 58 log. 59 WithError(err). 60 WithFields(logTag). 61 WithField("request-sender", msg.SenderID). 62 WithField("request-id", msg.RequestID). 63 Error("Unable to parse request payload") 64 return err 65 } 66 67 // Find the associated processing handler 68 requestMsgType := reflect.TypeOf(parsed) 69 requestHandler, ok := d.executionMap[requestMsgType] 70 if !ok { 71 err := fmt.Errorf("unknown supported request type '%s'", requestMsgType) 72 log. 73 WithError(err). 74 WithFields(logTag). 75 WithField("request-sender", msg.SenderID). 76 WithField("request-id", msg.RequestID). 77 WithField("request-type", requestMsgType). 78 Error("Unable to parse request payload") 79 return err 80 } 81 82 // Process request 83 response, err := requestHandler(ctxt, parsed, msg) 84 if err != nil { 85 log. 86 WithError(err). 87 WithFields(logTag). 88 WithField("request-sender", msg.SenderID). 89 WithField("request-id", msg.RequestID). 90 WithField("request-type", requestMsgType). 91 Error("Request processing failed") 92 return err 93 } 94 95 // Build the response 96 respMsg, err := json.Marshal(&response) 97 if err != nil { 98 log. 99 WithError(err). 100 WithFields(logTag). 101 WithField("request-sender", msg.SenderID). 102 WithField("request-id", msg.RequestID). 103 WithField("request-type", requestMsgType). 104 Error("Failed to prepare response") 105 return err 106 } 107 108 // Send the response 109 if err := d.Client.Respond(ctxt, msg, respMsg, nil, false); err != nil { 110 log. 111 WithError(err). 112 WithFields(logTag). 113 WithField("request-sender", msg.SenderID). 114 WithField("request-id", msg.RequestID). 115 WithField("request-type", requestMsgType). 116 Error("Failed to send response") 117 return err 118 } 119 120 return nil 121 } 122 123 /* 124 MakeRequest wrapper function to marking an outbound request 125 126 @param ctxt context.Context - execution context 127 @param requestInstanceName string - descriptive name for this request to identify it in logs 128 @param targetID string - request target ID 129 @param requestMsg []byte - request payload 130 @param requestMeta map[string]string - request's associated metadata 131 @param callParam RequestCallParam - request call parameters 132 @returns response payload or payloads if multiple responses expected 133 */ 134 func (d *RequestResponseDriver) MakeRequest( 135 ctxt context.Context, 136 requestInstanceName string, 137 targetID string, 138 requestMsg []byte, 139 requestMeta map[string]string, 140 callParam RequestCallParam, 141 ) ([]interface{}, error) { 142 logTags := d.GetLogTagsForContext(ctxt) 143 144 // Handler to receive the message from control 145 respReceiveChan := make(chan ReqRespMessage, callParam.ExpectedResponsesCount+1) 146 respReceiveCB := func(ctxt context.Context, msg ReqRespMessage) error { 147 log. 148 WithFields(logTags). 149 WithField("request-instance", requestInstanceName). 150 Debug("Received response") 151 respReceiveChan <- msg 152 return nil 153 } 154 // Handler in case of request timeout 155 timeoutChan := make(chan error, 2) 156 timeoutCB := func(ctxt context.Context) error { 157 err := fmt.Errorf(requestInstanceName) 158 log. 159 WithError(err). 160 WithFields(logTags). 161 WithField("request-instance", requestInstanceName). 162 Debug("No responses received before timeout") 163 timeoutChan <- err 164 return nil 165 } 166 167 // Update call parameter locally defined callbacks 168 callParam.RespHandler = respReceiveCB 169 callParam.TimeoutHandler = timeoutCB 170 171 var rawResponse ReqRespMessage 172 173 log. 174 WithFields(logTags). 175 WithField("request-instance", requestInstanceName). 176 Debug("Sending request") 177 // Make the call 178 requestID, err := d.Client.Request(ctxt, targetID, requestMsg, requestMeta, callParam) 179 if err != nil { 180 log. 181 WithError(err). 182 WithFields(logTags). 183 WithField("request-id", requestID). 184 WithField("request-instance", requestInstanceName). 185 Error("Failed to send request") 186 return nil, err 187 } 188 log. 189 WithFields(logTags). 190 WithField("request-id", requestID). 191 WithField("request-instance", requestInstanceName). 192 Debug("Sent request. Waiting for response...") 193 194 results := []interface{}{} 195 // Wait for responses from target/s 196 for itr := 0; itr < callParam.ExpectedResponsesCount; itr++ { 197 select { 198 // Execution context timeout 199 case <-ctxt.Done(): 200 return nil, fmt.Errorf("execution context timed out") 201 202 // Request timeout 203 case <-timeoutChan: 204 err = fmt.Errorf("timeout channel returned erroneous results") 205 log. 206 WithError(err). 207 WithFields(logTags). 208 WithField("request-id", requestID). 209 WithField("request-instance", requestInstanceName). 210 Error("Request failed") 211 return nil, err 212 213 // Response successful 214 case resp, ok := <-respReceiveChan: 215 if !ok { 216 err := fmt.Errorf("response channel returned erroneous results") 217 log. 218 WithError(err). 219 WithFields(logTags). 220 WithField("request-id", requestID). 221 WithField("request-instance", requestInstanceName). 222 Error("Request failed") 223 return nil, err 224 } 225 rawResponse = resp 226 } 227 228 log. 229 WithFields(logTags). 230 WithField("request-id", requestID). 231 WithField("request-instance", requestInstanceName). 232 WithField("raw-msg", string(rawResponse.Payload)). 233 Debug("Raw response payload") 234 235 // Parse the response 236 parsed, err := d.PayloadParser(rawResponse.Payload) 237 if err != nil { 238 log. 239 WithError(err). 240 WithFields(logTags). 241 WithField("request-id", requestID). 242 WithField("request-instance", requestInstanceName). 243 Error("Unable to parse response") 244 return nil, err 245 } 246 results = append(results, parsed) 247 } 248 249 return results, nil 250 }