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: %+vn", data) }下面的 Unpack 函數主要完成三件事情。第一,它呼叫 req.ParseForm() 來解析 HTTP 請求。然後,req.Form 將包含所有的請求引數,不管 HTTP 用戶端使用的是 GET 還是 POST 請求方法。
// 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, value); err != nil { return fmt.Errorf("%s: %v", name, err) } } } } return nil }最後,Unpack 遍歷 HTTP 請求的 name/valu 引數鍵值對,並且根據更新相應的結構體成員。回想一下,同一個名字的引數可能出現多次。如果發生這種情況,並且對應的結構體成員是一個 slice,那麼就將所有的引數新增到 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