Skip to content
This repository has been archived by the owner on Dec 23, 2023. It is now read-only.

Commit

Permalink
Add error handler
Browse files Browse the repository at this point in the history
  • Loading branch information
razonyang committed Feb 12, 2020
1 parent 9f56b3e commit d35ace8
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 60 deletions.
43 changes: 43 additions & 0 deletions error.go
@@ -0,0 +1,43 @@
// Copyright 2020 CleverGo. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package clevergo

import (
"errors"
"net/http"
)

// Error defines an HTTP response error.
type Error interface {
error
Status() int
}

// Errors
var (
ErrNotFound = StatusError{http.StatusNotFound, errors.New("404 page not found")}
ErrMethodNotAllowed = StatusError{http.StatusMethodNotAllowed, errors.New(http.StatusText(http.StatusMethodNotAllowed))}
)

// ErrorHandler is a handler to handle error returns from handle.
type ErrorHandler interface {
Handle(ctx *Context, err error)
}

// StatusError implements Error interface.
type StatusError struct {
Code int
Err error
}

// Error implements error.Error.
func (se StatusError) Error() string {
return se.Err.Error()
}

// Status implements Error.Status.
func (se StatusError) Status() int {
return se.Code
}
19 changes: 11 additions & 8 deletions middleware_test.go
Expand Up @@ -11,24 +11,27 @@ import (
)

func echoHandler(s string) Handle {
return func(ctx *Context) {
return func(ctx *Context) error {
ctx.WriteString(s)

return nil
}
}

func echoMiddleware(s string) MiddlewareFunc {
return func(handle Handle) Handle {
return func(ctx *Context) {
return func(ctx *Context) error {
ctx.WriteString(s + " ")
handle(ctx)
return handle(ctx)
}
}
}

func terminatedMiddleware() MiddlewareFunc {
return func(handle Handle) Handle {
return func(ctx *Context) {
return func(ctx *Context) error {
ctx.WriteString("terminated")
return nil
}
}
}
Expand Down Expand Up @@ -57,15 +60,15 @@ func TestChain(t *testing.T) {

func ExampleChain() {
m1 := func(handle Handle) Handle {
return func(ctx *Context) {
return func(ctx *Context) error {
ctx.WriteString("m1 ")
handle(ctx)
return handle(ctx)
}
}
m2 := func(handle Handle) Handle {
return func(ctx *Context) {
return func(ctx *Context) error {
ctx.WriteString("m2 ")
handle(ctx)
return handle(ctx)
}
}
handle := Chain(echoHandler("hello"), m1, m2)
Expand Down
24 changes: 16 additions & 8 deletions route_test.go
Expand Up @@ -73,26 +73,33 @@ func TestRouteGroupAPI(t *testing.T) {

router := NewRouter()
api := router.Group("/api")
api.Get("/GET", func(ctx *Context) {
api.Get("/GET", func(ctx *Context) error {
get = true
return nil
})
api.Head("/GET", func(ctx *Context) {
api.Head("/GET", func(ctx *Context) error {
head = true
return nil
})
api.Options("/GET", func(ctx *Context) {
api.Options("/GET", func(ctx *Context) error {
options = true
return nil
})
api.Post("/POST", func(ctx *Context) {
api.Post("/POST", func(ctx *Context) error {
post = true
return nil
})
api.Put("/PUT", func(ctx *Context) {
api.Put("/PUT", func(ctx *Context) error {
put = true
return nil
})
api.Patch("/PATCH", func(ctx *Context) {
api.Patch("/PATCH", func(ctx *Context) error {
patch = true
return nil
})
api.Delete("/DELETE", func(ctx *Context) {
api.Delete("/DELETE", func(ctx *Context) error {
delete = true
return nil
})
api.Handler(http.MethodGet, "/Handler", httpHandler)
api.HandlerFunc(http.MethodGet, "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -226,13 +233,14 @@ func TestNestedRouteGroup(t *testing.T) {

func ExampleRoute() {
router := NewRouter()
router.Get("/posts/:page", func(ctx *Context) {
router.Get("/posts/:page", func(ctx *Context) error {
page, _ := ctx.Params.Int("page")
route := ctx.Route
prev, _ := route.URL("page", strconv.Itoa(page-1))
next, _ := route.URL("page", strconv.Itoa(page+1))
fmt.Printf("prev page url: %s\n", prev)
fmt.Printf("next page url: %s\n", next)
return nil
})

req := httptest.NewRequest(http.MethodGet, "/posts/3", nil)
Expand Down
6 changes: 4 additions & 2 deletions routegroup_test.go
Expand Up @@ -48,13 +48,15 @@ func ExampleRouteGroup() {
api := router.Group("/api")

v1 := api.Group("/v1")
v1.Get("/users/:name", func(ctx *Context) {
v1.Get("/users/:name", func(ctx *Context) error {
fmt.Printf("v1 user: %s\n", ctx.Params.String("name"))
return nil
})

v2 := api.Group("/v2")
v2.Get("/users/:name", func(ctx *Context) {
v2.Get("/users/:name", func(ctx *Context) error {
fmt.Printf("v2 user: %s\n", ctx.Params.String("name"))
return nil
})

req := httptest.NewRequest(http.MethodGet, "/api/v1/users/foo", nil)
Expand Down
46 changes: 33 additions & 13 deletions router.go
Expand Up @@ -13,19 +13,21 @@ import (
)

// Handle is a function which handle incoming request and manage outgoing response.
type Handle func(ctx *Context)
type Handle func(ctx *Context) error

// HandleHandler converts http.Handler to Handle.
func HandleHandler(handler http.Handler) Handle {
return func(ctx *Context) {
return func(ctx *Context) error {
handler.ServeHTTP(ctx.Response, ctx.Request)
return nil
}
}

// HandleHandlerFunc converts http.HandlerFunc to Handle.
func HandleHandlerFunc(f http.HandlerFunc) Handle {
return func(ctx *Context) {
return func(ctx *Context) error {
f(ctx.Response, ctx.Request)
return nil
}
}

Expand Down Expand Up @@ -91,6 +93,9 @@ type Router struct {
// The "Allow" header with allowed request methods is set before the handler
// is called.
MethodNotAllowed http.Handler

// Error Handler.
ErrorHandler ErrorHandler
}

// Make sure the Router conforms with the http.Handler interface
Expand Down Expand Up @@ -256,9 +261,10 @@ func (r *Router) ServeFiles(path string, root http.FileSystem) {

fileServer := http.FileServer(root)

r.Get(path, func(ctx *Context) {
r.Get(path, func(ctx *Context) error {
ctx.Request.URL.Path = ctx.Params.String("filepath")
fileServer.ServeHTTP(ctx.Response, ctx.Request)
return nil
})
}

Expand Down Expand Up @@ -334,18 +340,21 @@ func (r *Router) allowed(path, reqMethod string) (allow string) {
// ServeHTTP makes the router implement the http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path
ctx := r.getContext()
ctx.Request = req
ctx.Response = w

if root := r.trees[req.Method]; root != nil {
if route, ps, tsr := root.getValue(path, r.getParams); route != nil {
ctx := r.getContext()
ctx.Route = route
ctx.Request = req
ctx.Response = w
if ps != nil {
r.putParams(ps)
ctx.Params = *ps
}
route.handle(ctx)
err := route.handle(ctx)
if err != nil {
r.handleError(ctx, err)
}
r.putContext(ctx)
return
} else if req.Method != http.MethodConnect && path != "/" {
Expand Down Expand Up @@ -396,10 +405,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if r.MethodNotAllowed != nil {
r.MethodNotAllowed.ServeHTTP(w, req)
} else {
http.Error(w,
http.StatusText(http.StatusMethodNotAllowed),
http.StatusMethodNotAllowed,
)
r.handleError(ctx, ErrMethodNotAllowed)
}
return
}
Expand All @@ -409,6 +415,20 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if r.NotFound != nil {
r.NotFound.ServeHTTP(w, req)
} else {
http.NotFound(w, req)
r.handleError(ctx, ErrNotFound)
}
}

func (r *Router) handleError(ctx *Context, err error) {
if r.ErrorHandler != nil {
r.ErrorHandler.Handle(ctx, err)
return
}

switch e := err.(type) {
case StatusError:
ctx.Error(err.Error(), e.Status())
default:
ctx.Error(err.Error(), http.StatusInternalServerError)
}
}

0 comments on commit d35ace8

Please sign in to comment.