diff options
| author | Shulhan <m.shulhan@gmail.com> | 2020-11-27 19:49:50 +0700 |
|---|---|---|
| committer | Shulhan <m.shulhan@gmail.com> | 2020-11-27 19:49:50 +0700 |
| commit | caa050939faabc96a7c5a7ca53d1f844d64d010d (patch) | |
| tree | 22773a02d5f6c33cd107b6bd455173946d235b62 /lib/http | |
| parent | e46ab6bc3b6aed18fd11269a578c7b9614fc2e4b (diff) | |
| download | pakakeh.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.go | 64 | ||||
| -rw-r--r-- | lib/http/endpoint.go | 42 | ||||
| -rw-r--r-- | lib/http/endpoint_example_test.go | 62 | ||||
| -rw-r--r-- | lib/http/http.go | 8 | ||||
| -rw-r--r-- | lib/http/route.go | 3 |
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, "/")), "/") |
