From 6079b1d963f34ada5c4b25363f2319901e283936 Mon Sep 17 00:00:00 2001 From: "Gabriel A. Giovanini" Date: Sat, 8 Jun 2024 00:01:44 +0200 Subject: feat: Add error handling --- pkg/ext/compression.go | 2 +- pkg/ext/log.go | 53 +++++++++++++++++++++++++++++++ pkg/ext/router.go | 72 +++++++++++++++++++++++++++++++++++++++++++ pkg/git/git.go | 3 -- pkg/handler/about/handler.go | 10 +++--- pkg/handler/config/handler.go | 16 +++++----- pkg/handler/git/handler.go | 61 ++++++++++++++++-------------------- pkg/handler/router.go | 36 ++++++++++------------ pkg/handler/static/handler.go | 6 ++-- pkg/service/git.go | 29 +++++++++++++---- 10 files changed, 208 insertions(+), 80 deletions(-) create mode 100644 pkg/ext/log.go create mode 100644 pkg/ext/router.go (limited to 'pkg') diff --git a/pkg/ext/compression.go b/pkg/ext/compression.go index 92144b8..57ad49a 100644 --- a/pkg/ext/compression.go +++ b/pkg/ext/compression.go @@ -24,7 +24,7 @@ type CompressionResponseWriter struct { compressWriter io.Writer } -func Compress(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { +func Compress(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if accept, ok := r.Header["Accept-Encoding"]; ok { if compress, algo := GetCompressionWriter(u.FirstOrZero(accept), w); algo != "" { diff --git a/pkg/ext/log.go b/pkg/ext/log.go new file mode 100644 index 0000000..a9d26a9 --- /dev/null +++ b/pkg/ext/log.go @@ -0,0 +1,53 @@ +package ext + +import ( + "log/slog" + "net/http" + "time" +) + +type statusWraper struct { + statusCode int + innerWriter http.ResponseWriter +} + +func (s *statusWraper) Header() http.Header { + return s.innerWriter.Header() +} + +func (s *statusWraper) Write(b []byte) (int, error) { + return s.innerWriter.Write(b) +} + +func (s *statusWraper) WriteHeader(statusCode int) { + s.statusCode = statusCode + s.innerWriter.WriteHeader(statusCode) +} + +func (s *statusWraper) StatusCode() int { + if s.statusCode == 0 { + return 200 + } + return s.statusCode +} + +func wrap(w http.ResponseWriter) *statusWraper { + return &statusWraper{ + innerWriter: w, + } +} + +func Log(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + t := time.Now() + s := wrap(w) + next(s, r) + slog.Info( + "Http request", + "method", r.Method, + "code", s.StatusCode(), + "path", r.URL, + "elapsed", time.Since(t), + ) + } +} diff --git a/pkg/ext/router.go b/pkg/ext/router.go new file mode 100644 index 0000000..5d22814 --- /dev/null +++ b/pkg/ext/router.go @@ -0,0 +1,72 @@ +package ext + +import ( + "errors" + "fmt" + "net/http" + + "git.gabrielgio.me/cerrado/pkg/service" + "git.gabrielgio.me/cerrado/templates" +) + +type ( + Router struct { + middlewares []Middleware + router *http.ServeMux + } + Middleware func(next http.HandlerFunc) http.HandlerFunc + ErrorRequestHandler func(w http.ResponseWriter, r *http.Request) error +) + +func NewRouter() *Router { + return &Router{ + router: http.NewServeMux(), + } +} +func (r *Router) Handler() http.Handler { + return r.router +} + +func (r *Router) AddMiddleware(middleware Middleware) { + r.middlewares = append(r.middlewares, middleware) +} + +func wrapError(next ErrorRequestHandler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := next(w, r); err != nil { + if errors.Is(err, service.RepositoryNotFoundErr) { + NotFound(w) + } else { + InternalServerError(w, err) + } + } + } +} + +func (r *Router) run(next ErrorRequestHandler) http.HandlerFunc { + return func(w http.ResponseWriter, re *http.Request) { + req := wrapError(next) + for _, r := range r.middlewares { + req = r(req) + } + req(w, re) + } +} + +func (r *Router) HandleFunc(path string, handler ErrorRequestHandler) { + r.router.HandleFunc(path, r.run(handler)) +} + +func NotFound(w http.ResponseWriter) { + w.WriteHeader(http.StatusNotFound) + templates.WritePageTemplate(w, &templates.ErrorPage{ + Message: "Not Found", + }) +} + +func InternalServerError(w http.ResponseWriter, err error) { + w.WriteHeader(http.StatusInternalServerError) + templates.WritePageTemplate(w, &templates.ErrorPage{ + Message: fmt.Sprintf("Internal Server Error:\n%s", err.Error()), + }) +} diff --git a/pkg/git/git.go b/pkg/git/git.go index 6a7b91f..ad5d3bc 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -99,9 +99,6 @@ func (g *GitRepository) Commits() ([]*object.Commit, error) { } commits = append(commits, c) } - if err != nil { - return nil, err - } return commits, nil } diff --git a/pkg/handler/about/handler.go b/pkg/handler/about/handler.go index 1acde60..ac3d314 100644 --- a/pkg/handler/about/handler.go +++ b/pkg/handler/about/handler.go @@ -2,7 +2,6 @@ package about import ( "io" - "log/slog" "net/http" "os" @@ -27,17 +26,15 @@ func NewAboutHandler(configRepo configurationRepository) *AboutHandler { return &AboutHandler{configRepo.GetRootReadme()} } -func (g *AboutHandler) About(w http.ResponseWriter, _ *http.Request) { +func (g *AboutHandler) About(w http.ResponseWriter, _ *http.Request) error { f, err := os.Open(g.readmePath) if err != nil { - slog.Error("Error loading readme file", "error", err) - return + return err } bs, err := io.ReadAll(f) if err != nil { - slog.Error("Error reading readme file bytes", "error", err) - return + return err } extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock @@ -54,4 +51,5 @@ func (g *AboutHandler) About(w http.ResponseWriter, _ *http.Request) { Body: bs, } templates.WritePageTemplate(w, gitList) + return nil } diff --git a/pkg/handler/config/handler.go b/pkg/handler/config/handler.go index 30f4283..c43b54d 100644 --- a/pkg/handler/config/handler.go +++ b/pkg/handler/config/handler.go @@ -3,7 +3,6 @@ package config import ( "bytes" "encoding/json" - "log/slog" "net/http" "github.com/alecthomas/chroma/v2/formatters/html" @@ -11,6 +10,7 @@ import ( "github.com/alecthomas/chroma/v2/styles" "git.gabrielgio.me/cerrado/pkg/config" + "git.gabrielgio.me/cerrado/pkg/ext" "git.gabrielgio.me/cerrado/templates" ) @@ -21,8 +21,8 @@ type ( } ) -func ConfigFile(configRepo configurationRepository) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, _ *http.Request) { +func ConfigFile(configRepo configurationRepository) ext.ErrorRequestHandler { + return func(w http.ResponseWriter, _ *http.Request) error { config := struct { RootReadme string @@ -34,8 +34,7 @@ func ConfigFile(configRepo configurationRepository) func(http.ResponseWriter, *h b, err := json.MarshalIndent(config, "", " ") if err != nil { - slog.Error("Error parsing json", "error", err) - return + return err } lexer := lexers.Get("json") @@ -45,15 +44,13 @@ func ConfigFile(configRepo configurationRepository) func(http.ResponseWriter, *h ) iterator, err := lexer.Tokenise(nil, string(b)) if err != nil { - slog.Error("Error tokenise", "error", err) - return + return err } var code bytes.Buffer err = formatter.Format(&code, style, iterator) if err != nil { - slog.Error("Error format", "error", err) - return + return err } hello := &templates.ConfigPage{ @@ -61,5 +58,6 @@ func ConfigFile(configRepo configurationRepository) func(http.ResponseWriter, *h } templates.WritePageTemplate(w, hello) + return nil } } diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go index 7bdf372..d952fef 100644 --- a/pkg/handler/git/handler.go +++ b/pkg/handler/git/handler.go @@ -3,7 +3,6 @@ package git import ( "bytes" "io" - "log/slog" "net/http" "os" "path/filepath" @@ -50,23 +49,20 @@ func NewGitHandler(gitService gitService, confRepo configurationRepository) *Git } } -func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) { +func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error { repos, err := g.gitService.ListRepositories() if err != nil { - slog.Error("Error listing repo", "error", err) - return + return err } f, err := os.Open(g.readmePath) if err != nil { - slog.Error("Error loading readme file", "error", err) - return + return err } bs, err := io.ReadAll(f) if err != nil { - slog.Error("Error reading readme file bytes", "error", err) - return + return err } extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock @@ -84,15 +80,15 @@ func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) { About: bs, } templates.WritePageTemplate(w, gitList) + return nil } -func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) { +func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref, err := g.gitService.GetHead(name) if err != nil { - slog.Error("Error loading head", "error", err) - return + return err } gitList := &templates.GitItemPage{ @@ -101,15 +97,15 @@ func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) { GitItemBase: &templates.GitItemSummaryPage{}, } templates.WritePageTemplate(w, gitList) + return nil } -func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) { +func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref, err := g.gitService.GetHead(name) if err != nil { - slog.Error("Error loading head", "error", err) - return + return err } gitList := &templates.GitItemPage{ Name: name, @@ -117,28 +113,26 @@ func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) { GitItemBase: &templates.GitItemAboutPage{}, } templates.WritePageTemplate(w, gitList) + return nil } -func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) { +func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error { ext.SetHTML(w) name := r.PathValue("name") tags, err := g.gitService.ListTags(name) if err != nil { - slog.Error("Error loading tags", "error", err) - return + return err } branches, err := g.gitService.ListBranches(name) if err != nil { - slog.Error("Error loading branches", "error", err) - return + return err } ref, err := g.gitService.GetHead(name) if err != nil { - slog.Error("Error loading head", "error", err) - return + return err } gitList := &templates.GitItemPage{ @@ -150,9 +144,10 @@ func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) { }, } templates.WritePageTemplate(w, gitList) + return nil } -func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) { +func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -160,8 +155,7 @@ func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) { tree, err := g.gitService.GetTree(name, ref, rest) if err != nil { - slog.Error("Error loading tree", "error", err) - return + return err } gitList := &templates.GitItemPage{ @@ -175,9 +169,10 @@ func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) { }, } templates.WritePageTemplate(w, gitList) + return nil } -func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) { +func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -185,8 +180,7 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) { file, err := g.gitService.GetFileContent(name, ref, rest) if err != nil { - slog.Error("Error loading blob", "error", err) - return + return err } filename := filepath.Base(rest) @@ -197,15 +191,13 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) { ) iterator, err := lexer.Tokenise(nil, file) if err != nil { - slog.Error("Error tokenise", "error", err) - return + return err } var code bytes.Buffer err = formatter.Format(&code, style, iterator) if err != nil { - slog.Error("Error format", "error", err) - return + return err } gitList := &templates.GitItemPage{ @@ -217,17 +209,17 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) { }, } templates.WritePageTemplate(w, gitList) + return nil } -func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) { +func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") commits, err := g.gitService.ListCommits(name, ref) if err != nil { - slog.Error("Error loading commits", "error", err) - return + return err } gitList := &templates.GitItemPage{ @@ -238,6 +230,7 @@ func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) { }, } templates.WritePageTemplate(w, gitList) + return nil } func GetLexers(filename string) chroma.Lexer { diff --git a/pkg/handler/router.go b/pkg/handler/router.go index bf13ad5..3da812f 100644 --- a/pkg/handler/router.go +++ b/pkg/handler/router.go @@ -20,9 +20,9 @@ func MountHandler( configRepo *serverconfig.ConfigurationRepository, ) (http.Handler, error) { var ( - gitHandler = git.NewGitHandler(gitService, configRepo) - aboutHandler = about.NewAboutHandler(configRepo) - configHander = config.ConfigFile(configRepo) + gitHandler = git.NewGitHandler(gitService, configRepo) + aboutHandler = about.NewAboutHandler(configRepo) + configHandler = config.ConfigFile(configRepo) ) staticHandler, err := static.ServeStaticHandler() @@ -30,21 +30,19 @@ func MountHandler( return nil, err } - mux := http.NewServeMux() + mux := ext.NewRouter() + mux.AddMiddleware(ext.Compress) + mux.AddMiddleware(ext.Log) - mux.HandleFunc("/static/{file}", m(staticHandler)) - mux.HandleFunc("/{name}/about/{$}", m(gitHandler.About)) - mux.HandleFunc("/{name}", m(gitHandler.Summary)) - mux.HandleFunc("/{name}/refs/{$}", m(gitHandler.Refs)) - mux.HandleFunc("/{name}/tree/{ref}/{rest...}", m(gitHandler.Tree)) - mux.HandleFunc("/{name}/blob/{ref}/{rest...}", m(gitHandler.Blob)) - mux.HandleFunc("/{name}/log/{ref}", m(gitHandler.Log)) - mux.HandleFunc("/config", m(configHander)) - mux.HandleFunc("/about", m(aboutHandler.About)) - mux.HandleFunc("/", m(gitHandler.List)) - return mux, nil -} - -func m(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { - return ext.Compress(next) + mux.HandleFunc("/static/{file}", staticHandler) + mux.HandleFunc("/{name}/about/{$}", gitHandler.About) + mux.HandleFunc("/{name}", gitHandler.Summary) + mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs) + mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree) + mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob) + mux.HandleFunc("/{name}/log/{ref}", gitHandler.Log) + mux.HandleFunc("/config", configHandler) + mux.HandleFunc("/about", aboutHandler.About) + mux.HandleFunc("/", gitHandler.List) + return mux.Handler(), nil } diff --git a/pkg/handler/static/handler.go b/pkg/handler/static/handler.go index 5155068..0973d75 100644 --- a/pkg/handler/static/handler.go +++ b/pkg/handler/static/handler.go @@ -10,19 +10,21 @@ import ( "git.gabrielgio.me/cerrado/static" ) -func ServeStaticHandler() (func(w http.ResponseWriter, r *http.Request), error) { +func ServeStaticHandler() (ext.ErrorRequestHandler, error) { staticFs, err := fs.Sub(static.Static, ".") if err != nil { return nil, err } - return func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) error { var ( f = r.PathValue("file") e = filepath.Ext(f) m = mime.TypeByExtension(e) ) ext.SetMIME(w, m) + w.Header().Add("Cache-Control", "immutable") http.ServeFileFS(w, r, staticFs, f) + return nil }, nil } diff --git a/pkg/service/git.go b/pkg/service/git.go index 31a1cbb..94e2adc 100644 --- a/pkg/service/git.go +++ b/pkg/service/git.go @@ -1,6 +1,7 @@ package service import ( + "errors" "log/slog" "os" "path" @@ -31,6 +32,10 @@ type ( } ) +var ( + RepositoryNotFoundErr = errors.New("Repository not found") +) + // TODO: make it configurable const timeFormat = "2006.01.02 15:04:05" @@ -84,8 +89,10 @@ func (g *GitService) ListRepositories() ([]*Repository, error) { } func (g *GitService) ListCommits(name, ref string) ([]*object.Commit, error) { - // TODO: handle nil r := g.configRepo.GetByName(name) + if r == nil { + return nil, RepositoryNotFoundErr + } repo, err := git.OpenRepository(r.Path) if err != nil { @@ -100,8 +107,10 @@ func (g *GitService) ListCommits(name, ref string) ([]*object.Commit, error) { } func (g *GitService) GetTree(name, ref, path string) (*object.Tree, error) { - // TODO: handle nil r := g.configRepo.GetByName(name) + if r == nil { + return nil, RepositoryNotFoundErr + } repo, err := git.OpenRepository(r.Path) if err != nil { @@ -116,8 +125,10 @@ func (g *GitService) GetTree(name, ref, path string) (*object.Tree, error) { } func (g *GitService) GetFileContent(name, ref, path string) (string, error) { - // TODO: handle nil r := g.configRepo.GetByName(name) + if r == nil { + return "", RepositoryNotFoundErr + } repo, err := git.OpenRepository(r.Path) if err != nil { @@ -132,8 +143,10 @@ func (g *GitService) GetFileContent(name, ref, path string) (string, error) { } func (g *GitService) ListTags(name string) ([]*object.Tag, error) { - // TODO: handle nil r := g.configRepo.GetByName(name) + if r == nil { + return nil, RepositoryNotFoundErr + } repo, err := git.OpenRepository(r.Path) if err != nil { @@ -143,8 +156,10 @@ func (g *GitService) ListTags(name string) ([]*object.Tag, error) { } func (g *GitService) ListBranches(name string) ([]*plumbing.Reference, error) { - // TODO: handle nil r := g.configRepo.GetByName(name) + if r == nil { + return nil, RepositoryNotFoundErr + } repo, err := git.OpenRepository(r.Path) if err != nil { @@ -154,8 +169,10 @@ func (g *GitService) ListBranches(name string) ([]*plumbing.Reference, error) { } func (g *GitService) GetHead(name string) (*plumbing.Reference, error) { - // TODO: handle nil r := g.configRepo.GetByName(name) + if r == nil { + return nil, RepositoryNotFoundErr + } repo, err := git.OpenRepository(r.Path) if err != nil { -- cgit v1.2.3