[英]Go: How to create a server which can serve urls described in config file
could anyone help me here please as I'm new to golang?因为我是 golang 的新手,所以有人可以在这里帮助我吗? I have a yaml file which looks like this:
我有一个 yaml 文件,如下所示:
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更新:如果我运行这个服务器,那么无论我点击哪个端点,它总是会返回我
500
和test2
主体
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. for
循环在每次迭代时重新分配handler
变量。 In the loop body, you create a new function and pass it to mux.HandlerFun
.在循环体中,您创建一个新的 function 并将其传递给
mux.HandlerFun
。 These function bodies kind of inherit the outer scope, and access this handler
variable.这些 function 主体继承了外部 scope,并访问此
handler
变量。 The variable is reassigned outside of the functions, and thus the values each handler function has access to changes with it.该变量在函数之外重新分配,因此每个处理程序 function 可以访问的值随之更改。 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.要解决此问题,您可以做的是屏蔽循环使用的
handler
变量,并创建每个处理程序唯一的 scope。 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):像 JavaScript 这样的语言的经典方法(这是 - 或者在我写一些 JS 时曾经回来过 - 一个常见问题)是将代码包装在 IIFE 中(立即调用 Function 表达式):
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:这有点乱,因为 golang 是正确的块范围的,你可以这样做:
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
?不过,我注意到您在添加到问题中的类型中使用指针有些奇怪……像
Port
这样的字段属于*string
类型? Why wouldn't you just use string
?你为什么不直接使用
string
? No Same for the Body
and Status
fields in the Response
type. Response
类型中的Body
和Status
字段不相同。 By changing them to plain string
fields you don't have to dereference them in your handler functions.通过将它们更改为纯
string
字段,您不必在处理函数中取消引用它们。 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. Handlers
现在是指向HandlerConfig
值切片的指针。 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.一般来说,指向切片的指针,尤其是在配置类型中,是错误的代码。
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.如果您在 YAML 文件中定义一个表示配置的结构,则可以使用yaml package 将 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)
}
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.在您的情况下,您可能希望处理结构中的路由,然后引用结构中的字段以获取路由名称、如何处理请求正文等内容。如果您的 YAML 存储在文件中,你必须使用类似
io
package 的东西将文件读入 YAML package 可以解析的字节数组。 See here for a reference.请参阅此处以供参考。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.