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  }