package httpserver import ( "context" "crypto/tls" "html/template" "net" "net/http" "sync" "github.com/gin-gonic/gin" "github.com/phuslu/log" "go.sebtobie.de/httpserver/auth" "go.sebtobie.de/httpserver/menus" "go.sebtobie.de/httpserver/templates" ) func init() { gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { log.Trace().Msgf("%-4s(%02d): %-20s %s", httpMethod, nuHandlers, absolutePath, handlerName) } } // 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 Certfile string Keyfile string Sites map[string]SiteConfig } // MarshalObject adds the information over the object to the *log.Entry func (c *Config) MarshalObject(e *log.Entry) { e.Strs("Address", c.Addr).Bool("TLS", c.TLSconfig != nil) if c.TLSconfig != nil { e.Str("Certfile", c.Certfile) e.Str("Keyfile", c.Keyfile) } } var _ log.ObjectMarshaler = &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 []struct { config SiteConfig site Site } menu []menus.Menu template template.Template NotFoundHandler http.Handler routines sync.WaitGroup } // 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.DefaultLogger.Std(log.ErrorLevel, log.Context{}, "GIN", 0).Writer() gin.DefaultWriter = log.DefaultLogger.Std(log.DebugLevel, log.Context{}, "GIN", 0).Writer() log.Info().Msg("Creating HTTP-Server") var server = &Server{ Conf: &Config{}, mrouter: map[string]*gin.Engine{}, } server.http = &http.Server{ ErrorLog: log.DefaultLogger.Std(log.ErrorLevel, log.Context{}, "", 0), Handler: http.HandlerFunc(server.DomainRouter), } server.NotFoundHandler = http.NotFoundHandler() server.menu = []menus.Menu{} 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) { var socket net.Listener var err error if net.ParseIP(address) != nil { socket, err = net.Listen("tcp", address) } else { socket, err = net.Listen("unix", address) } if err != nil { log.Error().Err(err).Msgf("failed to open socket on %s", address) } if tls { err = s.http.ServeTLS(socket, s.Conf.Certfile, s.Conf.Keyfile) } else { err = s.http.Serve(socket) } if err != http.ErrServerClosed { log.Error().Err(err).Msg("Socket unexpected exited") } s.routines.Done() } // StartServer starts the server as configured and sends the errormessage to the log. // it blocks until all ports are closed. func (s *Server) StartServer() { log.Info().Msg("Starting server") s.http.TLSConfig = s.Conf.TLSconfig var err error 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 { go s.runPort(addr, false) } if err != http.ErrServerClosed { log.Error().Err(err).Msg("Server unexpected exited") } 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 } var entrys []string for d := range s.mrouter { entrys = append(entrys, d) } log.Trace().Strs("reqistred 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) { for _, router := range s.mrouter { router.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 { s.site.Teardown() } } func (s *Server) menus() []menus.Menu { return s.menu } // RegisterSite adds an site to the engine as its own grouo func (s *Server) RegisterSite(cfg string, site Site) { var router *gin.Engine var found bool var config = s.Conf.Sites[cfg] if err := site.Setup(config); err != nil { log.Error().Err(err).Msg("Site failed to load the config") return } if router, found = s.mrouter[config.Domain()]; !found { var authhf auth.AuthenticationHandler router = gin.New() mw := []gin.HandlerFunc{ func(c *gin.Context) { c.Set(Menus, s.menus) c.Set(Domain, config.Domain()) }, } if authhf, found = site.(auth.AuthenticationHandler); !found { authhf = &auth.AnonAccountHandler{} } mw = append(mw, func(c *gin.Context) { authhf.Account(c) }) router.Use(mw...) s.mrouter[config.Domain()] = router } site.Init(router.Group(config.Path())) s.sites = append(s.sites, struct { config SiteConfig site Site }{ config: config, site: site, }) if ms, ok := site.(menus.MenuSite); ok { menus := ms.Menu(config.Domain()) 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() log.Debug().Msgf("Templates for %s%s are added", config.Domain(), config.Path()) s.template.AddParseTree(templates.Name(), templates.Tree) s.template.Funcs(ts.Funcs()) } return }