对于一个 web 服务,大部分 http 处理函数要做的第一件事情就是展开请求中的参数到本地变量中。我们定义了一个工具函数,叫 params.Unpack,通过使用结构体成员标签机制来让 http 处理函数解析请求参数更方便。
首先,我们看看如何使用它。下面的 search 函数是一个 http 请求处理函数。它定义了一个匿名结构体类型的变量,用结构体的每个成员表示 http 请求的参数。
其中结构体成员标签指明了对于请求参数的名字,为了减少 URL 的长度这些参数名通常都是神秘的缩略词。Unpack 将请求参数填充到合适的结构体成员中,这样我们可以方便地通过合适的类型类来访问这些参数。
import "gopl.io/ch12/params"// 搜索实现/search url端点。func search(resp http.ResponseWriter,req *http.Request) { var data struct { Labels []string `http:"l"` MaxResults int `http:"max"` Exact bool `http:"x"` } data.MaxResults = 10 // set default if err := params.Unpack(req,&data); err != nil { http.Error(resp,err.Error(),http.StatusBadRequest) // 400 return } // ...处理程序的其余部分... fmt.Fprintf(resp,"Search: %+v\n",data)}下面的 Unpack 函数主要完成三件事情。第一,它调用 req.ParseForm() 来解析 http 请求。然后,req.Form 将包含所有的请求参数,不管 http 客户端使用的是 GET 还是 POST 请求方法。
下一步,Unpack 函数将构建每个结构体成员有效参数名字到成员变量的映射。如果结构体成员有成员标签的话,有效参数名字可能和实际的成员名字不相同。reflect.Type 的 FIEld 方法将返回一个 reflect.StructFIEld,里面含有每个成员的名字、类型和可选的成员标签等信息。
其中成员标签信息对应 reflect.StructTag 类型的字符串,并且提供了 Get 方法用于解析和根据特定 key 提取的子串,例如这里的 http:"..." 形式的子串。
// unpack填充ptr指向的结构的字段// 从请求中的http请求参数。func Unpack(req *http.Request,ptr interface{}) error { if err := req.ParseForm(); err != nil { return err } // 构建由有效名称键控的字段的映射。 fIElds := make(map[string]reflect.Value) v := reflect.ValueOf(ptr).Elem() // 结构变量 for i := 0; i < v.NumFIEld(); i++ { fIEldInfo := v.Type().FIEld(i) // 反射.StructFIEld tag := fIEldInfo.Tag // 一个 reflect.StructTag name := tag.Get("http") if name == "" { name = strings.Tolower(fIEldInfo.name) } fIElds[name] = v.FIEld(i) } // 为请求中的每个参数更新结构字段。 for name,values := range req.Form { f := fIElds[name] if !f.IsValID() { continue // 忽略无法识别的http参数 } for _,value := range values { if f.Kind() == reflect.Slice { elem := reflect.New(f.Type().Elem()).Elem() if err := populate(elem,value); err != nil { return fmt.Errorf("%s: %v",name,err) } f.Set(reflect.Append(f,elem)) } else { if err := populate(f,err) } } } } return nil}最后,Unpack 遍历 http 请求的 name/valu 参数键值对,并且根据更新相应的结构体成员。回想一下,同一个名字的参数可能出现多次。如果发生这种情况,并且对应的结构体成员是一个 slice,那么就将所有的参数添加到 slice 中。其它情况,对应的成员值将被覆盖,只有最后一次出现的参数值才是起作用的。
populate 函数小心用请求的字符串类型参数值来填充单一的成员 v(或者是 slice 类型成员中的单一的元素)。目前,它仅支持字符串、有符号整数和布尔型。
func populate(v reflect.Value,value string) error { switch v.Kind() { case reflect.String: v.SetString(value) case reflect.Int: i,err := strconv.ParseInt(value,10,64) if err != nil { return err } v.SetInt(i) case reflect.Bool: b,err := strconv.ParseBool(value) if err != nil { return err } v.SetBool(b) default: return fmt.Errorf("unsupported kind %s",v.Type()) } return nil}如果将上面的处理程序添加到一个 web 服务器,则可以产生以下的会话:
$ go build gopl.io/ch12/search
$ ./search &
$ ./fetch 'http://localhost:12345/search'
Search: {Labels:[] MaxResults:10 Exact:false}
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming'
Search: {Labels:[golang programming] MaxResults:10 Exact:false}
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100'
Search: {Labels:[golang programming] MaxResults:100 Exact:false}
$ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming'
Search: {Labels:[golang programming] MaxResults:10 Exact:true}
$ ./fetch 'http://localhost:12345/search?q=hello&x=123'
x: strconv.ParseBool: parsing "123": invalID Syntax
$ ./fetch 'http://localhost:12345/search?q=hello&max=lots'
max: strconv.ParseInt: parsing "lots": invalID Syntax
以上是内存溢出为你收集整理的Go语言获取结构体字段标识全部内容,希望文章能够帮你解决Go语言获取结构体字段标识所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)