Created
September 29, 2020 16:20
-
-
Save rikonor/82a0890c7c2a14b6f22e7286a7d3d712 to your computer and use it in GitHub Desktop.
HTTP Middleware Helpers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type wrapHandlerFunc func(hn http.Handler, pattern string) http.HandlerFunc | |
// mwLogs is used to wrap a handler so that it will emit HTTP logs for every request | |
func mwLogs(logger *zap.Logger, hn handlerWithError) handlerWithError { | |
return func(w http.ResponseWriter, r *http.Request) *apiError { | |
// Track time | |
startTime := time.Now() | |
// Track response status code | |
t := status.Track(w) | |
// Handle the request | |
err := hn(t, r) | |
d := time.Since(startTime) | |
// Log | |
logFields := []zapcore.Field{ | |
zap.String("method", r.Method), | |
zap.String("path", r.URL.Path), | |
zap.Duration("duration", d), | |
zap.Int("status", t.Status()), | |
} | |
if err != nil { | |
logFields = append(logFields, zap.Error(err.Error)) | |
} | |
logger.Info("", logFields...) | |
return err | |
} | |
} | |
// apiError is used to simplify api error handling | |
// it allows a handler to return an error and further instructions | |
// in the form of a desired message and status code | |
type apiError struct { | |
Error error | |
Message string | |
StatusCode int | |
} | |
func (e apiError) String() string { | |
return fmt.Sprintf("[%d] %s: %s", e.StatusCode, e.Message, e.Error) | |
} | |
// handlerWithError is an extension of http.HandlerFunc | |
// which expects the handler to potentially return an apiError | |
type handlerWithError func(http.ResponseWriter, *http.Request) *apiError | |
// Define a base json error response so we can return errors to clients | |
type errResponse struct { | |
Error string `json:"error"` | |
} | |
// newErrorResponse creates a serialized errResponse from a given error string | |
func newErrorResponse(err string) string { | |
// This shouldn't ever fail | |
bs, _ := json.Marshal(&errResponse{Error: err}) | |
return string(bs) | |
} | |
// mwJSONError wraps a handlerWithError so that if it returned an apiError | |
// it will be returned to the client in JSON format | |
func mwJSONError(hn handlerWithError) handlerWithError { | |
return func(w http.ResponseWriter, r *http.Request) *apiError { | |
// Call the handler | |
err := hn(w, r) | |
// Check if there was an error | |
if err != nil { | |
// Respond to client with json wrapped error | |
http.Error(w, newErrorResponse(err.Message), err.StatusCode) | |
} | |
return err | |
} | |
} | |
func extractErrorFromResponse(statusCode int, body io.Reader) error { | |
var errRes errResponse | |
if err := json.NewDecoder(body).Decode(&errRes); err != nil { | |
return fmt.Errorf("request failed with status %d: %s", statusCode, err) | |
} | |
return fmt.Errorf("request failed with status %d: %s", statusCode, errRes.Error) | |
} | |
func mwDiscardError(hn handlerWithError) http.HandlerFunc { | |
return func(w http.ResponseWriter, r *http.Request) { | |
// Call handler, but discard error | |
_ = hn(w, r) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment