Skip to content

Instantly share code, notes, and snippets.

@lunemec
Last active January 26, 2019 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lunemec/772e8191335e33745ce76d1f1048e476 to your computer and use it in GitHub Desktop.
Save lunemec/772e8191335e33745ce76d1f1048e476 to your computer and use it in GitHub Desktop.
upgraded handler
// Handler is http handler func that returns error which will be written
// to ResponseWriter automatically.
type Handler func(http.ResponseWriter, *http.Request) error
// Response represents an error with an associated HTTP status code.
type Response struct {
Code int
RequestID RequestID
Err error
}
// WithUser is any handler that can accept user information.
type WithUser func(*auth.User, http.ResponseWriter, *http.Request) error
// Allows Response to satisfy the error interface.
func (r Response) Error() string {
// To avoid panic when printing Response without error.
if r.Err == nil {
return ""
}
return r.Err.Error()
}
// Status returns our HTTP status code.
func (r Response) Status() int {
if r.Code == 0 {
r.Code = 500
}
return r.Code
}
// MarshalJSON to correctly marshal error message.
func (r Response) MarshalJSON() ([]byte, error) {
type jsonError struct {
StatusCode int `json:"status_code,omitempty"`
Error string `json:"error,omitempty"`
RequestID string `json:"request_id,omitempty"`
}
return json.Marshal(&jsonError{
StatusCode: r.Status(),
Error: r.Error(),
RequestID: string(r.RequestID),
})
}
// Wrap allows us to have http.Handler that can return error which is handled
// here and encoded as JSON error with correct http status code.
func Wrap(handler Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := handler(w, r)
if err != nil {
switch e := errors.Cause(err).(type) {
case Response:
w.WriteHeader(e.Status())
// This is not error, but just returning status code.
if e.Err == nil {
return
}
default:
// Any error types we don't specifically look out for default
// to serving a HTTP 500
w.WriteHeader(500)
// Wrap normal error to Response struct so that we may JSON encode it.
err = Response{Err: err}
}
encErr := json.NewEncoder(w).Encode(err)
if encErr != nil {
// If this error happens, it means we are trying to encode something incorrect
// and is programming error.
panic(fmt.Sprintf("unable to encode error JSON: %s from %s", encErr, err))
}
w.Header().Add("Content-Type", "application/json")
return
}
})
}
// LoginRequiredHandler is a middleware that enforces users to have correct JWT Authorization token
// set and returns HTTP 401 if not.
func LoginRequiredHandler(next WithUser) Handler {
return func(w http.ResponseWriter, r *http.Request) error {
user := auth.GetUser(r)
if user == nil {
return Response{
Code: http.StatusUnauthorized,
Err: errors.New("must have correct Authorization header to access this resource"),
}
}
return next(user, w, r)
}
}
// router is incomplete, just a example
func router() {
middleware := func(h WithUser) http.Handler {
return Wrap(
LoginRequiredHandler(h))
}
router.Methods(http.MethodPost).Path("/URL/").Handler(middleware(myhandler))
}
// I think this is much better as you can return error which is always returned as JSON (via Wrap middleware)
// and this handler will ALWAYS be called with the user pre-set.
func myhandler(user *auth.User, w http.ResponseWriter, r *http.Request) error {
return nil
}
// Here we have middleware that generates uuid and passes it as requestID, and we parse ?limit=xxx&offset=xxx from url params.
func myhandler2(requestID handler.RequestID, limit handler.Limit, offset handler.Offset, user *auth.User, w http.ResponseWriter, r *http.Request) error {
return nil
}
@lunemec
Copy link
Author

lunemec commented Jan 26, 2019

/cc @Jonnybcz

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