about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2024-12-29 17:21:06 +0100
committerMel <einebeere@gmail.com>2024-12-29 17:21:06 +0100
commit5025fab41ac37665ad35967f1f03c16588461605 (patch)
tree526196063f63a88e5a7877bc136e7cb993d1bf82
parent54b021e352c6e8be605f4d223916ebf97a59f9a4 (diff)
downloadspecimen-5025fab41ac37665ad35967f1f03c16588461605.tar.zst
specimen-5025fab41ac37665ad35967f1f03c16588461605.zip
Simple Go HTTP server with graceful signal handling
Signed-off-by: Mel <einebeere@gmail.com>
-rw-r--r--application/cmd/specimen/main.go58
-rw-r--r--application/pkg/specimen/handler.go19
-rw-r--r--application/pkg/specimen/server.go54
-rw-r--r--application/pkg/specimen/specimen.go5
-rw-r--r--application/pkg/util/slog.go7
5 files changed, 136 insertions, 7 deletions
diff --git a/application/cmd/specimen/main.go b/application/cmd/specimen/main.go
index 8522b03..cf7ff36 100644
--- a/application/cmd/specimen/main.go
+++ b/application/cmd/specimen/main.go
@@ -1,10 +1,64 @@
 package main
 
 import (
-	"fmt"
+	"context"
+	"log/slog"
+	"os"
+	"os/signal"
+	"time"
+
 	"git.rnrd.eu/specimen/pkg/specimen"
+	"git.rnrd.eu/specimen/pkg/util"
 )
 
+const version = "3.3.3"
+
 func main() {
-	fmt.Println(specimen.ASimpleMessage())
+	ctx := context.Background()
+	logger := createLogger()
+
+	if err := start(ctx, logger); err != nil {
+		logger.Error("failed to start the specimen application :(", util.SlogErr(err))
+		os.Exit(1)
+	}
+}
+
+func start(ctx context.Context, logger *slog.Logger) error {
+	// listen for SIGINT (or others on other systems, but who cares about those.. :3)
+	ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
+	defer cancel()
+
+	logger.Info("starting the specimen application!", slog.String("version", version))
+
+	// start the specimen server!
+	server := specimen.NewServer(logger)
+	go server.Start(ctx)
+
+	// listen for signals to gracefully shutdown the server
+	return listenForSignals(ctx, server, logger)
+}
+
+func listenForSignals(ctx context.Context, server *specimen.Server, logger *slog.Logger) error {
+	// waiting for the global context to be done (happens on SIGINT)
+	<-ctx.Done()
+	logger.Info("received signal to shutdown the server, trying to gracefully shutdown...")
+
+	shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancelShutdown()
+
+	if err := server.Shutdown(shutdownCtx); err != nil {
+		logger.Error("failed to gracefully shutdown the server :( forcing it.", util.SlogErr(err))
+		os.Exit(1)
+	}
+
+	logger.Info("specimen has been gracefully shutdown! goodbye!")
+	return nil
+}
+
+func createLogger() *slog.Logger {
+	handlerOptions := slog.HandlerOptions{
+		Level: slog.LevelDebug,
+	}
+	consoleHandler := slog.NewTextHandler(os.Stderr, &handlerOptions)
+	return slog.New(consoleHandler)
 }
diff --git a/application/pkg/specimen/handler.go b/application/pkg/specimen/handler.go
new file mode 100644
index 0000000..05a56ec
--- /dev/null
+++ b/application/pkg/specimen/handler.go
@@ -0,0 +1,19 @@
+package specimen
+
+import (
+	"log/slog"
+	"net/http"
+)
+
+type requestHandler struct {
+	logger *slog.Logger
+}
+
+func newRequestHandler(logger *slog.Logger) *requestHandler {
+	return &requestHandler{logger: logger}
+}
+
+func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	h.logger.Info("received a request!", slog.String("method", r.Method), slog.String("url", r.URL.String()))
+	w.WriteHeader(http.StatusOK)
+}
diff --git a/application/pkg/specimen/server.go b/application/pkg/specimen/server.go
new file mode 100644
index 0000000..cccf6e2
--- /dev/null
+++ b/application/pkg/specimen/server.go
@@ -0,0 +1,54 @@
+package specimen
+
+import (
+	"context"
+	"fmt"
+	"log/slog"
+	"net/http"
+	"time"
+)
+
+const port = 4444
+
+type Server struct {
+	logger  *slog.Logger
+	server  *http.Server
+	handler *requestHandler
+}
+
+func NewServer(logger *slog.Logger) *Server {
+	handler := newRequestHandler(logger)
+
+	return &Server{
+		logger: logger,
+		server: &http.Server{
+			Addr:         fmt.Sprintf(":%d", port),
+			Handler:      handler,
+			ReadTimeout:  10 * time.Second,
+			WriteTimeout: 10 * time.Second,
+		},
+	}
+}
+
+func (s *Server) Start(ctx context.Context) {
+	s.logger.Info("listening for incoming connections...")
+	err := s.server.ListenAndServe()
+
+	switch err {
+	case http.ErrServerClosed:
+		s.logger.Info("listening has been successfully stopped.")
+	default:
+		s.logger.Error("failed to start the http server.", slog.Any("error", err))
+	}
+}
+
+func (s *Server) Shutdown(shutdownCtx context.Context) error {
+	s.logger.Info("shutting down the http server...")
+	if err := s.server.Shutdown(shutdownCtx); err != nil {
+		s.logger.Error("failed to shutdown the server :(", slog.Any("error", err))
+		return err
+	}
+
+	s.logger.Info("http server has been shutdown!")
+	return nil
+}
diff --git a/application/pkg/specimen/specimen.go b/application/pkg/specimen/specimen.go
deleted file mode 100644
index 3e11364..0000000
--- a/application/pkg/specimen/specimen.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package specimen
-
-func ASimpleMessage() string {
-	return "hi! everything is still working!! :)"
-}
diff --git a/application/pkg/util/slog.go b/application/pkg/util/slog.go
new file mode 100644
index 0000000..ebae96f
--- /dev/null
+++ b/application/pkg/util/slog.go
@@ -0,0 +1,7 @@
+package util
+
+import "log/slog"
+
+func SlogErr(err error) slog.Attr {
+	return slog.Any("error", err)
+}