英文:
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
作为参数。
然而,当运行这段代码时,执行的顺序是:
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:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论