Home > Enterprise >  Why is this function's argument not being invoked as a function?
Why is this function's argument not being invoked as a function?

Time:08-12

Here is the complete example from my current reading material "Hands-On Restful Web Services With Go" from Packt.

func filterContentType(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("Currently in the check content type middleware")
        // Filtering requests by MIME type
        if r.Header.Get("Content-type") != "application/json" {
            w.WriteHeader(http.StatusUnsupportedMediaType)
            w.Write([]byte("415 - Unsupported Media Type. Please send JSON"))
            return
        }
        handler.ServeHTTP(w, r)
    })
}

func setServerTimeCookie(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Setting cookie to every API response
        cookie := http.Cookie{Name: "ServerTimeUTC", Value: strconv.FormatInt(time.Now().Unix(), 10)}
        http.SetCookie(w, &cookie)
        log.Println("Currently in the set server time middleware")
        handler.ServeHTTP(w, r)
    })
}

func handle(w http.ResponseWriter, r *http.Request) {
    // Check if method is POST
    if r.Method == "POST" {
        var tempCity city
        decoder := json.NewDecoder(r.Body)
        err := decoder.Decode(&tempCity)
        if err != nil {
            panic(err)
        }
        defer r.Body.Close()
        // Your resource creation logic goes here. For now it is plain print to console
        log.Printf("Got %s city with area of %d sq miles!\n", tempCity.Name, tempCity.Area)
        // Tell everything is fine
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("201 - Created"))
    } else {
        // Say method not allowed
        w.WriteHeader(http.StatusMethodNotAllowed)
        w.Write([]byte("405 - Method Not Allowed"))
    }
}

func main() {
    originalHandler := http.HandlerFunc(handle)
    http.Handle("/city", filterContentType(setServerTimeCookie(originalHandler)))  // !
    http.ListenAndServe(":8000", nil)
}

This program simply consists of the main function and 3 other functions, their logic is arbitrary and just copied from my book's example.

At bottom, where I've commented with "!", filterContentType is using an argument that itself is a function (setServerTimeCookie), and it looks like it's being invoked with originalHandler as its argument.

However when this code is run, the order of execution is:

  1. filterContentType 2. setServerTimeCookie 3. originalHandler

This is counterintuitive to what I understand about using functions as arguments. I assumed that setServerTimeCookie would be the first to execute but that's not the case; it's behaving like an uninvoked function.

This leads to my question, what is causing setServerTimeCookie to defer its execution despite the syntax suggesting it's being invoked as filterContentType's argument?

I attempted to simplify things for my own understanding:

func main() {

    one(two(three))
}

func one(f func()) {
    fmt.Println("ONE\n")
    f()
}

func two(f func()) {
    fmt.Println("TWO\n")
    f()
}

func three(){
    fmt.Println("THREE\n")
}

This code does not build, I'm left with the error: two(three) used as value -which tells me that two is being invoked, unlike the book's example.

What's the difference and again, why doesn't the book's example invoke setServerTimeCookie first? My only assumption is that it has something to do with the implementation of http.HandlerFunc so maybe I should start there.

Any insight to fast-forward my understanding would be greatly appreciated.

CodePudding user response:

This doesn't compile because two(three) does not return a value.

I assume you want to return a function closure in this case, so to fix:

func two(f func()) func() {
    return func() {
        fmt.Println("TWO\n")
        f()
    }
}

https://go.dev/play/p/vBrAO6nwy4X


Circling back to your question about setServerTimeCookie and it's use of return http.HandlerFunc(fn). Looking at the source for http.HandlerFunc reveals it's actually a type definition - and NOT a conventional function call. It's actual IMHO the most powerful and underrated four lines of code in the go standard library:

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

By creating this value of http.HandlerFunc, it's implicitly a http.Handler, since it provides the ServeHTTP method. This therefore allows this method to be called upon request - which is exactly what a webservice is designed to do: the underlying function f will be invoked when the handler is invoked.

CodePudding user response:

Because in the expression one(two(three)) function two is not passed as function reference. Instead function two is called with the argument tree, which not not what function one expects

  •  Tags:  
  • go
  • Related