Handling HTTP Error in golang
Generally we need naked return to handle some kind of error in default http handler in golang. This is also true for framework like Gin and router like Chi too. So in order to handle errors in default http handler you will do as in following snippet.
func GetHandler(w http.ResponseWriter, r *http.Request) {
err := errors.New("some error")
if err != nil {
http.Error(err)
return
}
w.Write([]byte("successful response"))
}
Error handling in standard http handler by creating custom handler method🔗
Golang have excellent http library built in but it does not offer returning error for convient use case. where other router like echo does offer error returing handler. now in this post we are going to look at creating http error returning handler. how create how to execute it.
Creating custom http handler🔗
What is http handler in go or rather what is http.HandlerFunc
is?
In simple term http.HandlerFunc is whatever that implements ServeHTTP(w http.ResponseWriter, r *http.Request)
.
See subsequent snippet to follow how we are creating custom handler which returns error.
package main
import (
"http"
"errors"
)
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
mux := http.NewServeMux()
mux.Handle("/", HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
return errors.New("this is error")
}))
http.ListenAndServe(":3000", mux)
}
- On line number 10 we implmented ServeHTTP method on our handler function
- Line number 11 executes our incoming handler and reurns any error from it
- On line 19 we converted
http.HandlerFunc
to our customHandlerFunc
notice that this type of error handling now we can't specifiy status code and serve http will always return 500 as status. we will look at how to tackle it in next section.
Creating custom error for better response🔗
- Basic idea here is we will create struct that implments both error and Marshler interfaces.
- We need to implement Marsheler as golang doesn't know how to json encode errors
- Notice in MarshalJSON method we are returning custom struct with all field exported to encode json.
type AppErr struct {
err error
msg string
status int
}
func HTTPError(e error, m string, code int) AppErr {
return AppErr{e, m, code}
}
func (e AppErr) Unwrap() error {
return e.err
}
func (e AppErr) Error() string {
return e.err.Error()
}
func (e AppErr) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Err string `json:"err,omitempty"`
Msg string `json:"msg,omitempty"`
StatusCode int `json:"status_code,omitempty"`
}{
e.Error(), e.msg, e.status,
})
}
Changing our ServeHTTP to handle custom error🔗
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil {
if errors.As(err, &AppErr{}) {
e := err.(AppErr)
fmt.Println(err)
JSON(w, e.status, e, map[string]string{"Content-Type": "application/json"})
}
return
}
}
Complete Code🔗
package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
// JSON is repsonse helper functions
func JSON(w http.ResponseWriter, code int, payload any, headers map[string]string) error {
for k, v := range headers {
w.Header().Set(k, v)
}
w.WriteHeader(code)
return json.NewEncoder(w).Encode(payload)
}
// Bind binds the request body to given model
func Bind[M any](r *http.Request) (M, error) {
var m M
return m, json.NewDecoder(r.Body).Decode(&m)
}
// Custom Handler func that Defines error on top of http.HandlerFunc
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil {
if errors.As(err, &AppErr{}) {
e := err.(AppErr)
fmt.Println(err)
JSON(w, e.status, e, map[string]string{"Content-Type": "application/json"})
}
return
}
}
// app err which implments custom
// JsonMarshaler and Error interface
type AppErr struct {
err error
msg string
status int
}
func HTTPError(e error, m string, code int) AppErr {
return AppErr{e, m, code}
}
func (e AppErr) Unwrap() error {
return e.err
}
func (e AppErr) Error() string {
return e.err.Error()
}
func (e AppErr) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Err string `json:"err,omitempty"`
Msg string `json:"msg,omitempty"`
StatusCode int `json:"status_code,omitempty"`
}{
e.Error(), e.msg, e.status,
})
}
// api struct
type API struct {
router *http.ServeMux
}
func (api *API) GetE(w http.ResponseWriter, r *http.Request) error {
return HTTPError(errors.New("this is error"), "just error", 400)
}
func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
api.router.ServeHTTP(w, r)
}
func (api *API) Routes() {
api.router.Handle("/", HandlerFunc(api.GetE))
}
func main() {
mux := http.NewServeMux()
api := &API{mux}
api.Routes()
srv := &http.Server{
Addr: ":8080",
Handler: api,
}
srv.ListenAndServe()
}