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.