commit 57eaeea310cd675ee8c8eb9506e4baa122a441b9 Author: Miroslav Misek Date: Thu Mar 27 09:19:08 2025 +0100 Initial commit diff --git a/.idea/maf-webserver.iml b/.idea/maf-webserver.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/maf-webserver.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..95a8a34 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..0e0384f --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/common.go b/common.go new file mode 100644 index 0000000..6923d00 --- /dev/null +++ b/common.go @@ -0,0 +1,5 @@ +package webserver + +import "github.com/labstack/echo/v4" + +type MiddlewareFunc = echo.MiddlewareFunc diff --git a/config.go b/config.go new file mode 100644 index 0000000..b453a2f --- /dev/null +++ b/config.go @@ -0,0 +1,15 @@ +package webserver + +type Config struct { + Port uint16 `yaml:"port" envconfig:"PORT"` + BaseUrl string `yaml:"baseUrl" envconfig:"BASE_URL"` + ReloadTemplates bool `yaml:"reloadTemplates" envconfig:"RELOAD_TEMPLATES"` +} + +func NewConfig() *Config { + return &Config{ + Port: 8080, + BaseUrl: "", + ReloadTemplates: false, + } +} diff --git a/context.go b/context.go new file mode 100644 index 0000000..34d4a11 --- /dev/null +++ b/context.go @@ -0,0 +1,149 @@ +package webserver + +import ( + "errors" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "log/slog" + "net/http" +) + +type HandlerFunc func(c *Context) error + +type Context struct { + echo.Context + csrfConfig *middleware.CSRFConfig +} + +func CustomContext(csrfConfig *middleware.CSRFConfig) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + cc := &Context{ + Context: c, + csrfConfig: csrfConfig, + } + return next(cc) + } + } +} + +func (c *Context) View(code int, filename string, obj interface{}) error { + if mapContext, ok := obj.(map[string]interface{}); ok { + viewData := mapContext + viewData["request"] = c.Request() + viewData["csrf"] = c.CSRF() + obj = viewData + } + return c.Context.Render(code, filename, obj) +} + +func (c *Context) DtoError(err error) error { + + switch err.(type) { + case *NotFoundError: + return echo.NewHTTPError(http.StatusNotFound, "Not found") + case *InternalError: + return echo.NewHTTPError(http.StatusInternalServerError, "Internal server error") + case *InvalidRequestError: + return echo.NewHTTPError(http.StatusBadRequest, "Bad request") + default: + return echo.NewHTTPError(http.StatusInternalServerError, "Internal server error") + } + +} + +func (c *Context) NotFound() error { + return echo.NewHTTPError(http.StatusNotFound) +} + +func (c *Context) InternalServerError(err ...error) error { + if err != nil && len(err) > 0 { + slog.Error("", err) + } + return echo.NewHTTPError(http.StatusInternalServerError) +} + +func Handle(handler HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + return handler(c.(*Context)) + } +} + +func (c *Context) CSRF() string { + return c.Get(middleware.DefaultCSRFConfig.ContextKey).(string) +} + +func (c *Context) GetFormData() (map[string]string, error) { + + var err error + ret := make(map[string]string) + + //c.MultipartForm() + contentType := c.Request().Header.Get("Content-Type") + if contentType == "multipart/form-data" { + err = c.Request().ParseMultipartForm(4 * 1024 * 1024) + } else if contentType == "application/x-www-form-urlencoded" { + err = c.Request().ParseForm() + } else { + err = errors.New("unsupported content type") + } + + if err != nil { + return nil, err + } + for k, v := range c.Request().PostForm { + ret[k] = v[len(v)-1] + } + + return ret, nil +} + +/* +func requestQueryToFilter(c *Context, params map[string]string) (i2bolt.Filter, map[string]string) { + + var subFilters []i2bolt.Filter + filterParams := make(map[string]string) + + query := c.Request().URL.Query() + for param, fieldMethod := range params { + + if v, ok := query[param]; ok { + value := strings.TrimSpace(v[0]) + if value != "" { + + parts := strings.Split(fieldMethod, "__") + fieldName := parts[0] + method := "" + if len(parts) > 1 { + method = parts[1] + } + + var valueIntf interface{} + valueIntf = value + + if method == "is" { + valueIntf = value == "yes" + } + + fieldNames := strings.Split(fieldName, "|") + for _, name := range fieldNames { + subFilters = append(subFilters, i2bolt.Q(name, method, valueIntf)) + filterParams[param] = value + } + + } else { + filterParams[param] = "" + } + + } + + } + + var filter i2bolt.Filter + if len(subFilters) > 0 { + filter = i2bolt.And(subFilters...) + } + + return filter, filterParams +} +*/ diff --git a/dto.go b/dto.go new file mode 100644 index 0000000..6bc81f5 --- /dev/null +++ b/dto.go @@ -0,0 +1,70 @@ +package webserver + +import "fmt" + +type GateError struct { + Message string + Err error +} + +func (e *GateError) Error() string { + if e.Message != "" && e.Err != nil { + return fmt.Sprintf("%s: %v", e.Message, e.Err) + } else if e.Message != "" { + return e.Message + } else { + return e.Err.Error() + } +} + +type InputError struct { + GateError +} + +func NewInputError(message string, err error) *InputError { + return &InputError{ + GateError{ + Message: message, + Err: err, + }, + } +} + +type InternalError struct { + GateError +} + +func NewInternalError(message string, err error) *InternalError { + return &InternalError{ + GateError{ + Message: message, + Err: err, + }, + } +} + +type NotFoundError struct { + GateError +} + +func NewNotFoundError(message string, err error) *NotFoundError { + return &NotFoundError{ + GateError{ + Message: message, + Err: err, + }, + } +} + +type InvalidRequestError struct { + GateError +} + +func NewInvalidRequestError(message string, err error) *InvalidRequestError { + return &InvalidRequestError{ + GateError{ + Message: message, + Err: err, + }, + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2a37c86 --- /dev/null +++ b/go.mod @@ -0,0 +1,30 @@ +module netgarden.dev/netgarden/maf-webserver + +go 1.24.1 + +replace netgarden.dev/netgarden/maf => ../maf + +replace netgarden.dev/netgarden/mergefs => ../mergefs + +require ( + github.com/flosch/pongo2/v6 v6.0.0 + github.com/labstack/echo/v4 v4.13.3 + netgarden.dev/netgarden/maf v0.0.0-00010101000000-000000000000 + netgarden.dev/netgarden/mergefs v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/elliotchance/orderedmap v1.6.0 // indirect + github.com/kelseyhightower/envconfig v1.4.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.8.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..172b294 --- /dev/null +++ b/go.sum @@ -0,0 +1,50 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elliotchance/orderedmap v1.6.0 h1:xjn+kbbKXeDq6v9RVE+WYwRbYfAZKvlWfcJNxM8pvEw= +github.com/elliotchance/orderedmap v1.6.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= +github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU= +github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/group.go b/group.go new file mode 100644 index 0000000..0f98ae9 --- /dev/null +++ b/group.go @@ -0,0 +1,28 @@ +package webserver + +import ( + "github.com/labstack/echo/v4" + "strings" +) + +type echoGroup = echo.Group +type Group struct { + *echoGroup +} + +func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group { + return &Group{ + g.echoGroup.Group(prefix, middleware...), + } +} + +func (g *Group) AddMany(methods string, path string, handler echo.HandlerFunc) { + + for _, method := range strings.Split(methods, " ") { + method = strings.TrimSpace(method) + if method != "" { + g.Add(method, path, handler) + } + } + +} diff --git a/interfaces.go b/interfaces.go new file mode 100644 index 0000000..f2e8b30 --- /dev/null +++ b/interfaces.go @@ -0,0 +1,36 @@ +package webserver + +import ( + "github.com/flosch/pongo2/v6" + "io/fs" + "netgarden.dev/netgarden/maf" +) + +type Controller interface { + Register(app *Group) +} + +type WebModule interface { + maf.Module + GetWebControllers() []Controller +} + +type WebModuleStaticFSProvider interface { + WebModule + GetWebStaticFS() fs.FS +} + +type WebModuleTemplatesFSProvider interface { + WebModule + GetWebTemplatesFS() fs.FS +} + +type WebModuleTemplateTagsProvider interface { + WebModule + GetWebTemplateTags(renderer *Renderer) map[string]pongo2.TagParser +} + +type WebModuleTemplateFiltersProvider interface { + WebModule + GetWebTemplateFilters() map[string]pongo2.FilterFunction +} diff --git a/module.go b/module.go new file mode 100644 index 0000000..5d65cfd --- /dev/null +++ b/module.go @@ -0,0 +1,144 @@ +package webserver + +import ( + "github.com/flosch/pongo2/v6" + "io/fs" + "netgarden.dev/netgarden/maf" + "netgarden.dev/netgarden/mergefs" +) + +func NewModule(moduleConfig *ModuleConfig) *Module { + return &Module{ + moduleConfig: moduleConfig, + } +} + +type Module struct { + moduleConfig *ModuleConfig + manager *maf.Manager + config *Config + server *Server +} + +func (m *Module) GetID() string { + return "webserver" +} + +func (m *Module) GetName() string { + return "webserver" +} + +func (m *Module) SetManager(manager *maf.Manager) { + m.manager = manager +} + +func (m *Module) CreateConfig() interface{} { + return NewConfig() +} + +func (m *Module) SetConfig(cfg interface{}) { + m.config = cfg.(*Config) +} + +func (m *Module) GetConfig() *Config { + return m.config +} + +func (m *Module) Initialize() error { + return nil +} + +func (m *Module) PreStart() error { + + m.server = NewServer(m) + + for _, module := range m.manager.GetModulesList() { + + webModule, ok := module.(WebModule) + if !ok { + continue + } + + if tagsProvider, ok := module.(WebModuleTemplateTagsProvider); ok { + for name, tag := range tagsProvider.GetWebTemplateTags(m.server.GetRenderer()) { + err := pongo2.RegisterTag(name, tag) + if err != nil { + return err + } + } + } + + if filtersProvider, ok := module.(WebModuleTemplateFiltersProvider); ok { + for name, filter := range filtersProvider.GetWebTemplateFilters() { + err := pongo2.RegisterFilter(name, filter) + if err != nil { + return err + } + } + } + + routerGroup := m.server.GetGroup() + controllers := webModule.GetWebControllers() + for _, controller := range controllers { + controller.Register(routerGroup) + } + + } + + return nil +} + +func (m *Module) Start() error { + return m.server.Start() +} + +func (m *Module) Stop() error { + if m.server == nil { + return nil + } + return m.server.Stop() +} + +func (m *Module) getStaticFS() fs.FS { + + fsList := make([]fs.FS, 0) + + for _, module := range m.manager.GetModulesList() { + + webModule, ok := module.(WebModuleStaticFSProvider) + if !ok { + continue + } + + tplFS := webModule.GetWebStaticFS() + if tplFS == nil { + continue + } + + fsList = append(fsList, tplFS) + } + + return mergefs.Merge(fsList...) +} + +func (m *Module) getTemplatesFS() fs.FS { + + fsList := make([]fs.FS, 0) + + for _, module := range m.manager.GetModulesList() { + + webModule, ok := module.(WebModuleTemplatesFSProvider) + if !ok { + continue + } + + tplFS := webModule.GetWebTemplatesFS() + if tplFS == nil { + continue + } + + fsList = append(fsList, tplFS) + } + + return mergefs.Merge(fsList...) +} diff --git a/module_config.go b/module_config.go new file mode 100644 index 0000000..57b9e15 --- /dev/null +++ b/module_config.go @@ -0,0 +1,7 @@ +package webserver + +type MiddlewareFuncProvider = func() MiddlewareFunc + +type ModuleConfig struct { + Middlewares []MiddlewareFuncProvider +} diff --git a/renderer.go b/renderer.go new file mode 100644 index 0000000..dc886d4 --- /dev/null +++ b/renderer.go @@ -0,0 +1,89 @@ +package webserver + +import ( + "bytes" + "errors" + "fmt" + "github.com/flosch/pongo2/v6" + "github.com/labstack/echo/v4" + "io" + "io/fs" +) + +type Renderer struct { + fs fs.FS + templates *pongo2.TemplateSet +} + +// NewRenderer creates a new Renderer struct. +func NewRenderer(fs fs.FS) *Renderer { + + r := &Renderer{ + fs: fs, + } + + templates := pongo2.NewSet("templates", r) + r.templates = templates + + return r +} + +// Abs returns absolute path to file requested. +// Search path is configured in AddDirectory method. +// And default directory is "./templates". +func (p *Renderer) Abs(base, name string) string { + return name +} + +// Get reads the path's content from your local filesystem. +func (p *Renderer) Get(path string) (io.Reader, error) { + + f, err := p.fs.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + return bytes.NewReader(data), nil +} + +// RegisterTag registers a custom tag. +// It calls pongo2.RegisterTag method. +func (p *Renderer) RegisterTag(name string, parserFunc pongo2.TagParser) { + pongo2.RegisterTag(name, parserFunc) +} + +// RegisterFilter registers a custom filter. +// It calls pongo2.RegisterFilter method. +func (p *Renderer) RegisterFilter(name string, fn pongo2.FilterFunction) { + pongo2.RegisterFilter(name, fn) +} + +// SetDebug sets debug mode to the template set. +// See pongo2.TemplateSet.Debug for more information. +func (p *Renderer) SetDebug(v bool) { + p.templates.Debug = v +} + +// Render renders the view. +// Many other times, this is called in your echo handler functions. +func (p *Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + tmpl, err := p.templates.FromCache(name) + if err != nil { + return err + } + if tmpl == nil { + return fmt.Errorf("template '%s' not found", name) + } + d, ok := data.(map[string]interface{}) + if !ok { + return errors.New("Incorrect data format. Should be map[string]interface{}") + } + + return tmpl.ExecuteWriter(d, w) +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..3805c4f --- /dev/null +++ b/server.go @@ -0,0 +1,115 @@ +package webserver + +import ( + "context" + "fmt" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "log/slog" + "net" + "net/http" + "syscall" + "time" +) + +type Server struct { + module *Module + listener *net.Listener + csrfConfig *middleware.CSRFConfig + router *echo.Echo + server *http.Server +} + +func NewServer(module *Module) *Server { + + s := &Server{ + module: module, + } + + s.init() + + return s +} + +func (s *Server) init() { + + s.csrfConfig = &middleware.CSRFConfig{ + TokenLookup: "form:_csrf,query:csrf,header:X-CSRF-Token", + } + + s.router = echo.New() + //s.router.Pre(middleware.RemoveTrailingSlash()) + s.router.Use(middleware.Logger()) + s.router.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ + LogLevel: 4, + })) + s.router.Use(middleware.CSRFWithConfig(*s.csrfConfig)) + s.router.Use(CustomContext(s.csrfConfig)) + if s.module.moduleConfig != nil && s.module.moduleConfig.Middlewares != nil { + for _, m := range s.module.moduleConfig.Middlewares { + s.router.Use(m()) + } + } + + tplFS := s.module.getTemplatesFS() + s.router.Renderer = NewRenderer(tplFS) + + s.router.StaticFS("/static", s.module.getStaticFS()) + +} + +func (s *Server) Start() error { + + config := &net.ListenConfig{Control: s.reusePort} + listener, err := config.Listen( + context.Background(), + "tcp", + fmt.Sprintf(":%d", s.module.config.Port), + ) + if err != nil { + return err + } + + s.listener = &listener + + s.server = &http.Server{ + Addr: fmt.Sprintf(":%d", s.module.config.Port), + Handler: s.router, + } + + go s.server.Serve(listener) + + return nil +} + +func (s *Server) Stop() error { + + if s.server == nil { + return nil + } + + timeout := 10 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + return s.server.Shutdown(ctx) +} + +func (s *Server) GetGroup() *Group { + return &Group{ + s.router.Group("/"), + } +} + +func (s *Server) GetRenderer() *Renderer { + return s.router.Renderer.(*Renderer) +} + +func (s *Server) reusePort(network, address string, conn syscall.RawConn) error { + return conn.Control(func(descriptor uintptr) { + err := syscall.SetsockoptInt(int(descriptor), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err != nil { + slog.Error("error during setting reuseport", err) + } + }) +}