Skip to content

Instantly share code, notes, and snippets.

@fwojciec
Last active July 23, 2018 17:44
Show Gist options
  • Save fwojciec/8eda814d438710c2807abcf6adaa0125 to your computer and use it in GitHub Desktop.
Save fwojciec/8eda814d438710c2807abcf6adaa0125 to your computer and use it in GitHub Desktop.
My solution to "Exercise Setting headers only once + optional + super optional" from Section 03 of Francesc Campoy's Go Web Workshop
// https://github.com/campoy/go-web-workshop/blob/master/section03/README.md
// Found these very helpful in the context of this exercise (optional tasks):
// 1. https://blog.questionable.services/article/custom-handlers-avoiding-globals/
// 2. https://blog.questionable.services/article/http-handler-error-handling-revisited/
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
type statusError struct {
Code int
Err error
}
// allows statusError to satisfy the error interface.
func (se statusError) Error() string {
return se.Err.Error()
}
// does what a handler does, but also returns an error
type customHandler func(http.ResponseWriter, *http.Request) error
// allows customHandler to satisfy http.Handler interface
func (f customHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
if err := f(w, r); err != nil {
switch e := err.(type) {
case statusError:
http.Error(w, e.Error(), e.Code)
default:
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
}
func bodyHandler(w http.ResponseWriter, r *http.Request) error {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return statusError{500, errors.New("oops, couldn't read the body")}
}
name := strings.TrimSpace(string(b))
if name == "" {
return statusError{400, errors.New("hey, the body can't be empty")}
}
fmt.Fprintf(w, "Hello, %s!", name)
return nil
}
func main() {
http.Handle("/hello", customHandler(bodyHandler))
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
@mark-kubacki
Copy link

You can treat errors in a similar way:

const (
  ErrCannotReadBody itsProbablyUsError = "oops, couldn't read the body"
  ErrBodyIsEmpty itsDefinitelyYouError = "hey, no empty nonsense please"
)

type httpError interface {
  error
  SuggestedResponseCode() int
}

type itsProbablyUsError string
func (e itsProbablyUsError) Error() string { return string(e) }
func (e itsProbablyUsError) SuggestedResponseCode() int { return http.StatusInternalServerError }

@fwojciec
Copy link
Author

@wmark Thanks Mark, great suggestion! Arguably an overkill in this particular case, but will definitely come back to your comment when I'm starting to work on something more serious as this approach makes makes reusing errors simple and elegant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment