github.com/vmware/transport-go@v1.3.4/plank/pkg/server/endpointer_handler_factory.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/vmware/transport-go/model"
     8  	"github.com/vmware/transport-go/plank/utils"
     9  	"github.com/vmware/transport-go/service"
    10  	"net/http"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  // buildEndpointHandler builds a http.HandlerFunc that wraps Transport Bus operations in an HTTP request-response cycle.
    16  // service channel, request builder and rest bridge timeout are passed as parameters.
    17  func (ps *platformServer) buildEndpointHandler(svcChannel string, reqBuilder service.RequestBuilder, restBridgeTimeout time.Duration, msgChan chan *model.Message) http.HandlerFunc {
    18  	return func(w http.ResponseWriter, r *http.Request) {
    19  		defer func() {
    20  			if r := recover(); r != nil {
    21  				utils.Log.Errorln(r)
    22  				http.Error(w, "Internal Server Error", 500)
    23  			}
    24  		}()
    25  
    26  		// set context that expires after the provided amount of time in restBridgeTimeout to prevent requests from hanging forever
    27  		ctx, cancelFn := context.WithTimeout(context.Background(), restBridgeTimeout)
    28  		defer cancelFn()
    29  
    30  		// relay the request to transport channel
    31  		reqModel := reqBuilder(w, r)
    32  		err := ps.eventbus.SendRequestMessage(svcChannel, reqModel, reqModel.Id)
    33  
    34  		// get a response from the channel, render the results using ResponseWriter and log the data/error
    35  		// to the console as well.
    36  		select {
    37  		case <-ctx.Done():
    38  			http.Error(
    39  				w,
    40  				fmt.Sprintf("No response received from service channel in %s, request timed out", restBridgeTimeout.String()), 500)
    41  		case msg := <-msgChan:
    42  			if msg.Error != nil {
    43  				utils.Log.WithError(msg.Error).Errorf(
    44  					"Error received from channel %s:", svcChannel)
    45  				http.Error(w, msg.Error.Error(), 500)
    46  			} else {
    47  				// only send the actual user payloadChannel not wrapper information
    48  				response := msg.Payload.(*model.Response)
    49  				var respBody interface{}
    50  				if response.Error {
    51  					if response.Payload != nil {
    52  						respBody = response.Payload
    53  					} else {
    54  						respBody = response
    55  					}
    56  				} else {
    57  					respBody = response.Payload
    58  				}
    59  
    60  				// if our Message is an error and it has a code, lets send that back to the client.
    61  				if response.Error {
    62  
    63  					// we have to set the headers for the error response
    64  					for k, v := range response.Headers {
    65  						w.Header().Set(k, v)
    66  					}
    67  
    68  					// deal with the response body now, if set.
    69  					n, e := json.Marshal(respBody)
    70  					if e != nil {
    71  
    72  						w.WriteHeader(response.ErrorCode)
    73  						w.Write([]byte(response.ErrorMessage))
    74  						return
    75  
    76  					} else {
    77  						w.WriteHeader(response.ErrorCode)
    78  						w.Write(n)
    79  						return
    80  					}
    81  				} else {
    82  					// if the response has headers, set those headers. particularly if you're sending around
    83  					// byte array data for things like zip files etc.
    84  					for k, v := range response.Headers {
    85  						w.Header().Set(k, v)
    86  						if strings.ToLower(k) == "content-type" {
    87  							respBody, err = utils.ConvertInterfaceToByteArray(v, respBody)
    88  						}
    89  					}
    90  
    91  					var respBodyBytes []byte
    92  					// ensure respBody is properly converted to a byte slice as Content-Type header might not be
    93  					// set in the request and the restBody could be in a format that is not a byte slice.
    94  					respBodyBytes, err = ensureResponseInByteSlice(respBody)
    95  
    96  					// write the non-error payload back.
    97  					if _, err = w.Write(respBodyBytes); err != nil {
    98  						utils.Log.WithError(err).Errorf("Error received from channel %s:", svcChannel)
    99  						http.Error(w, err.Error(), 500)
   100  					}
   101  				}
   102  			}
   103  		}
   104  	}
   105  }