为什么这个函数的参数没有作为函数被调用?

huangapple go评论81阅读模式
英文:

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

问题

以下是我翻译好的内容:

这是我当前阅读的书籍《Hands-On Restful Web Services With Go》中的完整示例。

func filterContentType(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("当前在检查内容类型的中间件中")
		// 通过 MIME 类型过滤请求
		if r.Header.Get("Content-type") != "application/json" {
			w.WriteHeader(http.StatusUnsupportedMediaType)
			w.Write([]byte("415 - 不支持的媒体类型,请发送 JSON"))
			return
		}
		handler.ServeHTTP(w, r)
	})
}

func setServerTimeCookie(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 为每个 API 响应设置 cookie
		cookie := http.Cookie{Name: "ServerTimeUTC", Value: strconv.FormatInt(time.Now().Unix(), 10)}
		http.SetCookie(w, &cookie)
		log.Println("当前在设置服务器时间的中间件中")
		handler.ServeHTTP(w, r)
	})
}

func handle(w http.ResponseWriter, r *http.Request) {
	// 检查方法是否为 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()
		// 在这里编写你的资源创建逻辑。现在只是简单地打印到控制台
		log.Printf("得到了一个面积为 %d 平方英里的 %s 城市!\n", tempCity.Area, tempCity.Name)
		// 告诉客户端一切正常
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("201 - 已创建"))
	} else {
		// 返回方法不允许
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("405 - 方法不允许"))
	}
}

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

这个程序简单地包含了一个 main 函数和其他三个函数,它们的逻辑是任意的,只是从我的书籍示例中复制过来的。

在代码底部,我用 ! 注释的地方,filterContentType 使用了一个函数作为参数(setServerTimeCookie),看起来它被调用时使用了 originalHandler 作为参数。

然而,当运行这段代码时,执行的顺序是:

  1. filterContentType 2. setServerTimeCookie 3. originalHandler

这与我对使用函数作为参数的理解相反。我原以为 setServerTimeCookie 会首先执行,但事实并非如此;它的行为就像一个未被调用的函数。

这引出了我的问题,是什么导致 setServerTimeCookie 推迟执行,尽管语法表明它是作为 filterContentType 的参数被调用的?

为了更好地理解,我尝试简化代码:

func main() {
	one(two(three))
}

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

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

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

这段代码无法编译,我得到了错误信息:
two(three) used as value - 这告诉我 two 被调用了,与书中的示例不同。

这两者之间有什么区别,为什么书中的示例没有先调用 setServerTimeCookie?我唯一的猜测是它与 http.HandlerFunc 的实现有关,所以也许我应该从那里开始。

如果你能帮助我更快地理解这个问题,我将非常感激。

英文:

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.

答案1

得分: 1

因为在表达式one(two(three))中,函数two并没有作为函数引用传递。相反,函数two被调用时传入了参数tree,这不是函数one所期望的。

英文:

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 is not what function one expects

答案2

得分: 1

这段代码无法编译,因为two(three)没有返回值。

我猜你想在这种情况下返回一个函数闭包,所以修复如下:

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

修复后的代码在这里


回到你关于setServerTimeCookie和它使用return http.HandlerFunc(fn)的问题。查看http.HandlerFunc的源代码,可以发现它实际上是一个类型定义,而不是常规的函数调用。在go标准库中,这四行代码实际上是最强大且被低估的:

type HandlerFunc func(ResponseWriter, *Request)

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

通过创建http.HandlerFunc的值,它隐式地成为了一个http.Handler,因为它提供了ServeHTTP方法。这样一来,该方法可以在请求时被调用,这正是一个 Web 服务的设计目的:当处理程序被调用时,底层函数f将被调用。

英文:

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.

huangapple
  • 本文由 发表于 2022年8月12日 03:58:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/73326295.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定