Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 213a29a

Browse files
v1 - uncaught exception handling
1 parent 832ae01 commit 213a29a

File tree

5 files changed

+174
-13
lines changed

5 files changed

+174
-13
lines changed

‎api.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type API interface {
1616
Put(string, Handler)
1717
Delete(string, Handler)
1818
Exception(string, ErrorHandler)
19+
UncaughtException(ErrorHandler)
1920
ServeHTTP(http.ResponseWriter, *http.Request)
2021
}
2122

@@ -67,6 +68,10 @@ func (a *api) Exception(code string, task ErrorHandler) {
6768
a.list.exception(code, task)
6869
}
6970

71+
func (a *api) UncaughtException(task ErrorHandler) {
72+
a.list.unhandledException(task)
73+
}
74+
7075
func (a *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
7176
a.handler.serveHTTP(w, r)
7277
}

‎context.go‎

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package rest
22

33
import (
4+
"log"
45
"net/http"
56
)
67

78
type Context interface {
89
Request() *http.Request
10+
Params() map[string]string
911
Status(int) Context
12+
Header(string, string) Context
1013
Throw(string, error)
1114
JSON(interface{})
1215
Raw(interface{})
@@ -16,22 +19,46 @@ type context struct {
1619
w http.ResponseWriter
1720
r *http.Request
1821

19-
// private
20-
end bool
21-
status int
22-
code string
23-
err error
22+
// for internal purpose
23+
params map[string]string
24+
headers map[string]string
25+
end bool
26+
status int
27+
code string
28+
err error
29+
responseProcessed bool
30+
}
31+
32+
func (ctx *context) init() {
33+
ctx.headers = make(map[string]string)
34+
}
35+
36+
func (ctx *context) destroy() {
37+
ctx.w = nil
38+
ctx.r = nil
39+
ctx.headers = nil
40+
ctx.params = nil
41+
ctx.err = nil
2442
}
2543

2644
func (ctx *context) Request() *http.Request {
2745
return ctx.r
2846
}
2947

48+
func (ctx *context) Params() map[string]string {
49+
return ctx.params
50+
}
51+
3052
func (ctx *context) Status(status int) Context {
3153
ctx.status = status
3254
return ctx
3355
}
3456

57+
func (ctx *context) Header(name string, value string) Context {
58+
ctx.headers[name] = value
59+
return ctx
60+
}
61+
3562
func (ctx *context) Throw(code string, err error) {
3663
ctx.code = code
3764
ctx.err = err
@@ -55,6 +82,7 @@ func (ctx *context) Raw(data interface{}) {
5582
func (ctx *context) write(body []byte) {
5683
var err error
5784
ctx.end = true
85+
ctx.responseProcessed = true
5886

5987
if ctx.status > 0 {
6088
ctx.w.WriteHeader(ctx.status)
@@ -65,3 +93,37 @@ func (ctx *context) write(body []byte) {
6593
// TODO: handle error
6694
}
6795
}
96+
97+
// Unhandled Exception
98+
func (ctx *context) unhandledException() {
99+
defer ctx.recover()
100+
101+
if ctx.responseProcessed || ctx.end {
102+
return
103+
}
104+
105+
// NOT FOUND handler
106+
if ctx.code == ErrCodeNotFound {
107+
ctx.Status(http.StatusNotFound)
108+
}
109+
110+
if ctx.err != nil {
111+
ctx.Header("Content-Type", "text/plain;charset=UTF-8")
112+
if ctx.status < 400 {
113+
ctx.Status(http.StatusInternalServerError)
114+
}
115+
ctx.write([]byte(ctx.err.Error()))
116+
}
117+
}
118+
119+
// recover
120+
func (ctx *context) recover() {
121+
err := recover()
122+
if err != nil {
123+
//TODO: debugger mode
124+
log.Printf("runtime error: %v", err)
125+
if !ctx.responseProcessed {
126+
http.Error(ctx.w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
127+
}
128+
}
129+
}

‎examples/uncaught-exception/main.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"net/http"
7+
"strconv"
8+
9+
"github.com/go-rs/rest-api-framework"
10+
)
11+
12+
func main() {
13+
var api = rest.New("/")
14+
15+
api.Use(func(ctx rest.Context) {
16+
fmt.Println("/* middleware")
17+
})
18+
19+
api.Get("/page/:id", func(ctx rest.Context) {
20+
// way to reproduce uncaught exception
21+
zero, _ := strconv.ParseInt(ctx.Params()["id"], 10, 32)
22+
x := 10 / zero
23+
ctx.Raw("This will never respond, if value is zero - " + string(x))
24+
})
25+
26+
api.UncaughtException(func(e error, ctx rest.Context) {
27+
log.Println("ERROR: ", e.Error())
28+
//zero, _ := strconv.ParseInt(ctx.Params()["id"], 10, 32)
29+
//_ = 10 / zero
30+
ctx.Raw("Uncaught exception is handled by user")
31+
})
32+
33+
http.ListenAndServe(":8080", api)
34+
}

‎handler.go‎

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package rest
22

33
import (
4+
"errors"
5+
"fmt"
46
"net/http"
57
"strings"
68
)
@@ -9,17 +11,38 @@ type handler struct {
911
list *list
1012
}
1113

14+
// error variables to handle expected errors
15+
var (
16+
ErrCodeNotFound = "URL_NOT_FOUND"
17+
ErrCodeRuntimeError = "RUNTIME_ERROR"
18+
)
19+
1220
func (h *handler) serveHTTP(w http.ResponseWriter, r *http.Request) {
1321
var ctx = &context{
1422
w: w,
1523
r: r,
1624
}
1725

18-
// required "/" to match pattern
19-
var uri = r.RequestURI
20-
if !strings.HasSuffix(uri, sep) {
21-
uri += sep
22-
}
26+
ctx.init()
27+
defer ctx.destroy()
28+
29+
// recovery/handle any runtime error
30+
defer func() {
31+
err := recover()
32+
if err != nil {
33+
if !ctx.end {
34+
defer h.recover(ctx)
35+
ctx.code = ErrCodeRuntimeError
36+
ctx.err = fmt.Errorf("%v", err)
37+
if h.list.uncaughtException != nil {
38+
h.list.uncaughtException(ctx.err, ctx)
39+
} else {
40+
ctx.unhandledException()
41+
}
42+
}
43+
return
44+
}
45+
}()
2346

2447
// on context done, stop execution
2548
go func() {
@@ -30,6 +53,12 @@ func (h *handler) serveHTTP(w http.ResponseWriter, r *http.Request) {
3053
}
3154
}()
3255

56+
// required "/" to match pattern
57+
var uri = r.RequestURI
58+
if !strings.HasSuffix(uri, sep) {
59+
uri += sep
60+
}
61+
3362
// STEP 1: middlewares
3463
for _, handle := range h.list.middlewares {
3564
if ctx.end || ctx.err != nil {
@@ -62,4 +91,30 @@ func (h *handler) serveHTTP(w http.ResponseWriter, r *http.Request) {
6291
handle.task(ctx.err, ctx)
6392
}
6493
}
94+
95+
// STEP 5: unhandled exceptions
96+
if !ctx.end {
97+
// if no error and still not ended that means it NOT FOUND
98+
if ctx.code == "" {
99+
ctx.Throw(ErrCodeNotFound, errors.New("404 page not found"))
100+
}
101+
102+
fmt.Println("this should call...", h.list)
103+
// if user has custom unhandled function, then execute it
104+
if h.list.uncaughtException != nil {
105+
h.list.uncaughtException(ctx.err, ctx)
106+
}
107+
}
108+
109+
// STEP 6: system handle
110+
if !ctx.end {
111+
ctx.unhandledException()
112+
}
113+
}
114+
115+
func (h *handler) recover(ctx *context) {
116+
err := recover()
117+
if err != nil {
118+
ctx.unhandledException()
119+
}
65120
}

‎list.go‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ type exception struct {
1919
}
2020

2121
type list struct {
22-
middlewares []middleware
23-
routes []route
24-
exceptions []exception
22+
middlewares []middleware
23+
routes []route
24+
exceptions []exception
25+
uncaughtException ErrorHandler
2526
}
2627

2728
func (l *list) middleware(str string, task Handler) {
@@ -51,3 +52,7 @@ func (l *list) route(method string, str string, task Handler) {
5152
func (l *list) exception(code string, task ErrorHandler) {
5253
l.exceptions = append(l.exceptions, exception{code: code, task: task})
5354
}
55+
56+
func (l *list) unhandledException(task ErrorHandler) {
57+
l.uncaughtException = task
58+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /