Skip to content

Commit

Permalink
quality of life improvements (#45)
Browse files Browse the repository at this point in the history
fixes: #44
fixes: #43
fixes: #42
  • Loading branch information
komuw authored Jun 24, 2022
1 parent 54dac30 commit b1f1598
Show file tree
Hide file tree
Showing 19 changed files with 776 additions and 143 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ Most recent version is listed first.
- errors: https://github.com/komuw/goweb/commit/2603c06ca1257d75fb170872124b2afd81eb3f3e
- logger: https://github.com/komuw/goweb/pull/39
- logging middleware: https://github.com/komuw/goweb/pull/41
- quality of life improvements: https://github.com/komuw/goweb/pull/45
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) [2022] [Komu Wairagu]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@
[![codecov](https://codecov.io/gh/komuw/goweb/branch/main/graph/badge.svg)](https://codecov.io/gh/komuw/goweb)


Taken mainly from the talk; `How I Write HTTP Web Services after Eight Years`[1] by Mat Ryer.
Taken mainly from the talk; `How I Write HTTP Web Services after Eight Years`[1][2] by Mat Ryer.


You really should not be using this code/library. The Go `net/http` package is more that enough.
If you need some extra bits, may I suggest the awesome (https://github.com/gorilla)[https://github.com/gorilla] web toolkit.


This library is made just for me, might be unsafe & I it does not generally accept code contributions.


1. https://www.youtube.com/watch?v=rWBSMsLG8po
2. https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html
Expand Down
11 changes: 9 additions & 2 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,20 @@ func (e *stackError) Unwrap() error {

// New returns an error with the supplied message. New also records the stack trace at the point it was called.
func New(text string) *stackError {
return Wrap(errors.New(text))
return wrap(errors.New(text), 3)
}

// Wrap returns err, capturing a stack trace.
func Wrap(err error) *stackError {
return wrap(err, 3)
}

func wrap(err error, skip int) *stackError {
stack := make([]uintptr, 50)
length := runtime.Callers(3, stack[:])
// skip 0 identifies the frame for `runtime.Callers` itself and
// skip 1 identifies the caller of `runtime.Callers`(ie of `wrap`).
length := runtime.Callers(skip, stack[:])

return &stackError{
err: err,
stack: stack[:length],
Expand Down
110 changes: 89 additions & 21 deletions errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package errors

import (
stdErrors "errors"
"fmt"
"io/fs"
"os"
"strings"
"testing"

Expand All @@ -27,30 +30,82 @@ func foo() error {
return New("error in foo")
}

func prepFile() error {
kk := "police"
_ = kk
if err := open("/tmp/nonExistentFile-akJGdadE.txt"); err != nil {
return err
}
return nil
}

func open(p string) error {
f, err := os.Open(p)
if err != nil {
return Wrap(err)
}
defer f.Close()

return nil
}

func TestStackError(t *testing.T) {
t.Parallel()

t.Run("success", func(t *testing.T) {
err := hello()
t.Parallel()

sterr, ok := err.(*stackError)
attest.True(t, ok)
attest.Equal(t, sterr.Error(), "error in foo")
t.Run("errors.New", func(t *testing.T) {
t.Parallel()

stackTrace := sterr.getStackTrace()
for _, v := range []string{
"goweb/errors/errors_test.go:27",
"goweb/errors/errors_test.go:20",
"goweb/errors/errors_test.go:14",
"goweb/errors/errors_test.go:32",
} {
attest.True(
t,
strings.Contains(stackTrace, v),
attest.Sprintf("\n\t%s: not found in stackTrace: %s", v, stackTrace),
)
}
err := hello()

sterr, ok := err.(*stackError)
attest.True(t, ok)
attest.Equal(t, sterr.Error(), "error in foo")

stackTrace := sterr.getStackTrace()
for _, v := range []string{
"goweb/errors/errors_test.go:30",
"goweb/errors/errors_test.go:23",
"goweb/errors/errors_test.go:17",
"goweb/errors/errors_test.go:61",
} {
attest.True(
t,
strings.Contains(stackTrace, v),
attest.Sprintf("\n\t%s: not found in stackTrace: %s", v, stackTrace),
)
}
})

t.Run("errors.Wrap", func(t *testing.T) {
t.Parallel()

err := prepFile()

sterr, ok := err.(*stackError)
attest.True(t, ok)
attest.True(t, stdErrors.Is(err, os.ErrNotExist))

stackTrace := sterr.getStackTrace()
for _, v := range []string{
"goweb/errors/errors_test.go:45",
"goweb/errors/errors_test.go:36",
"goweb/errors/errors_test.go:85",
} {
attest.True(
t,
strings.Contains(stackTrace, v),
attest.Sprintf("\n\t%s: not found in stackTrace: %s", v, stackTrace),
)
}
})
})

t.Run("formattting", func(t *testing.T) {
t.Parallel()

err := hello()

attest.Equal(t, fmt.Sprintf("%s", err), "error in foo") //nolint:gocritic
Expand All @@ -59,10 +114,10 @@ func TestStackError(t *testing.T) {

extendedFormatting := fmt.Sprintf("%+v", err)
for _, v := range []string{
"goweb/errors/errors_test.go:27",
"goweb/errors/errors_test.go:20",
"goweb/errors/errors_test.go:14",
"goweb/errors/errors_test.go:54",
"goweb/errors/errors_test.go:30",
"goweb/errors/errors_test.go:23",
"goweb/errors/errors_test.go:17",
"goweb/errors/errors_test.go:109",
} {
attest.True(
t,
Expand All @@ -71,4 +126,17 @@ func TestStackError(t *testing.T) {
)
}
})

t.Run("errors Is As Unwrap", func(t *testing.T) {
t.Parallel()

err := prepFile()
var targetErr *fs.PathError

_, ok := err.(*stackError)
attest.True(t, ok)
attest.True(t, stdErrors.Is(err, os.ErrNotExist))
attest.NotZero(t, stdErrors.Unwrap(err))
attest.True(t, stdErrors.As(err, &targetErr))
})
}
32 changes: 16 additions & 16 deletions handlers.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package main

import (
"context"
"fmt"
"log"
"net/http"
"os"
"sync"
"time"

"github.com/komuw/goweb/log"
"github.com/komuw/goweb/middleware"
)

Expand All @@ -17,7 +18,6 @@ import (
type myAPI struct {
db string
router *http.ServeMux // some router
logger *log.Logger // some logger, maybe
}

// Make `myAPI` implement the http.Handler interface(https://golang.org/pkg/net/http/#Handler)
Expand All @@ -26,35 +26,34 @@ func (s *myAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.router.ServeHTTP(w, r)
}

func (s *myAPI) GetLogger() *log.Logger {
if s.logger == nil {
s.logger = log.New(os.Stdout, "logger: ", log.Lshortfile)
}
return s.logger
func (s *myAPI) GetLogger() log.Logger {
return log.New(context.Background(), os.Stdout, 1000, false)
}

// Have one place for all routes.
// You can even move it to a routes.go file
func (s *myAPI) Routes() {
s.router.HandleFunc("/api/",
middleware.Security(s.handleAPI(), "localhost"),
middleware.All(s.handleAPI(), middleware.WithOpts("localhost")),
)
s.router.HandleFunc("/greeting",
// you can even have your handler take a `*template.Template` dependency
middleware.Security(
middleware.All(
middleware.BasicAuth(s.handleGreeting(202), "user", "passwd"),
"localhost",
middleware.WithOpts("localhost"),
),
)
s.router.HandleFunc("/serveDirectory",
middleware.Security(
middleware.All(
middleware.BasicAuth(s.handleFileServer(), "user", "passwd"),
"localhost",
middleware.WithOpts("localhost"),
),
)

s.router.HandleFunc("/check",
middleware.Security(s.handleGreeting(200), "localhost"),
middleware.All(s.handleGreeting(200),
middleware.WithOpts("localhost"),
),
)

// etc
Expand All @@ -67,7 +66,7 @@ func (s *myAPI) handleFileServer() http.HandlerFunc {
fs := http.FileServer(http.Dir("./stuff"))
realHandler := http.StripPrefix("somePrefix", fs).ServeHTTP
return func(w http.ResponseWriter, req *http.Request) {
s.GetLogger().Println(req.URL.Redacted())
s.GetLogger().Info(log.F{"msg": "handleFileServer", "redactedURL": req.URL.Redacted()})
realHandler(w, req)
}
}
Expand All @@ -89,7 +88,8 @@ func (s *myAPI) handleAPI() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// intialize somethings only once for perf
once.Do(func() {
s.GetLogger().Println("called only once during the first request")
s.GetLogger().Info(log.F{"msg": "called only once during the first request"})

serverStart = time.Now()
})

Expand All @@ -111,7 +111,7 @@ func (s *myAPI) handleGreeting(code int) http.HandlerFunc {
// use code, which is a dependency specific to this handler

nonce := middleware.GetCspNonce(r.Context())
s.GetLogger().Println("\n\t handleGreeting, nonce: ", nonce)
s.GetLogger().Info(log.F{"msg": "handleGreeting", "nonce": nonce})

w.WriteHeader(code)
}
Expand Down
Loading

0 comments on commit b1f1598

Please sign in to comment.