From 22b132914edc896715bb7f75515b10d7026eaadf Mon Sep 17 00:00:00 2001
From: Wolfgang Welz <welzwo@gmail.com>
Date: Tue, 28 Apr 2020 11:45:56 +0200
Subject: [PATCH] Fix: Report HTTP server errors (#370)

---
 .../webinterface/httpserver/plugin.go         |  21 ++--
 plugins/dashboard/plugin.go                   | 100 ++++++++++++------
 plugins/webapi/plugin.go                      |  54 ++++++----
 3 files changed, 112 insertions(+), 63 deletions(-)

diff --git a/plugins/analysis/webinterface/httpserver/plugin.go b/plugins/analysis/webinterface/httpserver/plugin.go
index d32d9d3d..2837fa8b 100644
--- a/plugins/analysis/webinterface/httpserver/plugin.go
+++ b/plugins/analysis/webinterface/httpserver/plugin.go
@@ -20,16 +20,18 @@ var (
 	engine *echo.Echo
 )
 
-const name = "Analysis HTTP Server"
+// PluginName is the name of the analysis server plugin.
+const PluginName = "Analysis HTTP Server"
 
 var assetsBox = packr.New("Assets", "./static")
 
 // Configure configures the plugin.
 func Configure() {
-	log = logger.NewLogger(name)
+	log = logger.NewLogger(PluginName)
 
 	engine = echo.New()
 	engine.HideBanner = true
+	engine.HidePort = true
 
 	// we only need this special flag, because we always keep a packed box in the same directory
 	if config.Node.GetBool(CfgDev) {
@@ -47,17 +49,19 @@ func Configure() {
 
 // Run runs the plugin.
 func Run() {
-	log.Infof("Starting %s ...", name)
-	if err := daemon.BackgroundWorker(name, start, shutdown.PriorityAnalysis); err != nil {
+	log.Infof("Starting %s ...", PluginName)
+	if err := daemon.BackgroundWorker(PluginName, worker, shutdown.PriorityAnalysis); err != nil {
 		log.Errorf("Error starting as daemon: %s", err)
 	}
 }
 
-func start(shutdownSignal <-chan struct{}) {
+func worker(shutdownSignal <-chan struct{}) {
+	defer log.Infof("Stopping %s ... done", PluginName)
+
 	stopped := make(chan struct{})
 	bindAddr := config.Node.GetString(CfgBindAddress)
 	go func() {
-		log.Infof("Started %s: http://%s", name, bindAddr)
+		log.Infof("Started %s: http://%s", PluginName, bindAddr)
 		if err := engine.Start(bindAddr); err != nil {
 			if !errors.Is(err, http.ErrServerClosed) {
 				log.Errorf("Error serving: %s", err)
@@ -66,19 +70,18 @@ func start(shutdownSignal <-chan struct{}) {
 		}
 	}()
 
+	// stop if we are shutting down or the server could not be started
 	select {
 	case <-shutdownSignal:
 	case <-stopped:
 	}
 
-	log.Infof("Stopping %s ...", name)
+	log.Infof("Stopping %s ...", PluginName)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 	defer cancel()
-
 	if err := engine.Shutdown(ctx); err != nil {
 		log.Errorf("Error stopping: %s", err)
 	}
-	log.Info("Stopping %s ... done", name)
 }
 
 func index(e echo.Context) error {
diff --git a/plugins/dashboard/plugin.go b/plugins/dashboard/plugin.go
index b2b8e09f..a6476277 100644
--- a/plugins/dashboard/plugin.go
+++ b/plugins/dashboard/plugin.go
@@ -1,6 +1,8 @@
 package dashboard
 
 import (
+	"context"
+	"errors"
 	"net"
 	"net/http"
 	"runtime"
@@ -34,7 +36,9 @@ const PluginName = "Dashboard"
 var (
 	// Plugin is the plugin instance of the dashboard plugin.
 	Plugin = node.NewPlugin(PluginName, node.Enabled, configure, run)
+
 	log    *logger.Logger
+	server *echo.Echo
 
 	nodeStartAt = time.Now()
 
@@ -60,54 +64,79 @@ func configure(plugin *node.Plugin) {
 
 	configureLiveFeed()
 	configureDrngLiveFeed()
+
+	configureServer()
 }
 
-func run(plugin *node.Plugin) {
-	notifyStatus := events.NewClosure(func(mps uint64) {
-		wsSendWorkerPool.TrySubmit(mps)
-	})
-
-	daemon.BackgroundWorker("Dashboard[WSSend]", func(shutdownSignal <-chan struct{}) {
-		metrics.Events.ReceivedMPSUpdated.Attach(notifyStatus)
-		wsSendWorkerPool.Start()
-		<-shutdownSignal
-		log.Info("Stopping Dashboard[WSSend] ...")
-		metrics.Events.ReceivedMPSUpdated.Detach(notifyStatus)
-		wsSendWorkerPool.Stop()
-		log.Info("Stopping Dashboard[WSSend] ... done")
-	}, shutdown.PriorityDashboard)
+func configureServer() {
+	server = echo.New()
+	server.HideBanner = true
+	server.HidePort = true
+	server.Use(middleware.Recover())
 
-	runLiveFeed()
+	if config.Node.GetBool(CfgBasicAuthEnabled) {
+		server.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
+			if username == config.Node.GetString(CfgBasicAuthUsername) &&
+				password == config.Node.GetString(CfgBasicAuthPassword) {
+				return true, nil
+			}
+			return false, nil
+		}))
+	}
+
+	setupRoutes(server)
+}
 
+func run(*node.Plugin) {
+	// rune the message live feed
+	runLiveFeed()
 	// run dRNG live feed if dRNG plugin is enabled
 	if !node.IsSkipped(drng.Plugin) {
 		runDrngLiveFeed()
 	}
 
-	// allow any origin for websocket connections
-	upgrader.CheckOrigin = func(r *http.Request) bool {
-		return true
+	log.Infof("Starting %s ...", PluginName)
+	if err := daemon.BackgroundWorker(PluginName, worker, shutdown.PriorityAnalysis); err != nil {
+		log.Errorf("Error starting as daemon: %s", err)
 	}
+}
 
-	e := echo.New()
-	e.HideBanner = true
-	e.Use(middleware.Recover())
-
-	if config.Node.GetBool(CfgBasicAuthEnabled) {
-		e.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
-			if username == config.Node.GetString(CfgBasicAuthUsername) &&
-				password == config.Node.GetString(CfgBasicAuthPassword) {
-				return true, nil
+func worker(shutdownSignal <-chan struct{}) {
+	defer log.Infof("Stopping %s ... done", PluginName)
+
+	// start the web socket worker pool
+	wsSendWorkerPool.Start()
+	defer wsSendWorkerPool.Stop()
+
+	// submit the mps to the worker pool when triggered
+	notifyStatus := events.NewClosure(func(mps uint64) { wsSendWorkerPool.TrySubmit(mps) })
+	metrics.Events.ReceivedMPSUpdated.Attach(notifyStatus)
+	defer metrics.Events.ReceivedMPSUpdated.Detach(notifyStatus)
+
+	stopped := make(chan struct{})
+	bindAddr := config.Node.GetString(CfgBindAddress)
+	go func() {
+		log.Infof("Started %s: http://%s", PluginName, bindAddr)
+		if err := server.Start(bindAddr); err != nil {
+			if !errors.Is(err, http.ErrServerClosed) {
+				log.Errorf("Error serving: %s", err)
 			}
-			return false, nil
-		}))
-	}
+			close(stopped)
+		}
+	}()
 
-	setupRoutes(e)
-	addr := config.Node.GetString(CfgBindAddress)
+	// stop if we are shutting down or the server could not be started
+	select {
+	case <-shutdownSignal:
+	case <-stopped:
+	}
 
-	log.Infof("You can now access the dashboard using: http://%s", addr)
-	go e.Start(addr)
+	log.Infof("Stopping %s ...", PluginName)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	if err := server.Shutdown(ctx); err != nil {
+		log.Errorf("Error stopping: %s", err)
+	}
 }
 
 // sends the given message to all connected websocket clients
@@ -128,6 +157,7 @@ var webSocketWriteTimeout = time.Duration(3) * time.Second
 var (
 	upgrader = websocket.Upgrader{
 		HandshakeTimeout:  webSocketWriteTimeout,
+		CheckOrigin:       func(r *http.Request) bool { return true },
 		EnableCompression: true,
 	}
 )
@@ -195,7 +225,7 @@ type neighbormetric struct {
 }
 
 func neighborMetrics() []neighbormetric {
-	stats := []neighbormetric{}
+	var stats []neighbormetric
 
 	// gossip plugin might be disabled
 	neighbors := gossip.Manager().AllNeighbors()
diff --git a/plugins/webapi/plugin.go b/plugins/webapi/plugin.go
index 24f5108f..8e9944ba 100644
--- a/plugins/webapi/plugin.go
+++ b/plugins/webapi/plugin.go
@@ -2,6 +2,8 @@ package webapi
 
 import (
 	"context"
+	"errors"
+	"net/http"
 	"time"
 
 	"github.com/iotaledger/goshimmer/packages/shutdown"
@@ -18,38 +20,52 @@ const PluginName = "WebAPI"
 var (
 	// Plugin is the plugin instance of the web API plugin.
 	Plugin = node.NewPlugin(PluginName, node.Enabled, configure, run)
-	log    *logger.Logger
 	// Server is the web API server.
 	Server = echo.New()
+
+	log *logger.Logger
 )
 
-func configure(plugin *node.Plugin) {
+func configure(*node.Plugin) {
 	log = logger.NewLogger(PluginName)
+	// configure the server
 	Server.HideBanner = true
 	Server.HidePort = true
 	Server.GET("/", IndexRequest)
 }
 
-func run(plugin *node.Plugin) {
-	log.Info("Starting Web Server ...")
+func run(*node.Plugin) {
+	log.Infof("Starting %s ...", PluginName)
+	if err := daemon.BackgroundWorker("WebAPI Server", worker, shutdown.PriorityWebAPI); err != nil {
+		log.Errorf("Error starting as daemon: %s", err)
+	}
+}
 
-	daemon.BackgroundWorker("WebAPI Server", func(shutdownSignal <-chan struct{}) {
-		log.Info("Starting Web Server ... done")
+func worker(shutdownSignal <-chan struct{}) {
+	defer log.Infof("Stopping %s ... done", PluginName)
 
-		go func() {
-			if err := Server.Start(config.Node.GetString(CfgBindAddress)); err != nil {
-				log.Info("Stopping Web Server ... done")
+	stopped := make(chan struct{})
+	bindAddr := config.Node.GetString(CfgBindAddress)
+	go func() {
+		log.Infof("Started %s: http://%s", PluginName, bindAddr)
+		if err := Server.Start(bindAddr); err != nil {
+			if !errors.Is(err, http.ErrServerClosed) {
+				log.Errorf("Error serving: %s", err)
 			}
-		}()
-
-		<-shutdownSignal
+			close(stopped)
+		}
+	}()
 
-		log.Info("Stopping Web Server ...")
-		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
-		defer cancel()
+	// stop if we are shutting down or the server could not be started
+	select {
+	case <-shutdownSignal:
+	case <-stopped:
+	}
 
-		if err := Server.Shutdown(ctx); err != nil {
-			log.Errorf("Couldn't stop server cleanly: %s", err.Error())
-		}
-	}, shutdown.PriorityWebAPI)
+	log.Infof("Stopping %s ...", PluginName)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	if err := Server.Shutdown(ctx); err != nil {
+		log.Errorf("Error stopping: %s", err)
+	}
 }
-- 
GitLab