go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/web/app.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package web
     9  
    10  import (
    11  	"context"
    12  	"net/http"
    13  	"time"
    14  
    15  	"go.charczuk.com/sdk/errutil"
    16  	"go.charczuk.com/sdk/viewutil"
    17  )
    18  
    19  // New returns a new app with defaults configured and applies
    20  // a given set of options to it.
    21  //
    22  // You can alternately use the `app := new(App)` form but it's
    23  // highly recommended to use this constructor.
    24  func New() *App {
    25  	app := new(App)
    26  	app.Views.FuncMap = viewutil.Funcs
    27  	app.NotFoundHandler = app.ActionHandler(defaultNotFoundHandler)
    28  	app.PanicAction = defaultPanicAction
    29  	return app
    30  }
    31  
    32  func defaultNotFoundHandler(ctx Context) Result {
    33  	return AcceptedProvider(ctx).NotFound()
    34  }
    35  
    36  func defaultPanicAction(ctx Context, r any) Result {
    37  	return AcceptedProvider(ctx).InternalError(errutil.New(r))
    38  }
    39  
    40  // App is the container type for the application and its resources.
    41  //
    42  // Typical usage is just to instantiate it with a bare constructor:
    43  //
    44  //	app := new(web.App)
    45  //	app.Get("/", func(ctx web.Context) web.Result {
    46  //		return &web.JSONResult{StatusCode: http.StatusOK, Response: "OK!" }
    47  //	}
    48  type App struct {
    49  	Auth
    50  	RouteTree
    51  	Server
    52  
    53  	Views        Views
    54  	Localization Localization
    55  
    56  	BaseURL     string
    57  	Headers     http.Header
    58  	PanicAction PanicAction
    59  
    60  	middleware []Middleware
    61  	onRequest  []func(RequestEvent)
    62  	onError    []func(Context, error)
    63  }
    64  
    65  // RegisterControllers registers controllers.
    66  func (a *App) RegisterControllers(controllers ...Controller) {
    67  	for _, c := range controllers {
    68  		c.Register(a)
    69  	}
    70  }
    71  
    72  // RegisterMiddleware registers global middleware.
    73  func (a *App) RegisterMiddleware(middleware ...Middleware) {
    74  	a.middleware = append(a.middleware, middleware...)
    75  }
    76  
    77  // RegisterConfig registers the config.
    78  func (a *App) RegisterConfig(cfg Config) {
    79  	cfg.ApplyTo(a)
    80  }
    81  
    82  // RegisterLoggerListeners registers a logger with the listen,
    83  // request, and error handler lists.
    84  func (a *App) RegisterLoggerListeners(log Logger) {
    85  	a.RegisterOnListen(LogOnListen(a, log))
    86  	a.RegisterOnRequest(LogOnRequest(log))
    87  	a.RegisterOnError(LogOnError(log))
    88  }
    89  
    90  // RegisterOnRequest adds an on request hook.
    91  func (a *App) RegisterOnRequest(fn func(RequestEvent)) {
    92  	a.onRequest = append(a.onRequest, fn)
    93  }
    94  
    95  // RegisterOnError adds an on request hook.
    96  func (a *App) RegisterOnError(fn func(Context, error)) {
    97  	a.onError = append(a.onError, fn)
    98  }
    99  
   100  // Get registers a GET request route handler.
   101  //
   102  // To add additional middleware, use  `web.NextMiddleware(action, ...middleware)`.
   103  func (a *App) Get(path string, action Action) {
   104  	a.HandleAction(http.MethodGet, path, action)
   105  }
   106  
   107  // Options registers a OPTIONS request route handler.
   108  //
   109  // To add additional middleware, use  `web.NextMiddleware(action, ...middleware)`.
   110  func (a *App) Options(path string, action Action) {
   111  	a.HandleAction(http.MethodOptions, path, action)
   112  }
   113  
   114  // Head registers a HEAD request route handler.
   115  //
   116  // To add additional middleware, use  `web.NextMiddleware(action, ...middleware)`.
   117  func (a *App) Head(path string, action Action) {
   118  	a.HandleAction(http.MethodHead, path, action)
   119  }
   120  
   121  // Put registers a PUT request route handler.
   122  //
   123  // To add additional middleware, use `web.NextMiddleware(action, ...middleware)`.
   124  func (a *App) Put(path string, action Action) {
   125  	a.HandleAction(http.MethodPut, path, action)
   126  }
   127  
   128  // Patch registers a PATCH request route handler.
   129  //
   130  // To add additional middleware, use `web.NextMiddleware(action, ...middleware)`.
   131  func (a *App) Patch(path string, action Action) {
   132  	a.HandleAction(http.MethodPatch, path, action)
   133  }
   134  
   135  // Post registers a POST request route handler.
   136  //
   137  // To add additional middleware, use  `web.NextMiddleware(action, ...middleware)`.
   138  func (a *App) Post(path string, action Action) {
   139  	a.HandleAction(http.MethodPost, path, action)
   140  }
   141  
   142  // Delete registers a DELETE request route handler.
   143  //
   144  // To add additional middleware, use  `web.NextMiddleware(action, ...middleware)`.
   145  func (a *App) Delete(path string, action Action) {
   146  	a.HandleAction(http.MethodDelete, path, action)
   147  }
   148  
   149  // HandleAction registers an action for a given method and path with the given middleware.
   150  func (a *App) HandleAction(method string, path string, action Action) {
   151  	a.Handle(method, path, a.ActionHandler(NestMiddleware(action, a.middleware...)))
   152  }
   153  
   154  // Lookup finds the route data for a given method and path.
   155  func (a *App) Lookup(method, path string) (route *Route, params RouteParameters, skipSlashRedirect bool) {
   156  	if root := a.Routes[method]; root != nil {
   157  		route, params, skipSlashRedirect = root.getPath(path)
   158  		return
   159  	}
   160  	return
   161  }
   162  
   163  // ActionHandler is the translation step from Action to Handler.
   164  func (a *App) ActionHandler(action Action) Handler {
   165  	return func(w http.ResponseWriter, r *http.Request, route *Route, p RouteParameters) {
   166  		ctx := &baseContext{
   167  			app:         a,
   168  			req:         r,
   169  			res:         NewStatusResponseWriter(w),
   170  			route:       route,
   171  			routeParams: p,
   172  			started:     time.Now().UTC(),
   173  		}
   174  		if a.PanicAction != nil {
   175  			defer func() {
   176  				if r := recover(); r != nil {
   177  					if res := a.PanicAction(ctx, r); res != nil {
   178  						a.renderResult(ctx, res)
   179  					}
   180  					a.handleError(ctx, errutil.New(r))
   181  				}
   182  			}()
   183  		}
   184  		for key, value := range a.Headers {
   185  			ctx.Response().Header()[key] = value
   186  		}
   187  		if res := action(ctx); res != nil {
   188  			a.renderResult(ctx, res)
   189  		}
   190  		if len(a.onRequest) > 0 {
   191  			re := NewRequestEvent(ctx)
   192  			for _, reqHandler := range a.onRequest {
   193  				reqHandler(re)
   194  			}
   195  		}
   196  	}
   197  }
   198  
   199  // Initialize calls initializers on the localization engine
   200  // and the views cache.
   201  //
   202  // If you don't use either of these facilities (e.g. if you're only
   203  // implementing JSON api routes, you can skip calling this function).
   204  func (a *App) Initialize() error {
   205  	if err := a.Localization.Initialize(); err != nil {
   206  		return err
   207  	}
   208  	if err := a.Views.Initialize(); err != nil {
   209  		return err
   210  	}
   211  	return nil
   212  }
   213  
   214  // Start implements the first phase of graceful.Graceful
   215  // and initializes the view cache and does some other housekeeping
   216  // before starting the server.
   217  func (a *App) Start(ctx context.Context) error {
   218  	if err := a.Views.Initialize(); err != nil {
   219  		return err
   220  	}
   221  	a.Handler = a.RouteTree
   222  	return a.Server.Start(ctx)
   223  }
   224  
   225  //
   226  // internal helpers
   227  //
   228  
   229  func (a *App) renderResult(ctx Context, res Result) {
   230  	if err := res.Render(ctx); err != nil {
   231  		a.handleError(ctx, err)
   232  	}
   233  }
   234  
   235  func (a *App) handleError(ctx Context, err error) {
   236  	if err == nil {
   237  		return
   238  	}
   239  	for _, errHandler := range a.onError {
   240  		errHandler(ctx, err)
   241  	}
   242  }