github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/webutil/chain-handler.go (about)

     1  package webutil
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  )
     8  
     9  // ChainHandler is a slight variation of http.Handler which returns a new "w" and "r", allowing for them to be changed or replaced
    10  // (and thus allowing the request context to be changed as well).
    11  type ChainHandler interface {
    12  	// FIXME: Possibly ServeHTTPChain should be used for any situation where the response is not sent -
    13  	// rather than also letting ServeHTTP pass through.
    14  	ServeHTTPChain(w http.ResponseWriter, r *http.Request) (wnext http.ResponseWriter, rnext *http.Request)
    15  }
    16  
    17  // ChainHandlerFunc adapts a function to implement ChainHandler (same pattern as http.HandlerFunc)
    18  type ChainHandlerFunc func(w http.ResponseWriter, r *http.Request) (wnext http.ResponseWriter, rnext *http.Request)
    19  
    20  func (f ChainHandlerFunc) ServeHTTPChain(w http.ResponseWriter, r *http.Request) (wnext http.ResponseWriter, rnext *http.Request) {
    21  	return f(w, r)
    22  }
    23  
    24  // NewDefaultHandlerList takes a set of handlers and returns a HandlerList with
    25  // a ContextCancelHandler and GzipHandler prepended.  (But only if handlers
    26  // does not already start with a ContextCancelHandler.)
    27  func NewDefaultHandlerList(handlers ...interface{}) HandlerList {
    28  
    29  	var firstHandler interface{}
    30  	if len(handlers) > 0 {
    31  		firstHandler = handlers[0]
    32  	}
    33  
    34  	if _, ok := firstHandler.(ContextCancelHandler); !ok {
    35  		origHandlers := handlers
    36  		handlers = make([]interface{}, 0, len(handlers)+2)
    37  		handlers = append(handlers, NewContextCancelHandler())
    38  		handlers = append(handlers, NewGzipHandler())
    39  		handlers = append(handlers, origHandlers...)
    40  	}
    41  
    42  	return HandlerList(handlers)
    43  }
    44  
    45  // HandlerList is a slice of ChainHandler or http.Handler instances.
    46  // When ServeHTTP is called each one is called in turn, trying as ChainHandler first and if that fails then http.Handler.
    47  // Will panic if you put any other type in the slice.
    48  type HandlerList []interface{}
    49  
    50  // AppendChainHandler adds a ChainHandler to the list, returning the new list.  (Type-safe wrapper around append())
    51  func (hl HandlerList) AppendChainHandler(ch ChainHandler) HandlerList {
    52  	return append(hl, ch)
    53  }
    54  
    55  // AppendHandler adds a http.Handler to the list, returning the new list.  (Type-safe wrapper around append())
    56  func (hl HandlerList) AppendHandler(h http.Handler) HandlerList {
    57  	return append(hl, h)
    58  }
    59  
    60  // ServeHTTP calls ServeHTTPChain
    61  func (hl HandlerList) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    62  	hl.ServeHTTPChain(w, r)
    63  }
    64  
    65  // ServeHTTPChainNoFlush does the same thing as ServeHTTPChain but
    66  // does not call w.Flush() at the end.
    67  func (hl HandlerList) ServeHTTPChainNoFlush(w http.ResponseWriter, r *http.Request) (wnext http.ResponseWriter, rnext *http.Request) {
    68  
    69  	for _, h := range hl {
    70  
    71  		if r.Context().Err() != nil {
    72  			break
    73  		}
    74  
    75  		// try ChainHandler first
    76  		ch, ok := h.(ChainHandler)
    77  		if ok {
    78  			w, r = ch.ServeHTTPChain(w, r)
    79  			continue
    80  		}
    81  
    82  		// otherwise http.Handler
    83  		hh, ok := h.(http.Handler)
    84  		if ok {
    85  			hh.ServeHTTP(w, r)
    86  			continue
    87  		}
    88  
    89  		panic(fmt.Errorf("item in HandlerList (type=%t, val=%+v) is not a ChainHandler nor http.Handler", h, h))
    90  	}
    91  
    92  	return w, r
    93  }
    94  
    95  // ServeHTTPChain calls each element in the list. trying as ChainHandler first and if that fails then http.Handler.
    96  // Will panic if you put any other type in the slice.
    97  func (hl HandlerList) ServeHTTPChain(w http.ResponseWriter, r *http.Request) (wnext http.ResponseWriter, rnext *http.Request) {
    98  
    99  	w, r = hl.ServeHTTPChainNoFlush(w, r)
   100  
   101  	// call flush to ensure gzip and other things get their chance to cleanup
   102  	w.(http.Flusher).Flush()
   103  
   104  	return w, r
   105  }
   106  
   107  // // WithCloseHandler returns an http.Handler that calls ServeHTTPChain and then w.(io.Closer).Close().
   108  // // You normally want to use this as your top-level Handler that is called from the HTTP server.
   109  // func (hl HandlerList) WithCloseHandler() http.Handler {
   110  // 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   111  // 		w, r = hl.ServeHTTPChain(w, r)
   112  // 		log.Printf("FIXME: we should not be using Close() here but Flush() instead")
   113  // 		w.(io.Closer).Close()
   114  // 	})
   115  // }
   116  
   117  // // FlushHandler calls Flush on the ResponseWriter.  It's important to have this at
   118  // // the end of your HandlerList so that gzip output can be flushed or other things that
   119  // // need to know when the output is written can take appropriate action.
   120  // type FlushHandler struct{}
   121  
   122  // func (h FlushHandler) ServeHTTPChain(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request) {
   123  // 	w.(http.Flusher).Flush()
   124  // 	return w, r
   125  // }
   126  
   127  // func NewFlushHandler() FlushHandler {
   128  // 	return FlushHandler{}
   129  // }
   130  
   131  // ServeHTTPChain checks h to see if it implements ChainHandler and calls ServeHTTPChain if so.  Otherwise it falls
   132  // back to http.Handler and ServeHTTP.  The returned w and r will be as returned by ServeHTTPChain or if
   133  // falling back to ServeHTTP same as input.  Will panic if h does not implement either interface.
   134  func ServeHTTPChain(h interface{}, w http.ResponseWriter, r *http.Request) (wnext http.ResponseWriter, rnext *http.Request) {
   135  
   136  	h1, ok := h.(ChainHandler)
   137  	if ok {
   138  		return h1.ServeHTTPChain(w, r)
   139  	}
   140  
   141  	h2, ok := h.(http.Handler)
   142  	if ok {
   143  		h2.ServeHTTP(w, r)
   144  		return w, r
   145  	}
   146  
   147  	panic(fmt.Errorf("ServeHTTPChain: %#v is not a valid ChainHandler or http.Handler", h))
   148  
   149  }
   150  
   151  // NewCtxSetHandler is a helper that returns a ChainHandler which assigns a static value to the request context each time
   152  // it is called.  (NOTE: If you want a dynamic value just implement ChainHandler yourself.  The point of this function
   153  // is to make static values assignable as one-liners in your configuration code, e.g. making configuration values
   154  // available in a template.)
   155  func NewCtxSetHandler(key string, value interface{}) ChainHandler {
   156  	return ChainHandlerFunc(func(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request) {
   157  		return w, r.WithContext(context.WithValue(r.Context(), key, value))
   158  	})
   159  }
   160  
   161  // TODO: implement this one:
   162  // func NewCtxMapHandler(ctxMap map[string]interface{}) *CtxMapHandler {
   163  
   164  // TODO: what about PriorityHandlerList, which is a slice of PriorityHandler structs which have a priority float 0-100
   165  // and gets sorted before use (maybe it gets sorted each time something is added?)
   166  // Also think about having a Name field or similar and the ability to find or remove based on names.  It might
   167  // be very useful to find the default provider for something by its name and make some changes to it or remove it.