302 Zeilen
8.6 KiB
Go
302 Zeilen
8.6 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
|
|
intlog "log"
|
|
|
|
"github.com/flosch/pongo2/v6"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/rs/zerolog/log"
|
|
"go.sebtobie.de/httpserver/auth"
|
|
"go.sebtobie.de/httpserver/constants"
|
|
"go.sebtobie.de/httpserver/funcs"
|
|
"go.sebtobie.de/httpserver/menus"
|
|
"go.sebtobie.de/httpserver/middleware"
|
|
"go.sebtobie.de/httpserver/templates"
|
|
)
|
|
|
|
func init() {
|
|
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
|
|
log.Debug().Msgf("%-4s(%02d): %-20s %s", httpMethod, nuHandlers, absolutePath, handlerName)
|
|
}
|
|
gin.SetMode(gin.DebugMode)
|
|
}
|
|
|
|
// Config that is used to map the toml config to the settings that are used.
|
|
type Config struct {
|
|
Addr []string
|
|
TLSAddr []string
|
|
TLSconfig *tls.Config `toml:"-"`
|
|
Certfile string
|
|
Keyfile string
|
|
Sites map[string]SiteConfig
|
|
Middleware map[string]middleware.Config
|
|
}
|
|
|
|
// Server is an wrapper for the *http.Server and *gin.Engine
|
|
type Server struct {
|
|
http *http.Server
|
|
Conf *Config
|
|
mrouter map[string]*gin.Engine
|
|
sites map[string]Site
|
|
menu []menus.Menu
|
|
template *pongo2.TemplateSet
|
|
NotFoundHandler http.Handler
|
|
routines sync.WaitGroup
|
|
setup bool
|
|
authh auth.AuthenticationHandler
|
|
middleware gin.HandlersChain
|
|
advmiddleware map[string]middleware.Middleware
|
|
}
|
|
|
|
// CreateServer creates an server that can be run in a coroutine.
|
|
func CreateServer() *Server {
|
|
log.Info().Msg("Redirect logging output to phuslu/log")
|
|
gin.DefaultErrorWriter = log.Logger.With().Str("source", "GIN").Logger()
|
|
gin.DefaultWriter = log.Logger.With().Str("source", "GIN").Logger()
|
|
log.Info().Msg("Creating HTTP-Server")
|
|
var server = &Server{
|
|
Conf: &Config{
|
|
TLSconfig: &tls.Config{},
|
|
Sites: map[string]SiteConfig{},
|
|
Middleware: map[string]middleware.Config{},
|
|
},
|
|
mrouter: map[string]*gin.Engine{},
|
|
authh: &auth.AnonAccountHandler{},
|
|
menu: []menus.Menu{},
|
|
NotFoundHandler: http.NotFoundHandler(),
|
|
sites: map[string]Site{},
|
|
middleware: gin.HandlersChain{},
|
|
template: pongo2.NewSet("templates", &templates.EmptyLoader{}),
|
|
advmiddleware: map[string]middleware.Middleware{},
|
|
}
|
|
server.http = &http.Server{
|
|
ErrorLog: intlog.New(log.Logger, "", 0),
|
|
Handler: http.HandlerFunc(server.DomainRouter),
|
|
}
|
|
return server
|
|
}
|
|
|
|
// runPort runs a listener on the port. his enables th server to serve more than a address.
|
|
func (s *Server) runPort(address string, tls bool) {
|
|
defer s.routines.Done()
|
|
var socket net.Listener
|
|
var err error
|
|
var unix string
|
|
|
|
if funcs.IsTCP(address) {
|
|
socket, err = net.Listen("tcp", address)
|
|
}
|
|
if funcs.IsUnix(address) {
|
|
unix = "Unix-"
|
|
socket, err = net.Listen("unix", address)
|
|
}
|
|
if err != nil {
|
|
log.Error().Err(err).Msgf("failed to open socket on %s", address)
|
|
return
|
|
}
|
|
if socket == nil {
|
|
log.Error().Msg("Failed to identify the sockettype")
|
|
return
|
|
}
|
|
if tls {
|
|
log.Info().Msgf("starting listen on secure %ssocket %s", unix, address)
|
|
err = s.http.ServeTLS(socket, s.Conf.Certfile, s.Conf.Keyfile)
|
|
} else {
|
|
log.Info().Msgf("starting listen on %ssocket %s", unix, address)
|
|
err = s.http.Serve(socket)
|
|
}
|
|
if err != http.ErrServerClosed {
|
|
log.Error().Err(err).Msg("Socket unexpected exited")
|
|
}
|
|
}
|
|
|
|
// SetAuthentication sets the handler that is responsible for authentication
|
|
func (s *Server) SetAuthentication(a auth.AuthenticationHandler) {
|
|
s.authh = a
|
|
}
|
|
|
|
// StartServer starts the server as configured and sends the errormessage to the log.
|
|
// it blocks until all ports are closed.
|
|
func (s *Server) StartServer() {
|
|
if !s.setup {
|
|
log.Error().Msg("Server not set up")
|
|
return
|
|
}
|
|
log.Info().Msg("Starting server")
|
|
s.http.TLSConfig = s.Conf.TLSconfig
|
|
if s.Conf.Certfile != "" && s.Conf.Keyfile != "" {
|
|
for _, addr := range s.Conf.TLSAddr {
|
|
s.routines.Add(1)
|
|
go s.runPort(addr, true)
|
|
}
|
|
}
|
|
for _, addr := range s.Conf.Addr {
|
|
s.routines.Add(1)
|
|
go s.runPort(addr, false)
|
|
}
|
|
s.routines.Wait()
|
|
}
|
|
|
|
// DomainRouter redirects the requests to the routers of the domains
|
|
func (s *Server) DomainRouter(w http.ResponseWriter, r *http.Request) {
|
|
var domain string
|
|
if r.URL.Host != "" {
|
|
domain = r.URL.Host
|
|
} else if r.Host != "" {
|
|
domain = r.Host
|
|
} else if r.Header.Get("X-Original-Host") != "" {
|
|
domain = r.Header.Get("X-Original-Host")
|
|
}
|
|
r.Host = domain
|
|
r.URL.Host = domain
|
|
for header, value := range map[string][]string(r.Header) {
|
|
log.Trace().Strs(header, value).Msg("Headers")
|
|
}
|
|
if router, found := s.mrouter[domain]; found {
|
|
router.NoMethod(gin.WrapH(s.NotFoundHandler))
|
|
router.NoRoute(gin.WrapH(s.NotFoundHandler))
|
|
router.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
log.Error().Msgf("Failed to find domain for %s", domain)
|
|
var entrys []string
|
|
for d := range s.mrouter {
|
|
entrys = append(entrys, d)
|
|
}
|
|
log.Trace().Strs("registred domains", entrys).Msg("domain not found")
|
|
s.NotFoundHandler.ServeHTTP(w, r)
|
|
}
|
|
|
|
// Use installs the middleware into the router.
|
|
// The Middleware must be able to detect multiple calls byy itself. Deduplication is not performed.
|
|
func (s *Server) Use(m ...gin.HandlerFunc) {
|
|
s.middleware = append(s.middleware, m...)
|
|
for _, site := range s.mrouter {
|
|
site.Use(m...)
|
|
}
|
|
}
|
|
|
|
// Stop Shuts the Server down
|
|
func (s *Server) Stop(ctx context.Context) {
|
|
log.Info().Err(s.http.Shutdown(ctx)).Msg("Server Shut down.")
|
|
for _, s := range s.sites {
|
|
if ts, ok := s.(TeardownSite); ok {
|
|
ts.Teardown()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) menus() []menus.Menu {
|
|
return s.menu
|
|
}
|
|
|
|
func maptoarray(m map[string]Site) (a []any) {
|
|
for _, i := range m {
|
|
a = append(a, i)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Setup sets the server up. It loads the sites and prepare the server for startup.
|
|
// The Midleware and the site are setup in this Order:
|
|
// 1. Middleware.Setup
|
|
// 2. Site.Init
|
|
// 3. Middleware.Sites
|
|
// 4. Site.Setup
|
|
func (s *Server) Setup() {
|
|
log.Info().Msg("Perparing server for start")
|
|
var (
|
|
router *gin.Engine
|
|
found bool
|
|
site Site
|
|
cfg string
|
|
)
|
|
for cfg, m := range s.advmiddleware {
|
|
m.Setup(s.Conf.Middleware[cfg])
|
|
}
|
|
for cfg, site = range s.sites {
|
|
config := s.Conf.Sites[cfg]
|
|
if router, found = s.mrouter[config["domain"].(string)]; !found {
|
|
log.Info().Msgf("Setting up router for %s", config["domain"].(string))
|
|
router = gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set(constants.Domain, config["domain"])
|
|
c.Set(constants.Menus, s.menus)
|
|
c.Set(constants.Accounts, s.authh.Account(c))
|
|
})
|
|
router.Use(s.middleware...)
|
|
router.HTMLRender = templates.NewPongo2Renderer(s.template)
|
|
s.mrouter[config["domain"].(string)] = router
|
|
}
|
|
group := router.Group(config["path"].(string))
|
|
site.Init(group)
|
|
}
|
|
for _, m := range s.advmiddleware {
|
|
if psm, ok := m.(middleware.PreSetupMiddleware); ok {
|
|
if err := psm.PreSetup(maptoarray(s.sites)); err != nil {
|
|
log.Error().Err(err).Msg("Failed to setup midddleware (pre-site-setup). Stopping with the setup")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
for cfg, site = range s.sites {
|
|
config := s.Conf.Sites[cfg]
|
|
if ms, ok := site.(menus.MenuSite); ok {
|
|
menus := ms.Menu(config["domain"].(string))
|
|
log.Debug().Msgf("%d menus are added", len(menus))
|
|
s.menu = append(s.menu, menus...)
|
|
}
|
|
if ts, ok := site.(templates.TemplateSite); ok {
|
|
templates := ts.Templates()
|
|
if templates == nil {
|
|
log.Error().Msgf("Site %s had an empty templateloader", cfg)
|
|
} else {
|
|
s.template.AddLoader(templates)
|
|
}
|
|
}
|
|
if cs, ok := site.(ConfigSite); ok {
|
|
cs.Setup(config)
|
|
}
|
|
}
|
|
for _, m := range s.advmiddleware {
|
|
if psm, ok := m.(middleware.PostSetupMiddleware); ok {
|
|
if err := psm.PostSetup(maptoarray(s.sites)); err != nil {
|
|
log.Error().Err(err).Msg("Failed to setup midddleware (post-site-setup). Stopping with the setup")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
s.setup = true
|
|
}
|
|
|
|
// RegisterSite adds an site to the engine as its own grouo
|
|
// it registers the defaults so that the application can load/dump it from/into an configfile or commandline options
|
|
func (s *Server) RegisterSite(cfg string, site Site) {
|
|
if cs, ok := site.(ConfigSite); ok {
|
|
var config = cs.Defaults()
|
|
if _, found := config["domain"]; !found {
|
|
config["domain"] = ""
|
|
}
|
|
if _, found := config["path"]; !found {
|
|
config["path"] = ""
|
|
}
|
|
s.Conf.Sites[cfg] = config
|
|
}
|
|
s.sites[cfg] = site
|
|
}
|
|
|
|
// RegisterMiddleware registers middleware that has avanced functions, like persistence.
|
|
// That middleware allows configuration with the Setup Method and the Teardown allows an safe method for closing connections and shutting down.
|
|
// Middleware will be set up before sites.
|
|
func (s *Server) RegisterMiddleware(cfg string, m middleware.Middleware) {
|
|
s.Conf.Middleware[cfg] = m.Defaults()
|
|
s.Use(m.Gin)
|
|
s.advmiddleware[cfg] = m
|
|
}
|