Handle context cancellations and avoid logged errors.
This commit is contained in:
parent
0f7da0864e
commit
acd38c8205
8 changed files with 83 additions and 35 deletions
|
|
@ -1,5 +1,10 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// AuthenticatedUserKey is the key value used to store the authenticated user in context
|
// AuthenticatedUserKey is the key value used to store the authenticated user in context
|
||||||
AuthenticatedUserKey = "auth_user"
|
AuthenticatedUserKey = "auth_user"
|
||||||
|
|
@ -13,3 +18,8 @@ const (
|
||||||
// PasswordTokenKey is the key value used to store a password token in context
|
// PasswordTokenKey is the key value used to store a password token in context
|
||||||
PasswordTokenKey = "password_token"
|
PasswordTokenKey = "password_token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsCanceledError determines if an error is due to a context cancelation
|
||||||
|
func IsCanceledError(err error) bool {
|
||||||
|
return errors.Is(err, context.Canceled)
|
||||||
|
}
|
||||||
|
|
|
||||||
24
context/context_test.go
Normal file
24
context/context_test.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsCanceled(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
assert.False(t, IsCanceledError(ctx.Err()))
|
||||||
|
cancel()
|
||||||
|
assert.True(t, IsCanceledError(ctx.Err()))
|
||||||
|
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond)
|
||||||
|
time.Sleep(time.Microsecond * 2)
|
||||||
|
cancel()
|
||||||
|
assert.False(t, IsCanceledError(ctx.Err()))
|
||||||
|
|
||||||
|
assert.False(t, IsCanceledError(errors.New("test error")))
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mikestefanello/pagoda/context"
|
||||||
"github.com/mikestefanello/pagoda/middleware"
|
"github.com/mikestefanello/pagoda/middleware"
|
||||||
"github.com/mikestefanello/pagoda/services"
|
"github.com/mikestefanello/pagoda/services"
|
||||||
|
|
||||||
|
|
@ -143,7 +144,10 @@ func (c *Controller) cachePage(ctx echo.Context, page Page, html *bytes.Buffer)
|
||||||
|
|
||||||
err := marshaler.New(c.Container.Cache).Set(ctx.Request().Context(), key, cp, opts)
|
err := marshaler.New(c.Container.Cache).Set(ctx.Request().Context(), key, cp, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger().Errorf("failed to cache page: %v", err)
|
if !context.IsCanceledError(err) {
|
||||||
|
ctx.Logger().Errorf("failed to cache page: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,6 +162,9 @@ func (c *Controller) Redirect(ctx echo.Context, route string, routeParams ...int
|
||||||
|
|
||||||
// Fail is a helper to fail a request by returning a 500 error and logging the error
|
// Fail is a helper to fail a request by returning a 500 error and logging the error
|
||||||
func (c *Controller) Fail(ctx echo.Context, err error, log string) error {
|
func (c *Controller) Fail(ctx echo.Context, err error, log string) error {
|
||||||
|
if context.IsCanceledError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
ctx.Logger().Errorf("%s: %v", log, err)
|
ctx.Logger().Errorf("%s: %v", log, err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ func LoadAuthenticatedUser(authClient *services.AuthClient) echo.MiddlewareFunc
|
||||||
c.Set(context.AuthenticatedUserKey, u)
|
c.Set(context.AuthenticatedUserKey, u)
|
||||||
c.Logger().Infof("auth user loaded in to context: %d", u.ID)
|
c.Logger().Infof("auth user loaded in to context: %d", u.ID)
|
||||||
default:
|
default:
|
||||||
|
if context.IsCanceledError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
c.Logger().Errorf("error querying for authenticated user: %v", err)
|
c.Logger().Errorf("error querying for authenticated user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +58,9 @@ func LoadValidPasswordToken(authClient *services.AuthClient) echo.MiddlewareFunc
|
||||||
msg.Warning(c, "The link is either invalid or has expired. Please request a new one.")
|
msg.Warning(c, "The link is either invalid or has expired. Please request a new one.")
|
||||||
return c.Redirect(http.StatusFound, c.Echo().Reverse("forgot_password"))
|
return c.Redirect(http.StatusFound, c.Echo().Reverse("forgot_password"))
|
||||||
default:
|
default:
|
||||||
|
if context.IsCanceledError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,15 @@ func ServeCachedPage(ch *cache.Cache) echo.MiddlewareFunc {
|
||||||
new(CachedPage),
|
new(CachedPage),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == redis.Nil {
|
switch {
|
||||||
|
case err == redis.Nil:
|
||||||
c.Logger().Info("no cached page found")
|
c.Logger().Info("no cached page found")
|
||||||
} else {
|
case context.IsCanceledError(err):
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
c.Logger().Errorf("failed getting cached page: %v", err)
|
c.Logger().Errorf("failed getting cached page: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ func LoadUser(orm *ent.Client) echo.MiddlewareFunc {
|
||||||
case *ent.NotFoundError:
|
case *ent.NotFoundError:
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
default:
|
default:
|
||||||
|
if context.IsCanceledError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package routes
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mikestefanello/pagoda/context"
|
||||||
"github.com/mikestefanello/pagoda/controller"
|
"github.com/mikestefanello/pagoda/controller"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
@ -12,8 +13,8 @@ type Error struct {
|
||||||
controller.Controller
|
controller.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Get(err error, c echo.Context) {
|
func (e *Error) Get(err error, ctx echo.Context) {
|
||||||
if c.Response().Committed {
|
if ctx.Response().Committed || context.IsCanceledError(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,19 +24,19 @@ func (e *Error) Get(err error, c echo.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if code >= 500 {
|
if code >= 500 {
|
||||||
c.Logger().Error(err)
|
ctx.Logger().Error(err)
|
||||||
} else {
|
} else {
|
||||||
c.Logger().Info(err)
|
ctx.Logger().Info(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := controller.NewPage(c)
|
page := controller.NewPage(ctx)
|
||||||
p.Layout = "main"
|
page.Layout = "main"
|
||||||
p.Title = http.StatusText(code)
|
page.Title = http.StatusText(code)
|
||||||
p.Name = "error"
|
page.Name = "error"
|
||||||
p.StatusCode = code
|
page.StatusCode = code
|
||||||
p.HTMX.Request.Enabled = false
|
page.HTMX.Request.Enabled = false
|
||||||
|
|
||||||
if err = e.RenderPage(c, p); err != nil {
|
if err = e.RenderPage(ctx, page); err != nil {
|
||||||
c.Logger().Error(err)
|
ctx.Logger().Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,6 @@ type VerifyEmail struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VerifyEmail) Get(ctx echo.Context) error {
|
func (c *VerifyEmail) Get(ctx echo.Context) error {
|
||||||
c.verifyToken(ctx)
|
|
||||||
|
|
||||||
return c.Redirect(ctx, "home")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *VerifyEmail) verifyToken(ctx echo.Context) {
|
|
||||||
var usr *ent.User
|
var usr *ent.User
|
||||||
|
|
||||||
// Validate the token
|
// Validate the token
|
||||||
|
|
@ -27,7 +21,7 @@ func (c *VerifyEmail) verifyToken(ctx echo.Context) {
|
||||||
email, err := c.Container.Auth.ValidateEmailVerificationToken(token)
|
email, err := c.Container.Auth.ValidateEmailVerificationToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg.Warning(ctx, "The link is either invalid or has expired.")
|
msg.Warning(ctx, "The link is either invalid or has expired.")
|
||||||
return
|
return c.Redirect(ctx, "home")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it matches the authenticated user
|
// Check if it matches the authenticated user
|
||||||
|
|
@ -47,24 +41,23 @@ func (c *VerifyEmail) verifyToken(ctx echo.Context) {
|
||||||
Only(ctx.Request().Context())
|
Only(ctx.Request().Context())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger().Errorf("error querying user during email verification: %v", err)
|
return c.Fail(ctx, err, "query failed loading email verification token user")
|
||||||
msg.Danger(ctx, "An error occurred. Please try again.")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the user
|
// Verify the user, if needed
|
||||||
err = c.Container.ORM.User.
|
if !usr.Verified {
|
||||||
Update().
|
err = c.Container.ORM.User.
|
||||||
SetVerified(true).
|
Update().
|
||||||
Where(user.ID(usr.ID)).
|
SetVerified(true).
|
||||||
Exec(ctx.Request().Context())
|
Where(user.ID(usr.ID)).
|
||||||
|
Exec(ctx.Request().Context())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger().Errorf("error setting user as verified: %v", err)
|
return c.Fail(ctx, err, "failed to set user as verified")
|
||||||
msg.Danger(ctx, "An error occurred. Please try again.")
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Success(ctx, "Your email has been successfully verified.")
|
msg.Success(ctx, "Your email has been successfully verified.")
|
||||||
|
return c.Redirect(ctx, "home")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue