webgo.go
/*
Package webgo is a lightweight framework for building web apps. It has a multiplexer,
middleware plugging mechanism & context management of its own. The primary goal
of webgo is to get out of the developer's way as much as possible. i.e. it does
not enforce you to build your app in any particular pattern, instead just helps you
get all the trivial things done faster and easier.
e.g.
1. Getting named URI parameters.
2. Multiplexer for regex matching of URI and such.
3. Inject special app level configurations or any such objects to the request context as required.
*/
package webgo
import (
"context"
"crypto/tls"
"net/http"
)
var supportedHTTPMethods = []string{
http.MethodOptions,
http.MethodHead,
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
}
// ctxkey is a custom string type to store the WebGo context inside HTTP request context
type ctxkey string
const wgoCtxKey = ctxkey("webgocontext")
// ContextPayload is the WebgoContext. A new instance of ContextPayload is injected inside every request's context object
type ContextPayload struct {
Route *Route
Err error
URIParams map[string]string
}
// Params returns the URI parameters of the respective route
func (cp *ContextPayload) Params() map[string]string {
return cp.URIParams
}
func (cp *ContextPayload) reset() {
cp.Route = nil
cp.Err = nil
}
// SetError sets the err within the context
func (cp *ContextPayload) SetError(err error) {
cp.Err = err
}
// Error returns the error set within the context
func (cp *ContextPayload) Error() error {
return cp.Err
}
// Context returns the ContextPayload injected inside the HTTP request context
func Context(r *http.Request) *ContextPayload {
return r.Context().Value(wgoCtxKey).(*ContextPayload)
}
// SetError is a helper function to set the error in webgo context
func SetError(r *http.Request, err error) {
ctx := Context(r)
ctx.SetError(err)
}
// GetError is a helper function to get the error from webgo context
func GetError(r *http.Request) error {
return Context(r).Error()
}
// ResponseStatus returns the response status code. It works only if the http.ResponseWriter
// is not wrapped in another response writer before calling ResponseStatus
func ResponseStatus(rw http.ResponseWriter) int {
crw, ok := rw.(*customResponseWriter)
if !ok {
return http.StatusOK
}
return crw.statusCode
}
func (router *Router) setupServer() {
cfg := router.config
router.httpsServer = &http.Server{
Addr: "",
Handler: router,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
TLSConfig: &tls.Config{
InsecureSkipVerify: cfg.InsecureSkipVerify,
},
}
router.httpServer = &http.Server{
Addr: "",
Handler: router,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
}
router.SetupMiddleware()
}
// SetupMiddleware initializes all the middleware added using "Use".
// This function need not be called explicitly, if using router.Start()
// or router.StartHTTPS(). Instead if the router is being passed to an external server
// then the SetupMiddleware function should be called
func (router *Router) SetupMiddleware() {
// load middleware for all routes
for _, routes := range router.allHandlers {
for _, route := range routes {
route.setupMiddleware(router.config.ReverseMiddleware)
}
}
}
// StartHTTPS starts the server with HTTPS enabled
func (router *Router) StartHTTPS() {
cfg := router.config
if cfg.CertFile == "" {
LOGHANDLER.Fatal("No certificate provided for HTTPS")
}
if cfg.KeyFile == "" {
LOGHANDLER.Fatal("No key file provided for HTTPS")
}
router.setupServer()
host := cfg.Host
if len(cfg.HTTPSPort) > 0 {
host += ":" + cfg.HTTPSPort
}
router.httpsServer.Addr = host
LOGHANDLER.Info("HTTPS server, listening on", router.httpsServer.Addr)
err := router.httpsServer.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile)
if err != nil && err != http.ErrServerClosed {
LOGHANDLER.Error("HTTPS server exited with error:", err.Error())
}
}
// Start starts the HTTP server with the appropriate configurations
func (router *Router) Start() {
router.setupServer()
cfg := router.config
host := cfg.Host
if len(cfg.Port) > 0 {
host += ":" + cfg.Port
}
router.httpServer.Addr = host
LOGHANDLER.Info("HTTP server, listening on", router.httpServer.Addr)
err := router.httpServer.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
LOGHANDLER.Error("HTTP server exited with error:", err.Error())
}
}
// Shutdown gracefully shuts down HTTP server
func (router *Router) Shutdown() error {
if router.httpServer == nil {
return nil
}
timer := router.config.ShutdownTimeout
ctx, cancel := context.WithTimeout(context.TODO(), timer)
defer cancel()
err := router.httpServer.Shutdown(ctx)
if err != nil {
LOGHANDLER.Error(err)
}
return err
}
// ShutdownHTTPS gracefully shuts down HTTPS server
func (router *Router) ShutdownHTTPS() error {
if router.httpsServer == nil {
return nil
}
timer := router.config.ShutdownTimeout
ctx, cancel := context.WithTimeout(context.TODO(), timer)
defer cancel()
err := router.httpsServer.Shutdown(ctx)
if err != nil && err != http.ErrServerClosed {
LOGHANDLER.Error(err)
}
return err
}
// OriginalResponseWriter returns the Go response writer stored within the webgo custom response
// writer
func OriginalResponseWriter(rw http.ResponseWriter) http.ResponseWriter {
crw, ok := rw.(*customResponseWriter)
if !ok {
return nil
}
return crw.ResponseWriter
}