How to compose http response function in a better way

Hi, I have bunch of handlers who writes json or error based on lot of validations etc.
Because of that, my handlers got lot of returns and function if…else conditions. Currently I have my code composed this way:

func getFrom(cluster Cluster) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		ID, err := GetIDFromString(mux.Vars(r)["kid"])
		if err != nil {
			respondWith(w, http.StatusInternalServerError, err.Error())
			return
		}

		results, err := PollFrom()
		if err != nil {
			respondWith(w, http.StatusInternalServerError, err.Error())
			return
		}

		respondWith(w, http.StatusOK, results)
	}
}

func respondWith(w http.ResponseWriter, status int, data interface{}) {
	js, err := json.Marshal(data)
	if err == nil {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(status)
		w.Write(js)
	} else {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		log.Printf("Error: %v", err.Error())
	}
}

My issue is even in this function I am calling respondWith three times, I want to know is there any way I can change it to make it a bit DRY???

I compose things in similar way, waiting next person to give the better answer. :sweat_smile:

1 Like

There was a similar question on the mailing list recently as well, but your example is a little more complete, so I can be more verbose. No promises that this will compile, but it should be close (not in front of my own machine right now):smile:

type HTTPError struct {
	Status int
	Err    error
}

func (e *HTTPError) Error() string {
	// Simplified
	return fmt.Sprintf("HTTP %d: %s", e.Status, e.Err)
}

type ClusterHandler struct {
	Handler func(Cluster, http.ResponseWriter, *http.Request) error
	C       Cluster
}

func (ch ClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	err := ch.H(ch.C, w, r)
	if err != nil {
		switch e := err.(type) {
		case *HTTPError:
			log.Println(e)
			respondWith(w, e.Status, e.Err)
			return
		default:
			// fall back to a HTTP 500 for unspecified errors
			log.Println(err)
			respondWith(w, 500, nil)
			return
		}
	}
}

// Call it with http.Handle("/cluster", ClusterHandler{getFrom, Cluster})
func getFrom(cluster Cluster, w http.ResponseWriter, r *http.Request) error {
	ID, err := GetIDFromString(mux.Vars(r)["kid"])
	if err != nil {
		return &HTTPError(400, err)
	}

	results, err := PollFrom()
	if err != nil {
		return &HTTPError(500, err)
	}

	// You could also modify respondWith to return an error and then just
	// return respondWith(w, http.StatusOK, results)
	// respondWith could then write to a temporary buffer (pooled) to catch any JSON
	// marshalling errors before writing to the response.
	respondWith(w, http.StatusOK, results)
	return nil
}

Also on the Playground: http://play.golang.org/p/7BKDioSWiQ

Refer to http://blog.golang.org/error-handling-and-go and http://elithrar.github.io/article/http-handler-error-handling-revisited/#full-example (shameless plug) for some additional context.

3 Likes

try defining your own handler type. There’s a write up about this approach on http://blog.golang.org/error-handling-and-go

3 Likes

You are genius @elithrar

This worked flawlessly with very small changes. I made some changes and will be making some changes to respond with, but this is what I was looking for.

Thanks a lot.

Just one small addition (You’ve actually mentioned it in the comment already) - you could replace the raw integers with http.StatusBadRequest/InternalServerError :smiley:

Appart from that - thanks, greate explanation

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.