使用范围循环切片/映射来注册多个路由

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

Register multiple routes using range for loop slices/map

问题

考虑到我是你的中文翻译,我将为你翻译以下内容:

假设我有一个字符串路径的切片:

paths := []string{"/path0", "/path1", "/path2" /*... "/path-n"*/ }
// 这里的 n 是最后一个路径

使用 net/http 包,我想使用 for 循环和 range 子句为这些路径注册处理程序。我是这样做的:

for _, path := range paths {
    http.HandleFunc(path, handler)
}
// 在这种情况下,每个处理程序都会将路径打印到控制台或浏览器上

编辑:基本上,提问者使用了以下代码:

for _, path := range paths {
    http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, path)
    })
}

但是我得到了相同的输出,即切片的最后一个元素。因此,当我访问 /path1 时,输出是 /path-n。其他元素也是相同的行为,总是打印 /path-n

但是,如果我使用以下代码:

http.HandleFunc(paths[0], handler)
http.HandleFunc(paths[1], handler)
http.HandleFunc(paths[2], handler)
// ...
http.HandleFunc(paths[n], handler)

输出是正确的。

发生了什么,我错过了什么?我需要使用切片或映射给出的路径进行注册,所以我不能使用第二段代码。

你能给我提供完成这个任务的替代方法吗?

英文:

Consider I have slice of string paths:

paths := []string{"/path0", "/path1", "/path2" /*... "/path-n"*/ }
// where n is the last path

Using package net/http, I want to register handler for this path using for loop with range clause. This is how I do this:

for _, path := range paths {
    http.HandleFunc(path, handler)
}
// in this case every handler is print the path to the console or to the browser

EDIT: Basically the asker used this code:

for _, path := range paths {
    http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, path)
    })
}

But I ended up with same output which is the last element of slice, so when I go to /path1, the output is /path-n. Same behavior with other element, always print /path-n.

But if I use this:

http.HandleFunc(paths[0], handler)
http.HandleFunc(paths[1], handler)
http.HandleFunc(paths[2], handler)
// ...
http.HandleFunc(paths[n], handler)

The output is correct.

What's going on, did I miss something? I need for loop for registration given by slice of paths or map, so I can't do the second code.

Can you give me the alternative to accomplished this task?

答案1

得分: 11

所以问题是你实际上使用了这段代码:

for _, path := range paths {
    http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, path)
    })
}

你使用了一个函数字面量,一个闭包作为处理程序函数进行注册。闭包会捕获它们所引用的上下文,对于你的情况来说,是path循环变量。

但是只有一个path循环变量,它的值在每次循环迭代中被覆盖,最终的值将是最后一个路径。规范中相关的部分:带有range子句的for语句

> 迭代变量可以通过“range”子句使用短变量声明的形式进行声明(:=)。在这种情况下,它们的类型被设置为相应迭代值的类型,它们的作用域是“for”语句的块;它们在每次迭代中被重用。如果迭代变量在“for”语句之外声明,执行后它们的值将是最后一次迭代的值。

一旦for循环结束,你开始发出请求,每个注册的处理程序函数都会发送回这个单独的path变量的值。这就是为什么你看到最后一个路径对应于所有请求路径的原因。

解决方法很简单:在每次迭代中创建一个新变量,并在处理程序函数中使用它:

for _, path := range paths {
    path2 := path
    http.HandleFunc(path2, func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, path2)
    })
}

这里发生的是,在每次迭代中使用短变量声明创建一个新的变量,其初始值为path循环变量的值。我们注册的处理程序函数将引用这个新变量,它只对应一个注册的路径。

另一个同样好的解决方法是使用带有参数的匿名函数来传递path字符串。不过可能更难理解:

for _, path := range paths {
    func(p string) {
        http.HandleFunc(p, func(w http.ResponseWriter, req *http.Request) {
            fmt.Fprintf(w, p)
        })
    }(path)
}

这里发生的是我们调用一个匿名函数,将当前的path值传递给它,它注册处理程序函数时只使用这个匿名函数的参数(每次调用都会分配一个新的、独立的局部变量)。

英文:

So the problem was that you actually used this code:

for _, path := range paths {
    http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, path)
    })
}

You used a function literal, a closure as the handler function to register. Closures capture the context they refer to, in your case the path loop variable.

But there is only a single path loop variable, its value is overwritten in each iterations of the loop, and its final value will be the last path. Relevant section from the spec: For statements with range clause:

> The iteration variables may be declared by the "range" clause using a form of short variable declaration (:=). In this case their types are set to the types of the respective iteration values and their scope is the block of the "for" statement; they are re-used in each iteration. If the iteration variables are declared outside the "for" statement, after execution their values will be those of the last iteration.

Once the for loop is finished, and you start making requests, each registered handler function will send back the value of this single path variable. That's why you see the last path returned for all requested paths.

Solution is easy: create a new variable in each iteration, and use that in the handler function:

for _, path := range paths {
	path2 := path
	http.HandleFunc(path2, func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, path2)
	})
}

What happens here is that we use a short variable declaration in each iteration to create a new variable, initialized with the value of the path loop variable. And the handler function we register will refer to this new variable, unique only to one registered path.

Another, equally good solution is to use an anonymous function with a parameter to pass the path string. Might be harder to understand though:

for _, path := range paths {
	func(p string) {
		http.HandleFunc(p, func(w http.ResponseWriter, req *http.Request) {
			fmt.Fprintf(w, p)
		})
	}(path)
}

What happens here is that we call an anonymous function, passing the current path value to it, and it registers the handler function, using only the parameter of this anonymous function (and there's a new, distinct local variable allocated for each call).

huangapple
  • 本文由 发表于 2017年5月18日 17:44:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/44044245.html
匿名

发表评论

匿名网友

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

确定