commit 8e345ffa44a27eca24847af59f7f00e0e2e8a827 Author: Yadunand Prem Date: Tue Nov 19 16:37:31 2024 -0500 feat: initial commit diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..aaddd6a --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1731890469, + "narHash": "sha256-D1FNZ70NmQEwNxpSSdTXCSklBH1z2isPR84J6DQrJGs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5083ec887760adfe12af64830a66807423a859a7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..dd13583 --- /dev/null +++ b/flake.nix @@ -0,0 +1,23 @@ +{ + description = "yadunut.dev website"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils = { + url = "github:numtide/flake-utils"; + }; + }; + + outputs = {nixpkgs, flake-utils, ...}: + flake-utils.lib.eachDefaultSystem (system: + let pkgs = import nixpkgs { + inherit system; + }; in { + devShells = { + default = pkgs.mkShell { + buildInputs = with pkgs;[ + go gopls gotools go-tools + ]; + }; + }; + }); +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3fa718a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.yadunut.dev/yadunut.dev + +go 1.23.3 diff --git a/main.go b/main.go new file mode 100644 index 0000000..8460c1e --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "log/slog" + + "git.yadunut.dev/yadunut.dev/site" +) + +func main() { + c := site.NewConfig(slog.LevelDebug, 8080) + c.Logger.Info("Starting server") + if err := site.Run(c); err != nil { + c.Logger.Error(fmt.Sprintf("failed with error %s", err)) + } +} diff --git a/site/handler.go b/site/handler.go new file mode 100644 index 0000000..f58eaee --- /dev/null +++ b/site/handler.go @@ -0,0 +1,61 @@ +package site + +import ( + "fmt" + "log/slog" + "net/http" + "sync" +) + +type counter struct { + hits map[string]int + mu sync.Mutex +} + +type handler struct { + logger *slog.Logger + requestsCounter *counter +} + +func newHandler(logger *slog.Logger) *handler { + return &handler{ + logger: logger, + requestsCounter: &counter{hits: make(map[string]int)}, + } +} + +func (h *handler) LoggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("Received request", "path", r.URL.Path) + next.ServeHTTP(w, r) + h.logger.Debug("Finished Request", "path", r.URL.Path) + }) +} + +func (h *handler) RequestsCounterMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h.requestsCounter.mu.Lock() + h.requestsCounter.hits[r.URL.Path] += 1 + h.requestsCounter.mu.Unlock() + next.ServeHTTP(w, r) + }) +} + +func (h *handler) Ping(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Pong") +} + +func (h *handler) Metrics(w http.ResponseWriter, r *http.Request) { + h.requestsCounter.mu.Lock() + defer h.requestsCounter.mu.Unlock() + for path, hits := range h.requestsCounter.hits { + fmt.Fprintf(w, "http_requests_total{handler=\"%s\"} %d\n", path, hits) + } +} +func (h *handler) Healthz(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Ok") +} + +func (h *handler) NotFound(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Page Not Found", http.StatusNotFound) +} diff --git a/site/router.go b/site/router.go new file mode 100644 index 0000000..d0c0977 --- /dev/null +++ b/site/router.go @@ -0,0 +1,32 @@ +package site + +import "net/http" + +func middlewares(h *handler, next http.Handler) http.Handler { + return h.LoggingMiddleware(h.RequestsCounterMiddleware(next)) +} + +func createRoutes(h *handler) map[string]http.Handler { + routes := map[string]http.Handler{ + "/ping": middlewares(h, http.HandlerFunc(h.Ping)), + "/metrics": middlewares(h, http.HandlerFunc(h.Metrics)), + "/healthz": middlewares(h, http.HandlerFunc(h.Healthz)), + } + return routes +} + +func newRouter(handler *handler) http.Handler { + mux := http.NewServeMux() + for pattern, handler := range createRoutes(handler) { + mux.Handle(pattern, handler) + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if h, pattern := mux.Handler(r); pattern != "" { + h.ServeHTTP(w, r) + } else { + middlewares(handler, http.HandlerFunc(handler.NotFound)).ServeHTTP(w, r) + http.Error(w, "Boo", http.StatusNotFound) + } + }) +} diff --git a/site/site.go b/site/site.go new file mode 100644 index 0000000..871f6c1 --- /dev/null +++ b/site/site.go @@ -0,0 +1,26 @@ +package site + +import ( + "fmt" + "log/slog" + "net/http" + "os" +) + +type Config struct { + Logger *slog.Logger + Port int +} + +func NewConfig(logLevel slog.Level, port int) Config { + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + return Config{ + Logger: logger, + Port: port, + } +} + +func Run(c Config) error { + h := newHandler(c.Logger) + return http.ListenAndServe(fmt.Sprintf(":%d", c.Port), newRouter(h)) +}