github.com/xmidt-org/webpa-common@v1.11.9/xhttp/fanout/requestResponse.go (about)

     1  package fanout
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"net/textproto"
     7  	"strings"
     8  
     9  	"github.com/gorilla/mux"
    10  	"github.com/xmidt-org/webpa-common/xhttp"
    11  )
    12  
    13  // FanoutRequestFunc is invoked to build a fanout request.  It can transfer information from the original request,
    14  // set the body, update the context, etc.  This is the analog of go-kit's RequestFunc.
    15  type FanoutRequestFunc func(ctx context.Context, original, fanout *http.Request, body []byte) (context.Context, error)
    16  
    17  // ForwardBody creates a FanoutRequestFunc that sends the original request's body to each fanout.
    18  // If followRedirects is true, this function also sets fanout.GetBody so that the same body is read for redirects.
    19  //
    20  // This function also sets the ContentLength and Content-Type header appropriately.
    21  func ForwardBody(followRedirects bool) FanoutRequestFunc {
    22  	return func(ctx context.Context, original, fanout *http.Request, originalBody []byte) (context.Context, error) {
    23  		fanout.ContentLength = int64(len(originalBody))
    24  		fanout.Body = nil
    25  		fanout.GetBody = nil
    26  		fanout.Header.Del("Content-Type")
    27  
    28  		if len(originalBody) > 0 {
    29  			fanout.Header.Set("Content-Type", original.Header.Get("Content-Type"))
    30  			body, getBody := xhttp.NewRewindBytes(originalBody)
    31  			fanout.Body = body
    32  			if followRedirects {
    33  				fanout.GetBody = getBody
    34  			}
    35  		}
    36  
    37  		return ctx, nil
    38  	}
    39  }
    40  
    41  // ForwardHeaders creates a FanoutRequestFunc that copies headers from the original request onto each fanout request
    42  func ForwardHeaders(headers ...string) FanoutRequestFunc {
    43  	canonicalizedHeaders := make([]string, len(headers))
    44  	for i := 0; i < len(headers); i++ {
    45  		canonicalizedHeaders[i] = textproto.CanonicalMIMEHeaderKey(headers[i])
    46  	}
    47  
    48  	return func(ctx context.Context, original, fanout *http.Request, _ []byte) (context.Context, error) {
    49  		for _, key := range canonicalizedHeaders {
    50  			if values := original.Header[key]; len(values) > 0 {
    51  				fanout.Header[key] = append(fanout.Header[key], values...)
    52  			}
    53  		}
    54  
    55  		return ctx, nil
    56  	}
    57  }
    58  
    59  // UsePath sets a constant URI path for every fanout request.  Essentially, this replaces the original URL's
    60  // Path with the configured value.
    61  func UsePath(path string) FanoutRequestFunc {
    62  	return func(ctx context.Context, original, fanout *http.Request, _ []byte) (context.Context, error) {
    63  		fanout.URL.Path = path
    64  		fanout.URL.RawPath = ""
    65  		return ctx, nil
    66  	}
    67  }
    68  
    69  // ForwardVariableAsHeader returns a request function that copies the value of a gorilla/mux path variable
    70  // from the original HTTP request into an HTTP header on each fanout request.
    71  //
    72  // The fanout request will always have the given header.  If no path variable is supplied (or no path variables
    73  // are found), the fanout request will have the header associated with an empty string.
    74  func ForwardVariableAsHeader(variable, header string) FanoutRequestFunc {
    75  	return func(ctx context.Context, original, fanout *http.Request, _ []byte) (context.Context, error) {
    76  		variables := mux.Vars(original)
    77  		if len(variables) > 0 {
    78  			fanout.Header.Add(header, variables[variable])
    79  		} else {
    80  			fanout.Header.Add(header, "")
    81  		}
    82  
    83  		return ctx, nil
    84  	}
    85  }
    86  
    87  // FanoutResponseFunc is a strategy applied to the termination fanout response.
    88  type FanoutResponseFunc func(ctx context.Context, response http.ResponseWriter, result Result) context.Context
    89  
    90  // ReturnHeaders copies zero or more headers from the fanout response into the top-level HTTP response.
    91  func ReturnHeaders(headers ...string) FanoutResponseFunc {
    92  	canonicalizedHeaders := make([]string, len(headers))
    93  	for i := 0; i < len(headers); i++ {
    94  		canonicalizedHeaders[i] = textproto.CanonicalMIMEHeaderKey(headers[i])
    95  	}
    96  
    97  	return func(ctx context.Context, response http.ResponseWriter, result Result) context.Context {
    98  		if result.Response != nil {
    99  			header := response.Header()
   100  			for _, key := range canonicalizedHeaders {
   101  				if values := result.Response.Header[key]; len(values) > 0 {
   102  					header[key] = append(header[key], values...)
   103  				}
   104  			}
   105  		}
   106  
   107  		return ctx
   108  	}
   109  }
   110  
   111  // ReturnHeadersWithPrefix copies zero or more headers from the fanout where the headerPrefix is matched in the response into the top-level HTTP response.
   112  func ReturnHeadersWithPrefix(headerPrefixs ...string) FanoutResponseFunc {
   113  	canonicalizedHeaders := make([]string, len(headerPrefixs))
   114  	for i := 0; i < len(headerPrefixs); i++ {
   115  		canonicalizedHeaders[i] = textproto.CanonicalMIMEHeaderKey(headerPrefixs[i])
   116  	}
   117  
   118  	return func(ctx context.Context, response http.ResponseWriter, result Result) context.Context {
   119  		if result.Response != nil {
   120  			header := response.Header()
   121  			for _, prefix := range canonicalizedHeaders {
   122  				for key, results := range result.Response.Header {
   123  					if strings.HasPrefix(key, prefix) && len(results) > 0 {
   124  						header[key] = append(header[key], results...)
   125  					}
   126  				}
   127  
   128  			}
   129  		}
   130  
   131  		return ctx
   132  	}
   133  }