英文:
Go: How to create a server which can serve urls described in config file
问题
有人可以帮我吗?因为我对golang还不熟悉。我有一个yaml文件,内容如下:
port: 5000
handlers:
- name: test1
uri: /api/test1
response:
status: 200
body: test1
- name: test2
uri: /api/test2
response:
status: 500
body: test2
基于这个文件,我想创建一个服务器。目前我尝试这样做,但似乎不像预期那样工作。我做错了什么?有更好的方法来实现我所需的吗?
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"gopkg.in/yaml.v2"
)
func main() {
config := parseYaml("conf.yaml")
configHandlers := config.Handlers
mux := http.NewServeMux()
for _, handler := range *configHandlers {
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", *config.Port), mux))
}
type YamlConfig struct {
Port *string `yaml:"port"`
Handlers *[]HandlerConfig `yaml:"handlers"`
}
type HandlerConfig struct {
Uri *string `yaml:"uri"`
Name *string `yaml:"name"`
Response *Response `yaml:"response"`
}
type Response struct {
Status *int `yaml:"status"`
Body *string `yaml:"body"`
}
func (c *YamlConfig) parseYaml(data []byte) error {
return yaml.Unmarshal(data, c)
}
func parseYaml(path string) YamlConfig {
data, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal(err)
}
var config YamlConfig
if err := config.parseYaml(data); err != nil {
log.Fatal(err)
}
return config
}
更新:
如果我运行这个服务器,无论我访问哪个端点,它都会始终返回500
和test2
作为响应体。
英文:
could anyone help me here please as I'm new to golang? I have a yaml file which looks like this:
port: 5000
handlers:
- name: test1
uri: /api/test1
response:
status: 200
body: test1
- name: test2
uri: /api/test2
response:
status: 500
body: test2
based on this file I want to create a server. Currently I'm trying to do it this way, but looks like it doesn't work as expected.
What am I doing wrong and what is the better way to achieve what I need?
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"gopkg.in/yaml.v2"
)
func main() {
config := parseYaml("conf.yaml")
configHandlers := config.Handlers
mux := http.NewServeMux()
for _, handler := range *configHandlers {
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", *config.Port), mux))
}
type YamlConfig struct {
Port *string `yaml:"port"`
Handlers *[]HandlerConfig `yaml:"handlers"`
}
type HandlerConfig struct {
Uri *string `yaml:"uri"`
Name *string `yaml:"name"`
Response *Response `yaml:"response"`
}
type Response struct {
Status *int `yaml:"status"`
Body *string `yaml:"body"`
}
func (c *YamlConfig) parseYaml(data []byte) error {
return yaml.Unmarshal(data, c)
}
func parseYaml(path string) YamlConfig {
data, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal(err)
}
var config YamlConfig
if err := config.parseYaml(data); err != nil {
log.Fatal(err)
}
return config
}
Update:
If I run this server then regardless of which endpoint I hit, it will always return me 500
and test2
in body
答案1
得分: 1
你所看到的似乎是人们常见的一个陷阱:
configHandlers := config.Handlers
mux := http.NewServeMux()
for _, handler := range *configHandlers {
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}
在每次迭代中,for
循环会重新分配 handler
变量。在循环体中,你创建了一个新的函数并将其传递给 mux.HandlerFun
。这些函数体在某种程度上继承了外部作用域,并访问了 handler
变量。该变量在函数外部被重新赋值,因此每个处理程序函数可以访问的值也随之改变。为了解决这个问题,你可以掩盖循环使用的 handler
变量,并创建一个对每个处理程序唯一的作用域。在像 JavaScript 这样的语言中(在我写一些 JavaScript 时是一个常见问题),经典的解决方法是将代码包装在 IIFE(立即调用的函数表达式)中:
for _, handler := range *configHandlers {
func (handler *HandlerConfig) { // handler 现在是传递给此函数的参数
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}(handler) // 使用当前的 handler 值调用函数
}
这有点混乱,因为 Go 语言具有适当的块作用域,你可以直接这样做:
for _, handler := range *configHandlers {
h := handler // 在内部作用域中创建一个变量
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
// 现在 h 将引用每次迭代中的唯一副本
w.WriteHeader(*h.Response.Status)
fmt.Fprintf(w, *h.Response.Body)
})
}
这应该可以解决问题。不过,我注意到你在问题中添加的类型中使用了指针的一些奇怪之处... Port
字段的类型为 *string
?为什么不直接使用 string
?Response
类型中的 Body
和 Status
字段也是如此。将它们更改为普通的 string
字段,你就不需要在处理程序函数中解引用它们了。代码看起来会更清晰。
更大的问题是这个字段:
Handlers *[]HandlerConfig `yaml:"handlers"`
我不确定你是否真正知道这个字段的类型,但它几乎没有意义。Handlers
现在是指向 HandlerConfig
值切片的指针。我猜你想要的是:
// Handlers 是 HandlerConfig 值的切片:
Handlers []HandlerConfig `yaml:"handlers"`
// 或者 Handlers 是指向 HandlerConfig 值的指针切片
Handlers []*HandlerConfig `yaml:"handlers"`
一般来说,在配置类型中使用指向切片的指针,尤其是在配置类型中,是不好的代码风格。
英文:
What you're seeing is seemingly a common pitfall for people:
configHandlers := config.Handlers
mux := http.NewServeMux()
for _, handler := range *configHandlers {
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}
The for
loop, on each iteration, reassigns the handler
variable. In the loop body, you create a new function and pass it to mux.HandlerFun
. These function bodies kind of inherit the outer scope, and access this handler
variable. The variable is reassigned outside of the functions, and thus the values each handler function has access to changes with it. What you can do to address the issue is mask the handler
variable the loop uses, and create a scope that is unique to each handler. The classic way in languages like JavaScript (where this is - or used to be back when I wrote some JS - a common issue) is to wrap the code in an IIFE (Immediately Invoked Function Expression):
for _, handler := range *configHandlers {
func (handler *HandlerConfig) { // handler is now the argument passed to this function
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}(handler) // call the function with the _current_ value of handler
}
This is a tad messy, and because golang is properly block-scoped, you can just do this:
for _, handler := range *configHandlers {
h := handler // create a variable in the inner scope
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
// now h will reference a copy unique to each iteration
w.WriteHeader(*h.Response.Status)
fmt.Fprintf(w, *h.Response.Body)
})
}
That ought to fix it. I've noticed some weirdness with your use of pointers in the types you've added to your question, though... Fields like Port
being of type *string
? Why wouldn't you just use string
? No Same for the Body
and Status
fields in the Response
type. By changing them to plain string
fields you don't have to dereference them in your handler functions. It will look a lot cleaner.
A bigger worry is this field:
Handlers *[]HandlerConfig `yaml:"handlers"`
I'm not sure if you really know what the type of this field is, but it makes next to no sense. Handlers
is now a pointer to a slice of HandlerConfig
values. I'm assuming you wanted this field to be:
// Handlers is a slice of HandlerConfig values:
Handlers []HandlerConfig `yaml:"handlers"`
// or Handlers is a slice of pointers to HandlerConfig values
Handlers []*HandlerConfig `yaml:"handlers"`
Generally speaking, a pointer to a slice, especially in a config type is bad code.
答案2
得分: 0
如果你定义了一个结构体来表示你的YAML文件中的配置,你可以使用yaml包将yaml解析为该类型的实例化结构体。然后,你可以像处理其他结构体一样引用结构体中的字段。
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
type YamlExample struct {
FieldOne string `yaml:"fieldOne"`
NestedField struct {
Name string `yaml:"name"`
} `yaml:"nestedField"`
}
const YamlEx string = `
fieldOne: one
nestedField:
name: nestedFieldName
`
func main() {
var yamlE YamlExample
err := yaml.Unmarshal([]byte(YamlEx), &yamlE)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", yamlE)
}
在你的情况下,你可能希望在一个结构体中处理路由,然后引用结构体中的字段来处理路由名称、请求体等。如果你的YAML存储在文件中,你需要使用类似io
包的方法将文件读取到可以被YAML包解析的字节数组中。参考这里。
英文:
If you define a struct that will represent the configuration in your YAML file, you can unmarshall the yaml into an instantiated struct of that type using the yaml package. From there, you can reference the fields in the struct as any other struct.
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
type YamlExample struct {
FieldOne string `yaml:"fieldOne"`
NestedField struct {
Name string `yaml:"name"`
} `yaml:"nestedField"`
}
const YamlEx string = `
fieldOne: one
nestedField:
name: nestedFieldName
`
func main() {
var yamlE YamlExample
err := yaml.Unmarshal([]byte(YamlEx), &yamlE)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", yamlE)
}
In your case, you'd probably want to handle the routes in a struct and then reference the fields in the struct for things like route name, how to handle the body of the request, etc. If your YAML is stored in a file, you'll have to use something like the io
package to read the file into a byte array that the YAML package can parse. See here for a reference.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论