Skip to content

Instantly share code, notes, and snippets.

@rikonor
Created September 29, 2020 16:20
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 rikonor/82a0890c7c2a14b6f22e7286a7d3d712 to your computer and use it in GitHub Desktop.
Save rikonor/82a0890c7c2a14b6f22e7286a7d3d712 to your computer and use it in GitHub Desktop.
HTTP Middleware Helpers
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