Reputation: 1513
How can I safely terminate a request outside of a dispatcher (handlefunc) without an explicit return in handlefunc? In the code below, I end up seeing both "Bad request" and "Not found" in my response, but I'd like f() to abort the request -- so I wouldn't have to clutter dispatcher with error checks and returns.
package main
import (
"net/http"
)
func f(w http.ResponseWriter, r *http.Request, k string) string{
if v, ok := r.Form[k]; !ok{
http.Error(w, "Bad request", http.StatusBadRequest)
return ""
}else{
return v[0]
}
}
func main(){
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
foo := f(w, r, "foo") //make sure foo is passed, if not abort with "bad request" without requiring an explicit return here
bar := f(w, r, "bar") //make sure bar is passed, if not abort with "bad request" without requiring an explicit return here
//lookup in db...
//abort with a 404 if not found in db
http.NotFound(w, r)
})
http.ListenAndServe(":6000", nil)
}
The above was an attempt to refactor the following:
func main(){
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
if foo,ok:=r.Form["foo"];!ok{
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
if bar,ok:=r.Form["bar"];!ok{
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
//lookup in db...
//abort with a 404 if not found in db
http.NotFound(w, r)
})
http.ListenAndServe(":6000", nil)
}
Upvotes: 0
Views: 124
Reputation: 30087
I don't think trying to abort from within a function is what you want to do here. Instead, make the validation less repetitive. You could write something like package validate
with func Require(form url.Values, required ...string) error
, and, in your handler, if err := validate.Require(r.Form, "foo", "bar"); err != nil { ... }
.
Or you could do a more general validator: maybe take a map
of field name to validation type (number, string, etc.) and return another map
that's nil
if no errors and {"fieldName": error}
otherwise.
Redditors talked some about this and one wrote a library that uses struct tags to validate. There's another struct-based validation implementation in a new form rendering/validation toolkit. (I've tried neither.) The Redditors raised the tricky question of how much you can abstract validation before getting a "framework" that gets in the way when your app gets more complicated, and I don't have a simple answer.
There are cases where something really unexpected happens and the server can't do anything better than give the user an opaque "500 Internal Server Error" response, but the problem manifests deep below your HandlerFunc
. The canonical example is a programming error like a nil pointer dereference. The advice from the Go team is to use panic
when "the error is unrecoverable." (A tool like negroni.Recovery
can help log panic
s, etc.)
Unnecessary panic
s are lame because:
panic
s make it easier to forget necessary error handling entirely, because it's implied that things can panic
but explicit they can err
panic
/recover
error handling code is ugly and hard to maintainpanic
s introduce inconsistency with the standard err
returnspanic
s use stack unwinding, which is much slower than normal control flowI did a quick grep
and essentially all calls to panic
in the standard library and other official repositories are there to catch programming/internal errors, not crash on surprise external conditions like bad input or I/O errors. Things like file/object not found or invalid user input can usually be handled more gracefully than by crashing the request (it's at least possible to return a specific error), and they aren't really unexpected (it's the Internet; you'll get invalid input and requests for things that don't exist). So panic
is not for normal request aborts--it's for crashing when what "can't happen" has happened and the request can't continue.
Again, here, I'd handle invalid input with a standard if
block in the handler function, but rearrange your validation so that it doesn't require a lot of code per required argument.
Upvotes: 2