I'm trying to read through this: https://blog.golang.org/error-handling-and-go specifically the section titled Simplifying repetitive error handling
.
They call http.Handle
like this:
func init() {
http.Handle("/view", appHandler(viewRecord))
}
http.Handle
's second argument expects a type Handler
(https://golang.org/pkg/net/http/#Handler) which needs to have a method serveHttp
.
The serveHttp
function here:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
So, their type appHandler
now implements the Handler interface because it implements ServeHTTP
, I got that. So it can be used in the Handle
function whereas viewRecord
cannot.
Where I'm getting confused is between the relationship between viewRecord
which is of type appHandler
and ServeHTTP
. Which calls which? They make a parenthetical comment about how "a function can be a receiver too" and I think that's where I'm getting tripped up.
Here, with fn appHandler
as the receiver, I would expect something like viewRecord.serveHTTP()
, but that doesn't make sense, and viewRecord
is a function. What I think is happening is that the Handle
function calls serveHTTP
, but how does serveHTTP
call viewRecord
?
Also does appHandler(viewRecord)
do a cast?
Basically I'm looking for some clarity around what it means for a function to be a receiver. I'm new-ish to go and I think I accidentally landed on a non-trivial landmine here.
Function Receiver sets a method on variables that we create. This seems weird at first, but when you get the example, it will be clear as crystal. Let’s create a custom type in Golang and then assign that type to a variable. Now, you can set a method on that variable, and that method is Receiver function.
Welcome to tutorial no. 17 in Golang tutorial series. A method is just a function with a special receiver type between the func keyword and the method name. The receiver can either be a struct type or non-struct type. The syntax of a method declaration is provided below.
This is perfectly valid. The reason is that the line p.area (), for convenience will be interpreted by Go as (*p).area () since area has a value receiver. Similar to value arguments, functions with pointer arguments will accept only pointers whereas methods with pointer receivers will accept both pointer and value receiver.
When a method has a value receiver, it will accept both pointer and value receivers. Let's understand this by means of an example. function func area (r rectangle) in line no.12 accepts a value argument and method func (r rectangle) area () in line no. 16 accepts a value receiver.
Any type can be a receiver. For instance:
type X int
Here, X
is a new type, and you can create methods for it:
func (x X) method() {
// Do something with x
}
In Go, functions are like any other type. So if you have a function type:
type F func()
Here, F
is a new type, hence you can define methods for it:
func (x F) method() {
x()
}
With the above declaration, now you can call value.method()
if value
is of type F
.
a:=F(func() {fmt.Println("hey")})
a.method()
Here, a
is a variable of type F
. F
has a method called method
, so you can call a.method
. When you call that, a.method
calls a
, which is a function.
Going back to you example, appHandler
appears to be a function type:
type appHandler func(http.ResponseWriter, *http.Request)
So any function with that signature can be used in place of a appHandler
. Let's say you write such a function:
func myHandler(http.ResponseWriter, *http.Request) {
// Handle request
}
You can pass this function whereever an appHandler
is requested. However, you cannot pass if to where a Handler
is required without writing a struct like this:
type myHandlerStruct struct{}
func (myHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
myHandler(w,r)
}
Instead of definine a new struct, you can define a method for the type appHandler
:
func (a appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a(w,r)
}
Now you can pass appHandler
to where an appHandler
required as well as where a Handler
required. If it is called as a Handler
, the ServeHTTP
method will simply forward the call to the underlying function.
OK lots of questions there but I'll do my best to cover it all.
This defines a method ServeHTTP
on the type appHandler
, which means that appHandler
is now a valid net/http.Handler
. The type appHandler
happens to be a function type, so yes - here you have a function value with methods you can call on it, separately from calling the function itself. This is already how http.HandleFunc
works in the standard library, by the way - checking its source code might also help understand how this works.
When a handler is registered, net/http
calls its ServeHTTP
method to handle incoming requests. That method on our appHandler
type is:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
So, there on the first line of the function, ServeHTTP
calls the appHandler
function - fn appHandler
is our receiver, making fn
a function value, which we can call:
fn(w, r)
This is utilized by converting our handler function to the appHandler
type:
http.Handle("/view", appHandler(viewRecord))
This is no different really than the more common middleware pattern:
func middleware(fn func(w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
}
This does the exact same thing, but using a closure and function parameter instead of a method and function receiver. This is utilized by calling our wrapper function:
http.Handle("/view", http.HandlerFunc(middleware(viewRecord)))
This uses our middleware
function to wrap viewRecord
and converts it to a http.HandlerFunc
(which, as mentioned earlier, will in fact do the same thing appHandler
was doing with the function type method).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With