diff options
Diffstat (limited to 'lib')
| -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, "/")), "/") |
