github.com/epsagon/epsagon-go@v1.39.0/wrappers/net/http/handler_wrapper.go (about)

     1  package epsagonhttp
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"runtime/debug"
     9  
    10  	"github.com/epsagon/epsagon-go/epsagon"
    11  	"github.com/epsagon/epsagon-go/protocol"
    12  	"github.com/epsagon/epsagon-go/tracer"
    13  )
    14  
    15  // HandlerFunction is a generic http handler function
    16  type HandlerFunction func(http.ResponseWriter, *http.Request)
    17  
    18  func processRawQuery(urlObj *url.URL, wrapperTracer tracer.Tracer) string {
    19  	if urlObj == nil {
    20  		return ""
    21  	}
    22  	processed, err := json.Marshal(urlObj.Query())
    23  	if err != nil {
    24  		wrapperTracer.AddException(&protocol.Exception{
    25  			Type:      "trigger-creation",
    26  			Message:   fmt.Sprintf("Failed to serialize query params %s", urlObj.RawQuery),
    27  			Traceback: string(debug.Stack()),
    28  			Time:      tracer.GetTimestamp(),
    29  		})
    30  		return ""
    31  	}
    32  	return string(processed)
    33  }
    34  
    35  // CreateHTTPTriggerEvent creates an HTTP trigger event
    36  func CreateHTTPTriggerEvent(wrapperTracer tracer.Tracer, request *http.Request, resourceName string) *protocol.Event {
    37  	name := resourceName
    38  	if len(name) == 0 {
    39  		name = request.Host
    40  	}
    41  	event := &protocol.Event{
    42  		Id:        "",
    43  		Origin:    "trigger",
    44  		StartTime: tracer.GetTimestamp(),
    45  		Resource: &protocol.Resource{
    46  			Name:      name,
    47  			Type:      "http",
    48  			Operation: request.Method,
    49  			Metadata: map[string]string{
    50  				"path": request.URL.Path,
    51  			},
    52  		},
    53  	}
    54  	if !wrapperTracer.GetConfig().MetadataOnly {
    55  		event.Resource.Metadata["query_string_parameters"] = processRawQuery(
    56  			request.URL, wrapperTracer)
    57  		headers, body := epsagon.ExtractRequestData(request)
    58  		event.Resource.Metadata["request_headers"] = headers
    59  		event.Resource.Metadata["request_body"] = body
    60  	}
    61  	return event
    62  }
    63  
    64  // WrapHandleFunc wraps a generic http.HandleFunc handler function with Epsagon
    65  // Last two optional paramerts are the name of the handler (will be the resource name of the events) and an optional hardcoded hostname
    66  func WrapHandleFunc(
    67  	config *epsagon.Config, handler HandlerFunction, names ...string) HandlerFunction {
    68  	var hostName, handlerName string
    69  	if config == nil {
    70  		config = &epsagon.Config{}
    71  	}
    72  	if len(names) >= 1 {
    73  		handlerName = names[0]
    74  	}
    75  	if len(names) >= 2 {
    76  		hostName = names[1]
    77  	}
    78  	return func(rw http.ResponseWriter, request *http.Request) {
    79  		wrapperTracer := tracer.CreateTracer(&config.Config)
    80  		wrapperTracer.Start()
    81  		defer wrapperTracer.Stop()
    82  
    83  		triggerEvent := CreateHTTPTriggerEvent(
    84  			wrapperTracer, request, hostName)
    85  		wrapperTracer.AddEvent(triggerEvent)
    86  		triggerEvent.Resource.Metadata["status_code"] = "200"
    87  		defer func() {
    88  			if userError := recover(); userError != nil {
    89  				triggerEvent.Resource.Metadata["status_code"] = "500"
    90  				panic(userError)
    91  			}
    92  		}()
    93  
    94  		newRequest := request.WithContext(
    95  			epsagon.ContextWithTracer(wrapperTracer, request.Context()))
    96  
    97  		if !config.MetadataOnly {
    98  			rw = &WrappedResponseWriter{
    99  				ResponseWriter: rw,
   100  				resource:       triggerEvent.Resource,
   101  			}
   102  		}
   103  		defer func() {
   104  			if wrappedResponseWriter, ok := rw.(*WrappedResponseWriter); ok {
   105  				wrappedResponseWriter.UpdateResource()
   106  			}
   107  		}()
   108  
   109  		wrapper := epsagon.WrapGenericFunction(
   110  			handler, config, wrapperTracer, false, handlerName,
   111  		)
   112  		wrapper.Call(rw, newRequest)
   113  	}
   114  }