What happens if I defer a function inside a function that returns a function? How is the ordering?

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

What happens if I defer a function inside a function that returns a function? How is the ordering?

问题

我正在尝试创建一个非常简单的用于静态文件的gzip中间件。但是在代码中有5个不同的地方调用了next.ServeHTTP(w, r),如果我使用defer关键字,会发生什么?这个语句会在返回的函数运行之前被调用吗?

这是我的代码:

func gzipHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			// 如果客户端不理解gzip,则继续执行。
			next.ServeHTTP(w, r)
			return
		}
		path := filepath.FromSlash(filepath.Join(cfg.PublicHTML, r.URL.Path))
		if _, err := os.Stat(path); os.IsNotExist(err) {
			// 如果文件或文件夹不存在,则继续执行。
			next.ServeHTTP(w, r)
			return
		}
		var ext string
		for _, v := range cfg.GzipExt {
			if strings.HasSuffix(r.URL.Path, v) {
				ext = v
			}
		}
		if ext == "" {
			// 该文件不应作为gzip内容提供。
			next.ServeHTTP(w, r)
			return
		}
		// 仅在存在时提供gzip文件。
		if _, err := os.Stat(path + ".gz"); os.IsNotExist(err) {
			// TODO: 创建gzip文件。
			// http://stackoverflow.com/questions/16890648/how-can-i-use-golangs-compress-gzip-package-to-gzip-a-file
			next.ServeHTTP(w, r)
			return
		}
		w.Header().Add("Content-Encoding", "gzip")
		r.URL.Path = r.URL.Path + ".gz"
		next.ServeHTTP(w, r)
	})
}

在这里使用defer next.ServeHTTP(w, r)是否可行?像这样:

func gzipHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer next.ServeHTTP(w, r)
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			// 如果客户端不理解gzip,则继续执行。
			return
		}
		path := filepath.FromSlash(filepath.Join(cfg.PublicHTML, r.URL.Path))
		if _, err := os.Stat(path); os.IsNotExist(err) {
			// 如果文件或文件夹不存在,则继续执行。
			return
		}
		var ext string
		for _, v := range cfg.GzipExt {
			if strings.HasSuffix(r.URL.Path, v) {
				ext = v
			}
		}
		if ext == "" {
			// 该文件不应作为gzip内容提供。
			return
		}
		// 仅在存在时提供gzip文件。
		if _, err := os.Stat(path + ".gz"); os.IsNotExist(err) {
			// TODO: 创建gzip文件。
			// http://stackoverflow.com/questions/16890648/how-can-i-use-golangs-compress-gzip-package-to-gzip-a-file
			return
		}
		w.Header().Add("Content-Encoding", "gzip")
		r.URL.Path = r.URL.Path + ".gz"
	})
}

我在main()函数中这样使用它来提供静态文件:

router.NotFound = gzipHandler(fileServer())

如果我像这样使用defer next.ServeHTTP(w, r),它会在fileServer()执行之前被执行吗?

英文:

I am trying to create a very simple gzip middleware for static files. But I am calling next.ServeHTTP(w, r) 5 different places in the code, what happens if I defer this? Will this be called before the function that is returned is run?

This is what I have:

func gzipHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			// If for some weird reason client does not understand gzip, then continue.
			next.ServeHTTP(w, r)
			return
		}
		path := filepath.FromSlash(filepath.Join(cfg.PublicHTML, r.URL.Path))
		if _, err := os.Stat(path); os.IsNotExist(err) {
			// If file or folder does not exists, then continue.
			next.ServeHTTP(w, r)
			return
		}
		var ext string
		for _, v := range cfg.GzipExt {
			if strings.HasSuffix(r.URL.Path, v) {
				ext = v
			}
		}
		if ext == "" {
			// This file should not be served as gzipped content.
			next.ServeHTTP(w, r)
			return
		}
		// Only serve gzipped file if it exists.
		if _, err := os.Stat(path + ".gz"); os.IsNotExist(err) {
			// TODO: Create the gzipped file.
			// http://stackoverflow.com/questions/16890648/how-can-i-use-golangs-compress-gzip-package-to-gzip-a-file
			next.ServeHTTP(w, r)
			return
		}
		w.Header().Add("Content-Encoding", "gzip")
		r.URL.Path = r.URL.Path + ".gz"
		next.ServeHTTP(w, r)
	})
}

Would it be possible here to defer next.ServeHTTP(w, r)? Like this:

func gzipHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer next.ServeHTTP(w, r)
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			// If for some weird reason client does not understand gzip, then continue.
			return
		}
		path := filepath.FromSlash(filepath.Join(cfg.PublicHTML, r.URL.Path))
		if _, err := os.Stat(path); os.IsNotExist(err) {
			// If file or folder does not exists, then continue.
			return
		}
		var ext string
		for _, v := range cfg.GzipExt {
			if strings.HasSuffix(r.URL.Path, v) {
				ext = v
			}
		}
		if ext == "" {
			// This file should not be served as gzipped content.
			return
		}
		// Only serve gzipped file if it exists.
		if _, err := os.Stat(path + ".gz"); os.IsNotExist(err) {
			// TODO: Create the gzipped file.
			// http://stackoverflow.com/questions/16890648/how-can-i-use-golangs-compress-gzip-package-to-gzip-a-file
			return
		}
		w.Header().Add("Content-Encoding", "gzip")
		r.URL.Path = r.URL.Path + ".gz"
	})
}

I am using this in my main() function like this to serve static files:

router.NotFound = gzipHandler(fileServer())

If I defer next.ServeHTTP(w, r) like this, will it be executed before fileServer() is executed?

答案1

得分: 6

golang 规范

在函数调用中,函数值和参数按照通常的顺序进行求值。在它们求值之后,调用的参数按值传递给函数,并且被调用的函数开始执行。

gzipHandler(fileServer()) 类似于这样:

a := fileServer()
gzipHandler(a)

所以,显然 fileServer() 首先被执行。

但我认为你困惑的是 defer 语句何时执行,对吗?

根据规范

每次执行 "defer" 语句时,函数值和调用的参数按照通常的方式进行求值,并且保存为新值,但实际的函数不会被调用。相反,延迟函数会在包围它的函数返回之前立即被调用,按照它们被延迟的相反顺序。如果延迟函数值求值为 nil,在调用函数时会引发 panic,而不是在执行 "defer" 语句时。

下面是一个例子来解释:

func t() {
        i := 1

        defer fmt.Println("first defer:", i)
        defer func() {
                fmt.Println("second defer:", i)
        }()

        i = 2
        fmt.Println("t return")
}

t() 将打印:

t return
second defer: 2
first defer: 1

在你的代码中,延迟函数 "next.ServeHTTP" 在匿名函数 func(w http.ResponseWriter, r *http.Request) 返回之前被调用。

英文:

golang spec
> In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.

gzipHandler(fileServer()) is somewhat like this:

a:=fileServer()
gzipHandler(a)

So, obviously fileServer() is executed first.

but i think what confuse you is when will defer statement execute, right?

according to the spec
> Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the "defer" statement is executed.

an example to explain:

func t() {
        i := 1

        defer fmt.Println("first defer:", i)
        defer func() {
                fmt.Println("second defer:", i)
        }()

        i = 2
        fmt.Println("t return")
}

t() will print:

t return
second defer: 2
first defer: 1

in your code, the deferred function "next.ServeHTTP" are invoked before anonymous function func(w http.ResponseWriter, r *http.Request) returns.

huangapple
  • 本文由 发表于 2017年3月22日 10:32:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/42941676.html
匿名

发表评论

匿名网友

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

确定