github.com/influx6/npkg@v0.8.8/nhttp/middleware.go (about) 1 package nhttp 2 3 import ( 4 "net/http" 5 "strings" 6 7 "github.com/dimfeld/httptreemux" 8 9 "github.com/gorilla/mux" 10 ) 11 12 // Handler exposes a method to handle a giving http request 13 // but allowing the exposure of any error occuring. 14 type Handler interface { 15 Handle(res http.ResponseWriter, req *http.Request) error 16 } 17 18 // HandlerFunc implements Handler interface. 19 type HandlerFunc func(res http.ResponseWriter, req *http.Request) error 20 21 func (h HandlerFunc) Handle(res http.ResponseWriter, req *http.Request) error { 22 return h(res, req) 23 } 24 25 func (h HandlerFunc) ServeHTTP(res http.ResponseWriter, req *http.Request) { 26 _ = h(res, req) 27 } 28 29 // HandlerMW defines a function which wraps a provided http.handlerFunc 30 // which encapsulates the original for a underline operation. 31 type HandlerMW func(http.Handler, ...Middleware) http.Handler 32 33 // HandlerFuncMW defines a function which wraps a provided http.handlerFunc 34 // which encapsulates the original for a underline operation. 35 type HandlerFuncMW func(http.Handler, ...Middleware) http.HandlerFunc 36 37 // TreeHandler defines a function type for the httptreemux.Handler type. 38 type TreeHandler func(http.ResponseWriter, *http.Request, map[string]string) 39 40 // Middleware defines a function type which is used to create a chain 41 // of handlers for processing giving request. 42 type Middleware func(next http.Handler) http.Handler 43 44 // IdentityMW defines a http.Handler function that returns a the next http.Handler passed to it. 45 func IdentityMW(next http.Handler) http.Handler { 46 return next 47 } 48 49 // Combine combines multiple Middleware to return a single http.Handler. 50 func Combine(mos ...Middleware) http.Handler { 51 return CombineMore(mos...)(IdentityHandler()) 52 } 53 54 // CombineTwo combines two middleware and returns a single http.Handler. 55 func CombineTwo(mo, mi Middleware) Middleware { 56 return func(next http.Handler) http.Handler { 57 handler := mo(mi(IdentityHandler())) 58 59 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 handler.ServeHTTP(w, r) 61 62 if next != nil { 63 next.ServeHTTP(w, r) 64 } 65 }) 66 } 67 } 68 69 // CombineMore combines multiple Middleware to return a new Middleware. 70 func CombineMore(mos ...Middleware) Middleware { 71 var initial Middleware 72 if len(mos) == 0 { 73 initial = IdentityMW 74 } 75 76 if len(mos) == 1 { 77 return mos[0] 78 } 79 80 for _, mw := range mos { 81 if initial == nil { 82 initial = mw 83 continue 84 } 85 86 initial = CombineTwo(initial, mw) 87 } 88 89 return initial 90 } 91 92 // ContextHandler defines a function type which accepts a function type. 93 type ContextHandler func(*Ctx) error 94 95 // ErrorResponse defines a function which receives the possible error that 96 // occurs from a ContextHandler and applies necessary response as needed. 97 type ErrorResponse func(error, *Ctx) 98 99 // ErrorHandler defines a function type which sets giving response to a Response object. 100 type ErrorHandler func(error, *Ctx) error 101 102 // HandlerToContextHandler returns a new ContextHandler from a http.Handler. 103 func HandlerToContextHandler(handler http.Handler) ContextHandler { 104 return func(context *Ctx) error { 105 handler.ServeHTTP(context.Response(), context.Request()) 106 return nil 107 } 108 } 109 110 // Tree returns httptreemux.Handler for use with a httptreemux router. 111 func Tree(ops []Options, errHandler ErrorResponse, handler ContextHandler, before []Middleware, after []Middleware) httptreemux.HandlerFunc { 112 beforeMW := Combine(before...) 113 afterMW := Combine(after...) 114 115 return func(w http.ResponseWriter, r *http.Request, params map[string]string) { 116 beforeMW.ServeHTTP(w, r) 117 defer afterMW.ServeHTTP(w, r) 118 119 ctx := NewContext(ops...) 120 ctx.Reset(r, &Response{Writer: w}) 121 defer ctx.Reset(nil, nil) 122 defer ctx.ClearFlashMessages() 123 124 for key, val := range params { 125 ctx.AddParam(key, val) 126 } 127 128 if err := handler(ctx); err != nil && errHandler != nil { 129 errHandler(err, ctx) 130 return 131 } 132 } 133 } 134 135 // HandlerWith defines a function which will return a http.Handler from a ErrorHandler, 136 // and a ContextHandler. If the middleware set is provided then it's executed 137 func HandlerWith(ops []Options, errHandler ErrorResponse, handle ContextHandler, before []Middleware, after []Middleware) http.Handler { 138 beforeMW := Combine(before...) 139 afterMW := Combine(after...) 140 141 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 142 beforeMW.ServeHTTP(w, r) 143 defer afterMW.ServeHTTP(w, r) 144 145 var nctx = NewContext(ops...) 146 if err := nctx.Reset(r, &Response{Writer: w}); err != nil { 147 if errHandler != nil { 148 errHandler(err, nctx) 149 return 150 } 151 } 152 153 if err := handle(nctx); err != nil { 154 if errHandler != nil { 155 errHandler(err, nctx) 156 } 157 } 158 }) 159 } 160 161 // IdentityHandler returns a non-op http.Handler 162 func IdentityHandler() http.Handler { 163 return http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}) 164 } 165 166 // NetworkAuthenticationNeeded implements a http.Handler which returns http.StatusNetworkAuthenticationRequired always. 167 func NetworkAuthenticationNeeded(ctx *Ctx) error { 168 ctx.Status(http.StatusNetworkAuthenticationRequired) 169 return nil 170 } 171 172 // NoContentRequest implements a http.Handler which returns http.StatusNoContent always. 173 func NoContentRequest(ctx *Ctx) error { 174 ctx.Status(http.StatusNoContent) 175 return nil 176 } 177 178 // OKRequest implements a http.Handler which returns http.StatusOK always. 179 func OKRequest(ctx *Ctx) error { 180 ctx.Status(http.StatusOK) 181 return nil 182 } 183 184 // BadRequestWithError implements a http.Handler which returns http.StatusBagRequest always. 185 func BadRequestWithError(err error, ctx *Ctx) error { 186 if err != nil { 187 if httperr, ok := err.(HTTPError); ok { 188 http.Error(ctx.Response(), httperr.Error(), httperr.Code) 189 return nil 190 } 191 http.Error(ctx.Response(), err.Error(), http.StatusBadRequest) 192 } 193 return nil 194 } 195 196 // BadRequest implements a http.Handler which returns http.StatusBagRequest always. 197 func BadRequest(ctx *Ctx) error { 198 ctx.Status(http.StatusBadRequest) 199 return nil 200 } 201 202 // NotFound implements a http.Handler which returns http.StatusNotFound always. 203 func NotFound(ctx *Ctx) error { 204 ctx.Status(http.StatusNotFound) 205 return nil 206 } 207 208 // StripPrefix returns a middleware which strips the URI of the request of 209 // the provided Prefix. All prefix must come in /prefix/ format. 210 func StripPrefix(prefix string) Middleware { 211 if !strings.HasPrefix(prefix, "/") { 212 prefix = "/" + prefix 213 } 214 215 return func(next http.Handler) http.Handler { 216 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 217 reqURL := r.URL.Path 218 if !strings.HasPrefix(reqURL, "/") { 219 reqURL = "/" + reqURL 220 } 221 222 r.URL.Path = strings.TrimPrefix(reqURL, prefix) 223 if next != nil { 224 next.ServeHTTP(w, r) 225 } 226 }) 227 } 228 } 229 230 // GorillaMuxVars retrieves the parameter lists from the underline 231 // variable map provided by the gorilla mux router and stores those 232 // into the context. 233 func GorillaMuxVars(ctx *Ctx) error { 234 for k, v := range mux.Vars(ctx.Request()) { 235 ctx.AddParam(k, v) 236 } 237 return nil 238 } 239 240 // HTTPRedirect returns a http.Handler which always redirect to the given path. 241 func HTTPRedirect(to string, code int) ContextHandler { 242 return func(ctx *Ctx) error { 243 return ctx.Redirect(code, to) 244 } 245 } 246 247 // OnDone calls the next http.Handler after the condition handler returns without error. 248 func OnDone(condition ContextHandler, nexts ...ContextHandler) ContextHandler { 249 if len(nexts) == 0 { 250 return condition 251 } 252 253 return func(c *Ctx) error { 254 if err := condition(c); err != nil { 255 return err 256 } 257 258 for _, next := range nexts { 259 if err := next(c); err != nil { 260 return err 261 } 262 } 263 return nil 264 } 265 } 266 267 // OnNoError calls the next http.Handler after the condition handler returns no error. 268 func OnNoError(condition ContextHandler, action ContextHandler) ContextHandler { 269 return func(c *Ctx) error { 270 if err := condition(c); err != nil { 271 return err 272 } 273 274 return action(c) 275 } 276 } 277 278 // OnError calls the next ContextHandler after the condition handler returns an error. 279 func OnError(condition ContextHandler, errorAction ContextHandler) ContextHandler { 280 return func(c *Ctx) error { 281 if err := condition(c); err != nil { 282 return errorAction(c) 283 } 284 285 return nil 286 } 287 } 288 289 // OnErrorAccess calls the next ErrorHandler after the condition handler returns an error. 290 func OnErrorAccess(condition ContextHandler, errorAction ErrorHandler) ContextHandler { 291 return func(c *Ctx) error { 292 if err := condition(c); err != nil { 293 return errorAction(err, c) 294 } 295 296 return nil 297 } 298 } 299 300 // HTTPConditionFunc retusn a handler where a ContextHandler is used as a condition where if the handler 301 // returns an error then the errorAction is called else the noerrorAction gets called with 302 // context. This allows you create a binary switch where the final action is based on the 303 // success of the first. Generally if you wish to pass info around, use the context.Bag() 304 // to do so. 305 func HTTPConditionFunc(condition ContextHandler, noerrorAction, errorAction ContextHandler) ContextHandler { 306 return func(ctx *Ctx) error { 307 if err := condition(ctx); err != nil { 308 return errorAction(ctx) 309 } 310 return noerrorAction(ctx) 311 } 312 } 313 314 // HTTPConditionErrorFunc returns a handler where a condition ContextHandler is called whoes result if with an error 315 // is passed to the errorAction for execution else using the noerrorAction. Differs from HTTPConditionFunc 316 // due to the assess to the error value. 317 func HTTPConditionErrorFunc(condition ContextHandler, noerrorAction ContextHandler, errorAction ErrorHandler) ContextHandler { 318 return func(ctx *Ctx) error { 319 if err := condition(ctx); err != nil { 320 return errorAction(err, ctx) 321 } 322 return noerrorAction(ctx) 323 } 324 } 325 326 // ErrorsAsResponse returns a ContextHandler which will always write out any error that 327 // occurs as the response for a request if any occurs. 328 func ErrorsAsResponse(code int, next ContextHandler) ContextHandler { 329 return func(ctx *Ctx) error { 330 if err := next(ctx); err != nil { 331 if httperr, ok := err.(HTTPError); ok { 332 http.Error(ctx.Response(), httperr.Error(), httperr.Code) 333 return err 334 } 335 336 if code <= 0 { 337 code = http.StatusBadRequest 338 } 339 340 http.Error(ctx.Response(), err.Error(), code) 341 return err 342 } 343 return nil 344 } 345 } 346 347 // HTTPConditionsFunc returns a ContextHandler where if an error occurs would match the returned 348 // error with a ContextHandler to be runned if the match is found. 349 func HTTPConditionsFunc(condition ContextHandler, noerrAction ContextHandler, errCons ...MatchableContextHandler) ContextHandler { 350 return func(ctx *Ctx) error { 351 if err := condition(ctx); err != nil { 352 for _, errcon := range errCons { 353 if errcon.Match(err) { 354 return errcon.Handle(ctx) 355 } 356 } 357 return err 358 } 359 return noerrAction(ctx) 360 } 361 } 362 363 // MatchableContextHandler defines a condition which matches expected error 364 // for performing giving action. 365 type MatchableContextHandler interface { 366 Match(error) bool 367 Handle(*Ctx) error 368 } 369 370 // Matchable returns MatchableContextHandler using provided arguments. 371 func Matchable(err error, fn ContextHandler) MatchableContextHandler { 372 return errorConditionImpl{ 373 Err: err, 374 Fn: fn, 375 } 376 } 377 378 // errorConditionImpl defines a type which sets the error that occurs and the handler to be called 379 // for such an error. 380 type errorConditionImpl struct { 381 Err error 382 Fn ContextHandler 383 } 384 385 // Handler calls the internal http.Handler with provided Ctx returning error. 386 func (ec errorConditionImpl) Handle(ctx *Ctx) error { 387 return ec.Fn(ctx) 388 } 389 390 // Match validates the provided error matches expected error. 391 func (ec errorConditionImpl) Match(err error) bool { 392 return ec.Err == err 393 } 394 395 // MatchableFunction returns MatchableContextHandler using provided arguments. 396 func MatchableFunction(err func(error) bool, fn ContextHandler) MatchableContextHandler { 397 return fnErrorCondition{ 398 Err: err, 399 Fn: fn, 400 } 401 } 402 403 // fnErrorCondition defines a type which sets the error that occurs and the handler to be called 404 // for such an error. 405 type fnErrorCondition struct { 406 Fn ContextHandler 407 Err func(error) bool 408 } 409 410 // http.Handler calls the internal http.Handler with provided Ctx returning error. 411 func (ec fnErrorCondition) Handle(ctx *Ctx) error { 412 return ec.Fn(ctx) 413 } 414 415 // Match validates the provided error matches expected error. 416 func (ec fnErrorCondition) Match(err error) bool { 417 return ec.Err(err) 418 }