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  }