aboutsummaryrefslogtreecommitdiff
path: root/lib/http
diff options
context:
space:
mode:
authorShulhan <m.shulhan@gmail.com>2020-11-27 19:49:50 +0700
committerShulhan <m.shulhan@gmail.com>2020-11-27 19:49:50 +0700
commitcaa050939faabc96a7c5a7ca53d1f844d64d010d (patch)
tree22773a02d5f6c33cd107b6bd455173946d235b62 /lib/http
parente46ab6bc3b6aed18fd11269a578c7b9614fc2e4b (diff)
downloadpakakeh.go-caa050939faabc96a7c5a7ca53d1f844d64d010d.tar.xz
http: allow Endpoint to register custom error handler
The new field ErrorHandler on Endpoint allow the implementor to define their own function to handler error from Endpoint.Call. If the ErrorHandler is nil it will default to DefaultErrorHandler.
Diffstat (limited to 'lib/http')
-rw-r--r--lib/http/callback_error_handler.go64
-rw-r--r--lib/http/endpoint.go42
-rw-r--r--lib/http/endpoint_example_test.go62
-rw-r--r--lib/http/http.go8
-rw-r--r--lib/http/route.go3
5 files changed, 144 insertions, 35 deletions
diff --git a/lib/http/callback_error_handler.go b/lib/http/callback_error_handler.go
new file mode 100644
index 00000000..199e1a12
--- /dev/null
+++ b/lib/http/callback_error_handler.go
@@ -0,0 +1,64 @@
+// Copyright 2020, Shulhan <ms@kilabit.info>. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package http
+
+import (
+ "encoding/json"
+ "errors"
+ "log"
+ "net/http"
+
+ liberrors "github.com/shuLhan/share/lib/errors"
+)
+
+//
+// CallbackErrorHandler define the function that can be used to handle an
+// error returned from Endpoint.Call.
+// By default, if Endpoint.Call is nil, it will use DefaultErrorHandler.
+//
+type CallbackErrorHandler func(http.ResponseWriter, *http.Request, error)
+
+//
+// DefaultErrorHandler define the default function that will called to handle
+// the error returned from Callback function, if the Endpoint.ErrorHandler is
+// not defined.
+//
+// First, it will check if error instance of errors.E. If its true, it will
+// use the Code value for HTTP status code, otherwise if its zero or invalid,
+// it will set to http.StatusInternalServerError.
+//
+// Second, it will set the HTTP content-type to "application/json" and write
+// the response body as JSON format,
+//
+// {"code":<HTTP_STATUS_CODE>, "message":<err.Error()>}
+//
+func DefaultErrorHandler(res http.ResponseWriter, req *http.Request, err error) {
+ errInternal := &liberrors.E{}
+ if errors.As(err, &errInternal) {
+ if errInternal.Code <= 0 || errInternal.Code >= 512 {
+ errInternal.Code = http.StatusInternalServerError
+ }
+ } else {
+ log.Printf("DefaultErrorHandler: %d %s %s %s\n",
+ http.StatusInternalServerError,
+ req.Method, req.URL.Path, err)
+
+ errInternal = liberrors.Internal(err)
+ }
+
+ res.Header().Set(HeaderContentType, ContentTypeJSON)
+ res.WriteHeader(errInternal.Code)
+
+ rsp, err := json.Marshal(errInternal)
+ if err != nil {
+ log.Println("DefaultErrorHandler: " + err.Error())
+ return
+ }
+
+ _, err = res.Write(rsp)
+ if err != nil {
+ log.Println("DefaultErrorHandler: " + err.Error())
+ }
+}
diff --git a/lib/http/endpoint.go b/lib/http/endpoint.go
index d3aa47cd..0df428ce 100644
--- a/lib/http/endpoint.go
+++ b/lib/http/endpoint.go
@@ -6,15 +6,12 @@ package http
import (
"bytes"
- "encoding/json"
- stderrors "errors"
"io/ioutil"
"log"
"net/http"
"net/url"
"github.com/shuLhan/share/lib/debug"
- "github.com/shuLhan/share/lib/errors"
)
//
@@ -41,6 +38,10 @@ type Endpoint struct {
// Call is the main process of route.
Call Callback
+
+ // ErrorHandler define the function that will handle the error
+ // returned from Call.
+ ErrorHandler CallbackErrorHandler
}
//
@@ -117,7 +118,7 @@ func (ep *Endpoint) call(
for _, eval := range evaluators {
e = eval(req, reqBody)
if e != nil {
- ep.error(res, req, e)
+ ep.ErrorHandler(res, req, e)
return
}
}
@@ -125,14 +126,14 @@ func (ep *Endpoint) call(
if ep.Eval != nil {
e = ep.Eval(req, reqBody)
if e != nil {
- ep.error(res, req, e)
+ ep.ErrorHandler(res, req, e)
return
}
}
rspb, e := ep.Call(res, req, reqBody)
if e != nil {
- ep.error(res, req, e)
+ ep.ErrorHandler(res, req, e)
return
}
@@ -159,32 +160,3 @@ func (ep *Endpoint) call(
nwrite += n
}
}
-
-func (ep *Endpoint) error(res http.ResponseWriter, req *http.Request, err error) {
- errInternal := &errors.E{}
- if stderrors.As(err, &errInternal) {
- if errInternal.Code <= 0 || errInternal.Code >= 512 {
- errInternal.Code = http.StatusInternalServerError
- }
- } else {
- log.Printf("endpoint.call: %d %s %s %s\n",
- http.StatusInternalServerError,
- req.Method, req.URL.Path, err)
-
- errInternal = errors.Internal(err)
- }
-
- res.Header().Set(HeaderContentType, ContentTypeJSON)
- res.WriteHeader(errInternal.Code)
-
- rsp, err := json.Marshal(errInternal)
- if err != nil {
- log.Println("endpoint.error: ", err)
- return
- }
-
- _, err = res.Write(rsp)
- if err != nil {
- log.Println("endpoint.error: ", err)
- }
-}
diff --git a/lib/http/endpoint_example_test.go b/lib/http/endpoint_example_test.go
new file mode 100644
index 00000000..b605a931
--- /dev/null
+++ b/lib/http/endpoint_example_test.go
@@ -0,0 +1,62 @@
+package http
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+)
+
+func ExampleEndpoint_errorHandler() {
+ serverOpts := &ServerOptions{
+ Address: "127.0.0.1:8123",
+ }
+ server, _ := NewServer(serverOpts)
+
+ endpointError := &Endpoint{
+ Method: RequestMethodGet,
+ Path: "/",
+ RequestType: RequestTypeQuery,
+ ResponseType: ResponseTypePlain,
+ Call: func(res http.ResponseWriter, req *http.Request, reqBody []byte) ([]byte, error) {
+ return nil, fmt.Errorf(req.Form.Get("error"))
+ },
+ ErrorHandler: func(res http.ResponseWriter, req *http.Request, err error) {
+ res.Header().Set(HeaderContentType, ContentTypePlain)
+
+ codeMsg := strings.Split(err.Error(), ":")
+ if len(codeMsg) != 2 {
+ res.WriteHeader(http.StatusInternalServerError)
+ res.Write([]byte(err.Error()))
+ } else {
+ code, _ := strconv.Atoi(codeMsg[0])
+ res.WriteHeader(code)
+ res.Write([]byte(codeMsg[1]))
+ }
+ },
+ }
+ _ = server.RegisterEndpoint(endpointError)
+
+ go func() {
+ _ = server.Start()
+ }()
+ defer server.Stop(1 * time.Second)
+ time.Sleep(1 * time.Second)
+
+ client := NewClient("http://"+serverOpts.Address, nil, false)
+
+ params := url.Values{}
+ params.Set("error", "400:error with status code")
+ httpres, resbody, _ := client.Get(nil, "/", params)
+ fmt.Printf("%d: %s\n", httpres.StatusCode, resbody)
+
+ params.Set("error", "error without status code")
+ httpres, resbody, _ = client.Get(nil, "/", params)
+ fmt.Printf("%d: %s\n", httpres.StatusCode, resbody)
+
+ // Output:
+ // 400: error with status code
+ // 500: error without status code
+}
diff --git a/lib/http/http.go b/lib/http/http.go
index 51b6f3ac..30eca705 100644
--- a/lib/http/http.go
+++ b/lib/http/http.go
@@ -124,6 +124,14 @@
//
// map[name:[book Hitchiker]]
//
+// Callback error handling
+//
+// Each Endpoint can have their own error handler. If its nil, it will default
+// to DefaultErrorHandler, which return the error as JSON with the following
+// format,
+//
+// {"code":<HTTP_STATUS_CODE>,"message":<err.Error()>}
+//
// Known Bugs and Limitations
//
// * The server does not handle CONNECT method
diff --git a/lib/http/route.go b/lib/http/route.go
index 0946830e..3100e6a0 100644
--- a/lib/http/route.go
+++ b/lib/http/route.go
@@ -28,6 +28,9 @@ func newRoute(ep *Endpoint) (rute *route, err error) {
rute = &route{
endpoint: ep,
}
+ if ep.ErrorHandler == nil {
+ ep.ErrorHandler = DefaultErrorHandler
+ }
paths := strings.Split(strings.ToLower(strings.Trim(ep.Path, "/")), "/")