简体   繁体   English

Go:如何创建可以提供配置文件中描述的 URL 的服务器

[英]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更新:如果我运行这个服务器,那么无论我点击哪个端点,它总是会返回我500test2主体

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类型中的BodyStatus字段不相同。 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)
}

Link to example.链接到示例。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM