// Package webpublic contains the public webserver // which provides the primary and only intended access point // for interacting with the system. // // # Sections // // - Frontend: Serves the various web frontend versions // - Main: The original Linstrom specific frontend // - NoJs: An entirely serverside rendered frontend, no JS included // - Custom: Custom frontend files will be served here // // - API: Endpoints for the actual interactions // - Frontend: The API used by the main frontend // - Masto: Mastodon compatible adapter for internal structures // - ActivityPub: For integration with the Fediverse via ActivityPub // - Linstrom-RPC: For Linstrom to Linstrom server communication // // # Guarantees // // - The Masto and ActivityPub API will remain stable // - Frontend API might change, but the intended consumer (Main frontend) // will always be up to date with the changes // - Linstrom-RPC API is versioned and will keep // a few versions of backwards compatibility // // TODO: Decide how long the Linstrom-RPC API will remain backwards compatible package webpublic import ( "context" "io/fs" "net/http" webutils "git.mstar.dev/mstar/goutils/http" "github.com/quic-go/quic-go/http3" "github.com/rs/zerolog/log" "git.mstar.dev/mstar/linstrom/auth-new" "git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/web/public/api" webmiddleware "git.mstar.dev/mstar/linstrom/web/public/middleware" ) type Server struct { server *http.Server quicServer *http3.Server } func New(addr string, duckImg *string, duckFs fs.FS) *Server { handler := http.NewServeMux() handler.Handle("/api/", http.StripPrefix("/api", api.BuildApiRouter())) handler.HandleFunc("GET /.well-known/webfinger", api.WellKnownWebfinger) handler.HandleFunc("GET /.well-known/nodeinfo", api.NodeInfoOverview) handler.HandleFunc("GET /nodeinfo/2.1", api.NodeInfo21) handler.HandleFunc("GET /nodeinfo/2.0", api.NodeInfo20) handler.HandleFunc("GET /errors/{name}", errorTypeHandler) handler.HandleFunc("GET /default-image", buildServeDefaultImage(duckImg, duckFs)) handler.HandleFunc("GET /default-image.webp", buildServeDefaultImage(duckImg, duckFs)) rootHandler := webutils.ChainMiddlewares( handler, webutils.BuildLoggingMiddleware( true, []string{"/assets"}, map[string]string{"server": "public"}, ), webmiddleware.AppendFullPathMiddleware, webmiddleware.TraceRequestInfoMiddleware, webmiddleware.AddUpgradeHeader, ) server := http.Server{ Handler: rootHandler, Addr: addr, } quicServer := http3.Server{ Handler: rootHandler, Addr: addr, } if config.GlobalConfig.General.PublicPort != nil { quicServer.Port = *config.GlobalConfig.General.PublicPort } return &Server{server: &server, quicServer: &quicServer} } func (s *Server) Start() error { if config.GlobalConfig.SSL.HandleSSL { certFile, keyFile, err := auth.TlsFromConfig() if err != nil { return err } if config.GlobalConfig.Experimental.Http3Support { errChan := make(chan error, 2) go func() { errChan <- s.server.ListenAndServeTLS(certFile, keyFile) }() go func() { errChan <- s.quicServer.ListenAndServeTLS(certFile, keyFile) }() err := <-errChan if err != nil && err != http.ErrServerClosed { return err } // Always gets two values back on the channel since both servers are running err = <-errChan if err != nil && err != http.ErrServerClosed { return err } return nil } else { return s.server.ListenAndServeTLS(certFile, keyFile) } } else { // But refuse http3 support here, since it doesn't work without tls return s.server.ListenAndServe() } } func (s *Server) Stop() error { if config.GlobalConfig.SSL.HandleSSL && config.GlobalConfig.Experimental.Http3Support { errChan := make(chan error, 2) go func() { log.Debug().Msg("Stopping tcp server") errChan <- s.server.Shutdown(context.Background()) log.Debug().Msg("Tcp server stopped") }() go func() { log.Debug().Msg("Stopping udp server") errChan <- s.quicServer.Shutdown(context.Background()) log.Debug().Msg("Udp server stopped") }() err := <-errChan if err != nil && err != http.ErrServerClosed { return err } // Always gets two values back on the channel since both servers are running err = <-errChan if err != nil && err != http.ErrServerClosed { return err } return nil } else { return s.server.Shutdown(context.Background()) } } func buildServeDefaultImage( duckImg *string, duckFs fs.FS, ) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { http.ServeFileFS(w, r, duckFs, "duck.webp") // w.Header().Add("Content-Type", "image/webp") // w.Header().Add("Content-Disposition", "attachment; filename=\"duck.webp\"") // fmt.Fprint(w, *duckImg) } }