aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-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, "/")), "/")