英文:
Usage of global variables with Http handlers in Golang
问题
我知道有一些关于这个问题的问题和帖子/文章,但从我这个新手的角度来看,不完全正确。
问题是,我有一个主程序监听一个端口并将调用重定向到特定的处理程序。典型的结构如下:
func main() {
http.HandleFunc("/something", specificHandler)
http.ListenAndServe(":8080", nil)
}
func specificHandler(w http.ResponseWriter, r *http.Request) {
somepackage.foo()
}
然后,somepackage 包含函数 foo,其中包含一些全局变量,基本上是因为它们需要在函数之间共享(例如,当使用使用容器/堆实现的优先级队列时,Swap 函数将从一个全局距离矩阵中获取优先级,该矩阵当然是可变的)。还有许多其他例子。总之,全局变量...
问题是,正如你可能看到的那样,这些变量在所有对处理程序的调用之间是共享的。这是不好的。
我应该如何解决这个问题?肯定有一种简单的方法可以做到这一点,但我还没有找到,因为它看起来像是一种常见的情况...
提前感谢。
编辑
为了更清楚地说明。例如,在我的 A* 包中,我有以下全局变量:
var openVerticesAS PriorityQueueAStar
// 哪些顶点是关闭的
var closedVertices map[int]bool
// 哪些顶点当前是开放的
var openVertices map[int]bool
// 从起点到节点的代价
var gScore map[int]float64
// 从起点到终点,经过节点 i 的代价(g+h)
var fScore map[int]float64
然后,PriorityQueueAStar 的实现如下:
type PriorityQueueAStar []int // rel id
func (pq PriorityQueueAStar) Len() int { return len(pq) }
func (pq PriorityQueueAStar) Empty() bool { return len(pq) == 0 }
func (pq PriorityQueueAStar) Less(i, j int) bool {
return fScore[pq[i]] < fScore[pq[j]]
}
func (pq PriorityQueueAStar) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueueAStar) Push(x interface{}) {
*pq = append(*pq, x.(int))
}
func (pq *PriorityQueueAStar) Pop() interface{} {
old := *pq
n := len(old)
rel := old[n-1]
*pq = old[0 : n-1]
return rel
}
func (pq PriorityQueueAStar) Top() interface{} {
return pq[0]
}
问题是,如何在不将所有这些映射作为全局变量的情况下继续进行这样的操作?如果它们是结构体的一部分,如何从优先级队列函数中访问结构体?
英文:
I know there are some questions and posts/articles regarding this question, but from my newbie view, not exactly.
The thing is, I've got a main program listening to a port and redirecting the calls to a specific handler. The typical structure:
func main() {
http.HandleFunc("/something", specificHandler)
http.ListenAndServe(":8080", nil)
}
With the handler being something like:
func specificHandler(w http.ResponseWriter, r *http.Request) {
somepackage.foo()
}
Then somepackage, which contains the function foo, has some global variables, basically because they're needed for functions to share (e.g., when using a priority queue implemented with a container/heap, which will get the priorities in the Swap function from a global matrix of distances which is of course changable). And many other examples. In summary, global variables...
The problem is, as you might see, that those variables are shared among all the calls to the handler. And that's bad.
How can I actually solve this? There must be an easy to way to do it that I haven't got to yet, because it looks like something so usual...
Thanks in advance.
EDIT
To make it clearer. For example, in my A* package, I've got the following global variables:
var openVerticesAS PriorityQueueAStar
// which vertices are closed
var closedVertices map[int]bool
// which vertices are currently open
var openVertices map[int]bool
// cost from start to node
var gScore map[int]float64
// cost from start to end, passing by node i (g+h)
var fScore map[int]float64
Then, PriorityQueueAStar is implemented as follows:
type PriorityQueueAStar []int // rel id
func (pq PriorityQueueAStar) Len() int { return len(pq) }
func (pq PriorityQueueAStar) Empty() bool { return len(pq) == 0 }
func (pq PriorityQueueAStar) Less(i, j int) bool {
return fScore[pq[i]] < fScore[pq[j]]
}
func (pq PriorityQueueAStar) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueueAStar) Push(x interface{}) {
*pq = append(*pq, x.(int))
}
func (pq *PriorityQueueAStar) Pop() interface{} {
old := *pq
n := len(old)
rel := old[n-1]
*pq = old[0 : n-1]
return rel
}
func (pq PriorityQueueAStar) Top() interface{} {
return pq[0]
}
The question then, is, how do I keep doing this without having all those maps as global variables? If they are part of the struct, how do I access the struct from the priority queue functions?
答案1
得分: 21
当处理程序需要一个变量时,通常意味着你应该实现Handler
接口,而不是提供HandlerFunc
函数。
下面是一个糟糕的示例(使用全局变量):
var globalThing string
func specificHandler(w http.ResponseWriter, r *http.Request) {
w.Write(globalConfigThing)
}
func main() {
globalThing = "Hello world!"
http.HandleFunc("/something", specificHandler)
http.ListenAndServe(":8080", nil)
}
下面是一个更好的示例(不使用全局变量):
type specificHandler struct {
Thing string
}
func (h *specificHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write(h.Thing)
}
func main() {
http.Handle("/something", &specificHandler{Thing: "Hello world!"})
http.ListenAndServe(":8080", nil)
}
如你所见,Handler
可以封装变量。
为了完整起见,另一种方法是使用函数闭包。这对于一次性处理程序很有效,但不可重用,并且更难编写单元测试。
func main() {
scopedThing := "Hello world!"
http.HandleFunc("/something", func(w http.ResponseWriter, r *http.Request) {
w.Write(scopedThing)
})
http.ListenAndServe(":8080", nil)
}
正确地完成后,你现在可以通过将它们作为参数传递等方式避免在somepackage
包中使用全局变量。
**编辑:**例如,你可以在处理程序结构中定义来自somepackage
包的多个PriorityQueueAStar
字段:
type specificHandler struct {
QueueA somepackage.PriorityQueueAStar
QueueB somepackage.PriorityQueueAStar
QueueC somepackage.PriorityQueueAStar
}
func (h *specificHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.QueueA.Push(h.QueueB.Pop)
h.QueueB.Push(h.QueueC.Pop)
w.Write([]byte("Queues pushed and popped"))
}
英文:
When your handler needs a variable, usually that means you should implement the Handler
interface instead of providing a HandlerFunc
function.
Here is a BAD example (using global variables):
var globalThing string
func specificHandler(w http.ResponseWriter, r *http.Request) {
w.Write(globalConfigThing)
}
func main() {
globalThing = "Hello world!"
http.HandleFunc("/something", specificHandler)
http.ListenAndServe(":8080", nil)
}
Here is a BETTER example (not using global variables):
type specificHandler struct {
Thing string
}
func (h *specificHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write(h.Thing)
}
func main() {
http.Handle("/something", &specificHandler{Thing: "Hello world!"})
http.ListenAndServe(":8080", nil)
}
As you can see, a Handler
can encapsulate variables.
For completeness, the other approach is to use a function closure. This works well for once-off handlers but is not re-usable and is harder to write unit tests for.
func main() {
scopedThing := "Hello world!"
http.HandleFunc("/something", func (w http.ResponseWriter, r *http.Request) {
w.Write(scopedThing)
})
http.ListenAndServe(":8080", nil)
}
Done correctly, you can now avoid global variables in your package somepackage
by passing them as arguments, etc.
EDIT: For example, you can define your handler struct with several PriorityQueueAStar
fields from the somepackage
package:
type specificHandler struct {
QueueA somepackage.PriorityQueueAStar
QueueB somepackage.PriorityQueueAStar
QueueC somepackage.PriorityQueueAStar
}
func (h *specificHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.QueueA.Push(h.QueueB.Pop)
h.QueueB.Push(h.QueueC.Pop)
w.Write([]byte("Queues pushed and popped"))
}
答案2
得分: 1
闭包方法是由chowey提到的,但有一个注意事项,即它不能进行测试或重用。如果你有一个函数来返回闭包,它实际上是可测试和可重用的。对于某些类型的数据,这可能会使抽象和实现更清晰:
func handleThing(thing string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write(thing)
}
}
func main() {
http.HandleFunc("/something", handleThing("Hello world!"))
http.ListenAndServe(":8080", nil)
}
英文:
The closure method was mentioned by chowey, but had a caveat that it isn't testable or reusable. It is actually testable and reusable if you have a function to return the closure. For some types of data this may make for a cleaner abstraction and implementation:
func handleThing(thing string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write(thing)
}
}
func main() {
http.HandleFunc("/something", handleThing("Hello world!"))
http.ListenAndServe(":8080", nil)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论