Adding middleware and generic error handling

main
Drew Bednar 7 hours ago
parent ff2749ed2c
commit c726f59b6e

@ -0,0 +1,56 @@
package main
import (
"fmt"
"net/http"
)
// The logError() method is a generic helper for logging an error message along
// with the current request method and URL as attributes in the log entry.
func (app *application) logError(r *http.Request, err error) {
var (
method = r.Method
uri = r.URL.RequestURI()
)
app.logger.Error(err.Error(), "method", method, "uri", uri)
}
// The errorResponse() method is a generic helper for sending JSON-formatted error
// messages to the client with a given status code. Note that we're using the any
// type for the message parameter, rather than just a string type, as this gives us
// more flexibility over the values that we can include in the response.
func (app *application) errorResponse(w http.ResponseWriter, r *http.Request, status int, message any) {
env := envelope{"error": message}
err := app.writeJSON(w, status, env, nil)
if err != nil {
app.logError(r, err)
w.WriteHeader(500)
}
}
// The serverErrorResponse() method will be used when our application encounters an
// unexpected problem at runtime. It logs the detailed error message, then uses the
// errorResponse() helper to send a 500 Internal Server Error status code and JSON
// response (containing a generic error message) to the client.
func (app *application) serverErrorResponse(w http.ResponseWriter, r *http.Request, err error) {
app.logError(r, err)
message := "the server encountered a problem and could not process the request"
app.errorResponse(w, r, http.StatusInternalServerError, message)
}
// The notFoundResponse() method will be used to send a 404 Not Found status code and
// JSON response to the client.
func (app *application) notFoundResponse(w http.ResponseWriter, r *http.Request) {
message := "the requested resource could not be found"
app.errorResponse(w, r, http.StatusNotFound, message)
}
// The methodNotAllowedResponse() method will be used to send a 405 Method Not Allowed
// status code and JSON response to the client.
func (app *application) methodNotAllowedResponse(w http.ResponseWriter, r *http.Request) {
message := fmt.Sprintf("the %s mehtod is not supported for this resource", r.Method)
app.errorResponse(w, r, http.StatusMethodNotAllowed, message)
}

@ -26,7 +26,8 @@ func (app *application) getMovieHandler(w http.ResponseWriter, r *http.Request)
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
if err != nil || id < 1 {
http.NotFound(w, r)
// http.NotFound(w, r)
app.notFoundResponse(w, r)
return
}
@ -38,11 +39,13 @@ func (app *application) getMovieHandler(w http.ResponseWriter, r *http.Request)
Genres: []string{"sci-fi", "action"},
Version: 1,
}
app.logger.Info("Hit the get movies and found", "movie", movie.Title)
err = app.writeJSON(w, http.StatusOK, envelope{"movie": movie}, nil)
if err != nil {
app.logger.Error(err.Error())
http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError)
//app.logger.Error(err.Error())
//http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError)
app.serverErrorResponse(w, r, err)
}
}
@ -72,8 +75,9 @@ func (app *application) healthCheckHandler(w http.ResponseWriter, r *http.Reques
// w.Write(js)
err := app.writeJSON(w, 200, env, nil)
if err != nil {
app.logger.Error(err.Error())
http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError)
//app.logger.Error(err.Error())
//http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError)
app.serverErrorResponse(w, r, err)
return
}
}

@ -0,0 +1,30 @@
package main
import (
"fmt"
"net/http"
)
// recoverPanic provides middleware for recovering from panics in the same goroutine that
// executred the recoverPanic middleware.
//
// This means that panics from goroutines created in handler operations will still need
// to be handled separately.Failure to do so will cause the application to exit and bring
// down the server.
func (app *application) recoverPanic(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Create a deferred function (which will always be run in the event of a panic
// as Go unwinds the stack).
defer func() {
if err := recover(); err != nil {
// If there was a panic, set a "Connection: close" header on the
// response. This acts as a trigger to make Go's HTTP server
// automatically close the current connection after a response has been
// sent.
w.Header().Set("Connection", "close")
app.serverErrorResponse(w, r, fmt.Errorf("%s", http.ErrAbortHandler))
}
}()
next.ServeHTTP(w, r)
})
}

@ -13,9 +13,19 @@ func (app *application) routes() http.Handler {
// http.Handler rather than handler functions as seen below
router := httprouter.New()
// Convert the notFoundResponse() helper to a http.Handler using the
// http.HandlerFunc() adapter, and then set it as the custom error handler for 404
// Not Found responses.
router.NotFound = http.HandlerFunc(app.notFoundResponse)
// Likewise, convert the methodNotAllowedResponse() helper to a http.Handler and set
// it as the custom error handler for 405 Method Not Allowed responses.
router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowedResponse)
router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthCheckHandler)
router.HandlerFunc(http.MethodPost, "/v1/movies", app.createMovieHandler)
router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.getMovieHandler)
return router
// middleware
return app.recoverPanic(router)
}

Loading…
Cancel
Save