Method declaration with function receiver in Golang
Golang has limited OO features, for example method declaration with struct receiver. This article presents a lesser-known method declaration: method declaration with function receiver.
Free link to this artice: https://pgillich.medium.com/method-declaration-with-function-receiver-in-golang-7f5531ded97d?source=friends_link&sk=02f466aac61000e83508e2c34763f76e
Golang has limited OO features, for example method declaration with struct receiver. This article presents a lesser-known method declaration: method declaration with function receiver.
First, let’s see a simple HTTP server, which uses authorization:
package mainimport (
"fmt"
"net/http"
)func main() {
http.HandleFunc("/create", AuthNeeded(handleCreate))
fmt.Println(http.ListenAndServe(":8000", nil))
}func handleCreate(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(201)
}
Where the authorization is handled by a decorator function:
func AuthNeeded(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if !isValidAuthorization(req.Header.Get("Authorization")) {
w.WriteHeader(http.StatusUnauthorized)
} else {
handler(w, req)
}
}
}func isValidAuthorization(token string) bool {
// check token, etc...
return token != ""
}
Nothing special, it’s a typical middleware solution with decorator function in Golang. The AuthNeeded() gets the HTTP header “Authorization” and calls isValidAuthorization(), which can be restructured by method declarations with function receiver:
type AuthHandlerValidator func(http.ResponseWriter, *http.Request)func (AuthHandlerValidator) GetHeaderKey() string {
return "Authorization"
}func (AuthHandlerValidator) IsValid(token string) bool {
// check token, etc...
return token != ""
}
Please notice, the method receiver is a function type, not a struct type. The two methods are declared to a function type, so if we would like to use it, we must use type conversion, for example:
AuthHandlerValidator(handleCreate)
This expression creates a new function from handleCreate() with two methods. The AuthNeeded() must be refactored, like this:
func AuthNeeded(handler AuthHandlerValidator) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if !handler.IsValid(req.Header.Get(handler.GetHeaderKey())) {
w.WriteHeader(http.StatusUnauthorized)
} else {
handler(w, req)
}
}
}
The http.HandlerFunc can be called by handler(w, req) and the two methods can be called by handler.GetHeaderKey() and handler.IsValid(…).
The AuthHandlerValidator function type can implement interfaces, so type assertions can also be used, for example (not used in the whole example code):
type AuthHandler interface {
GetHeaderKey() string
IsValid(token string) bool
}func AuthNeeded(handler interface{}) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if h, ok := handler.(func(http.ResponseWriter, *http.Request)); ok {
if v, ok := handler.(AuthHandler); ok && !v.IsValid(req.Header.Get(v.GetHeaderKey())) {
w.WriteHeader(http.StatusUnauthorized)
} else {
h(w, req)
}
}
}
}
If the interface defines a function, which returns the http.HandlerFunc, type assertion is not needed, for example (not used in the whole example code):
func (handler AuthHandlerValidator) GetHandler() func(http.ResponseWriter, *http.Request) {
return handler
}type AuthHandler interface {
GetHandler() func(http.ResponseWriter, *http.Request)
GetHeaderKey() string
IsValid(token string) bool
}func AuthNeeded(handler AuthHandler) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if !handler.IsValid(req.Header.Get(handler.GetHeaderKey())) {
w.WriteHeader(http.StatusUnauthorized)
} else {
handler.GetHandler()(w, req)
}
}
}
Method declaration with function receiver is possible but it has more limitations comparing to struct receiver, which can have fields and can have stateful behavior.
Finally, let’s see the whole source code: