github.com/wangyougui/gf/v2@v2.6.5/net/ghttp/ghttp_request_param.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/wangyougui/gf. 6 7 package ghttp 8 9 import ( 10 "bytes" 11 "fmt" 12 "io" 13 "mime/multipart" 14 "net/http" 15 "reflect" 16 "strings" 17 18 "github.com/wangyougui/gf/v2/container/gvar" 19 "github.com/wangyougui/gf/v2/encoding/gjson" 20 "github.com/wangyougui/gf/v2/encoding/gurl" 21 "github.com/wangyougui/gf/v2/encoding/gxml" 22 "github.com/wangyougui/gf/v2/errors/gcode" 23 "github.com/wangyougui/gf/v2/errors/gerror" 24 "github.com/wangyougui/gf/v2/internal/json" 25 "github.com/wangyougui/gf/v2/internal/utils" 26 "github.com/wangyougui/gf/v2/text/gregex" 27 "github.com/wangyougui/gf/v2/text/gstr" 28 "github.com/wangyougui/gf/v2/util/gconv" 29 "github.com/wangyougui/gf/v2/util/gvalid" 30 ) 31 32 const ( 33 parseTypeRequest = iota 34 parseTypeQuery 35 parseTypeForm 36 ) 37 38 var ( 39 // xmlHeaderBytes is the most common XML format header. 40 xmlHeaderBytes = []byte("<?xml") 41 ) 42 43 // Parse is the most commonly used function, which converts request parameters to struct or struct 44 // slice. It also automatically validates the struct or every element of the struct slice according 45 // to the validation tag of the struct. 46 // 47 // The parameter `pointer` can be type of: *struct/**struct/*[]struct/*[]*struct. 48 // 49 // It supports single and multiple struct converting: 50 // 1. Single struct, post content like: {"id":1, "name":"john"} or ?id=1&name=john 51 // 2. Multiple struct, post content like: [{"id":1, "name":"john"}, {"id":, "name":"smith"}] 52 // 53 // TODO: Improve the performance by reducing duplicated reflect usage on the same variable across packages. 54 func (r *Request) Parse(pointer interface{}) error { 55 return r.doParse(pointer, parseTypeRequest) 56 } 57 58 // ParseQuery performs like function Parse, but only parses the query parameters. 59 func (r *Request) ParseQuery(pointer interface{}) error { 60 return r.doParse(pointer, parseTypeQuery) 61 } 62 63 // ParseForm performs like function Parse, but only parses the form parameters or the body content. 64 func (r *Request) ParseForm(pointer interface{}) error { 65 return r.doParse(pointer, parseTypeForm) 66 } 67 68 // doParse parses the request data to struct/structs according to request type. 69 func (r *Request) doParse(pointer interface{}, requestType int) error { 70 var ( 71 reflectVal1 = reflect.ValueOf(pointer) 72 reflectKind1 = reflectVal1.Kind() 73 ) 74 if reflectKind1 != reflect.Ptr { 75 return gerror.NewCodef( 76 gcode.CodeInvalidParameter, 77 `invalid parameter type "%v", of which kind should be of *struct/**struct/*[]struct/*[]*struct, but got: "%v"`, 78 reflectVal1.Type(), 79 reflectKind1, 80 ) 81 } 82 var ( 83 reflectVal2 = reflectVal1.Elem() 84 reflectKind2 = reflectVal2.Kind() 85 ) 86 switch reflectKind2 { 87 // Single struct, post content like: 88 // 1. {"id":1, "name":"john"} 89 // 2. ?id=1&name=john 90 case reflect.Ptr, reflect.Struct: 91 var ( 92 err error 93 data map[string]interface{} 94 ) 95 // Converting. 96 switch requestType { 97 case parseTypeQuery: 98 if data, err = r.doGetQueryStruct(pointer); err != nil { 99 return err 100 } 101 case parseTypeForm: 102 if data, err = r.doGetFormStruct(pointer); err != nil { 103 return err 104 } 105 default: 106 if data, err = r.doGetRequestStruct(pointer); err != nil { 107 return err 108 } 109 } 110 // TODO: https://github.com/wangyougui/gf/pull/2450 111 // Validation. 112 if err = gvalid.New(). 113 Bail(). 114 Data(pointer). 115 Assoc(data). 116 Run(r.Context()); err != nil { 117 return err 118 } 119 120 // Multiple struct, it only supports JSON type post content like: 121 // [{"id":1, "name":"john"}, {"id":, "name":"smith"}] 122 case reflect.Array, reflect.Slice: 123 // If struct slice conversion, it might post JSON/XML/... content, 124 // so it uses `gjson` for the conversion. 125 j, err := gjson.LoadContent(r.GetBody()) 126 if err != nil { 127 return err 128 } 129 if err = j.Var().Scan(pointer); err != nil { 130 return err 131 } 132 for i := 0; i < reflectVal2.Len(); i++ { 133 if err = gvalid.New(). 134 Bail(). 135 Data(reflectVal2.Index(i)). 136 Assoc(j.Get(gconv.String(i)).Map()). 137 Run(r.Context()); err != nil { 138 return err 139 } 140 } 141 } 142 return nil 143 } 144 145 // Get is alias of GetRequest, which is one of the most commonly used functions for 146 // retrieving parameter. 147 // See r.GetRequest. 148 func (r *Request) Get(key string, def ...interface{}) *gvar.Var { 149 return r.GetRequest(key, def...) 150 } 151 152 // GetBody retrieves and returns request body content as bytes. 153 // It can be called multiple times retrieving the same body content. 154 func (r *Request) GetBody() []byte { 155 if r.bodyContent == nil { 156 r.bodyContent = r.MakeBodyRepeatableRead(true) 157 } 158 return r.bodyContent 159 } 160 161 // MakeBodyRepeatableRead marks the request body could be repeatedly readable or not. 162 // It also returns the current content of the request body. 163 func (r *Request) MakeBodyRepeatableRead(repeatableRead bool) []byte { 164 if r.bodyContent == nil { 165 var err error 166 if r.bodyContent, err = io.ReadAll(r.Body); err != nil { 167 errMsg := `Read from request Body failed` 168 if gerror.Is(err, io.EOF) { 169 errMsg += `, the Body might be closed or read manually from middleware/hook/other package previously` 170 } 171 panic(gerror.WrapCode(gcode.CodeInternalError, err, errMsg)) 172 } 173 } 174 r.Body = utils.NewReadCloser(r.bodyContent, repeatableRead) 175 return r.bodyContent 176 } 177 178 // GetBodyString retrieves and returns request body content as string. 179 // It can be called multiple times retrieving the same body content. 180 func (r *Request) GetBodyString() string { 181 return string(r.GetBody()) 182 } 183 184 // GetJson parses current request content as JSON format, and returns the JSON object. 185 // Note that the request content is read from request BODY, not from any field of FORM. 186 func (r *Request) GetJson() (*gjson.Json, error) { 187 return gjson.LoadWithOptions(r.GetBody(), gjson.Options{ 188 Type: gjson.ContentTypeJson, 189 StrNumber: true, 190 }) 191 } 192 193 // GetMap is an alias and convenient function for GetRequestMap. 194 // See GetRequestMap. 195 func (r *Request) GetMap(def ...map[string]interface{}) map[string]interface{} { 196 return r.GetRequestMap(def...) 197 } 198 199 // GetMapStrStr is an alias and convenient function for GetRequestMapStrStr. 200 // See GetRequestMapStrStr. 201 func (r *Request) GetMapStrStr(def ...map[string]interface{}) map[string]string { 202 return r.GetRequestMapStrStr(def...) 203 } 204 205 // GetStruct is an alias and convenient function for GetRequestStruct. 206 // See GetRequestStruct. 207 func (r *Request) GetStruct(pointer interface{}, mapping ...map[string]string) error { 208 return r.GetRequestStruct(pointer, mapping...) 209 } 210 211 // parseQuery parses query string into r.queryMap. 212 func (r *Request) parseQuery() { 213 if r.parsedQuery { 214 return 215 } 216 r.parsedQuery = true 217 if r.URL.RawQuery != "" { 218 var err error 219 r.queryMap, err = gstr.Parse(r.URL.RawQuery) 220 if err != nil { 221 panic(gerror.WrapCode(gcode.CodeInvalidParameter, err, "Parse Query failed")) 222 } 223 } 224 } 225 226 // parseBody parses the request raw data into r.rawMap. 227 // Note that it also supports JSON data from client request. 228 func (r *Request) parseBody() { 229 if r.parsedBody { 230 return 231 } 232 r.parsedBody = true 233 // There's no data posted. 234 if r.ContentLength == 0 { 235 return 236 } 237 if body := r.GetBody(); len(body) > 0 { 238 // Trim space/new line characters. 239 body = bytes.TrimSpace(body) 240 // JSON format checks. 241 if body[0] == '{' && body[len(body)-1] == '}' { 242 _ = json.UnmarshalUseNumber(body, &r.bodyMap) 243 } 244 // XML format checks. 245 if len(body) > 5 && bytes.EqualFold(body[:5], xmlHeaderBytes) { 246 r.bodyMap, _ = gxml.DecodeWithoutRoot(body) 247 } 248 if body[0] == '<' && body[len(body)-1] == '>' { 249 r.bodyMap, _ = gxml.DecodeWithoutRoot(body) 250 } 251 // Default parameters decoding. 252 if contentType := r.Header.Get("Content-Type"); (contentType == "" || !gstr.Contains(contentType, "multipart/")) && r.bodyMap == nil { 253 r.bodyMap, _ = gstr.Parse(r.GetBodyString()) 254 } 255 } 256 } 257 258 // parseForm parses the request form for HTTP method PUT, POST, PATCH. 259 // The form data is pared into r.formMap. 260 // 261 // Note that if the form was parsed firstly, the request body would be cleared and empty. 262 func (r *Request) parseForm() { 263 if r.parsedForm { 264 return 265 } 266 r.parsedForm = true 267 // There's no data posted. 268 if r.ContentLength == 0 { 269 return 270 } 271 if contentType := r.Header.Get("Content-Type"); contentType != "" { 272 var ( 273 err error 274 repeatableRead = true 275 ) 276 if gstr.Contains(contentType, "multipart/") { 277 // To avoid big memory consuming. 278 // The `multipart/` type form always contains binary data, which is not necessary read twice. 279 repeatableRead = false 280 // multipart/form-data, multipart/mixed 281 if err = r.ParseMultipartForm(r.Server.config.FormParsingMemory); err != nil { 282 panic(gerror.WrapCode(gcode.CodeInvalidRequest, err, "r.ParseMultipartForm failed")) 283 } 284 } else if gstr.Contains(contentType, "form") { 285 // application/x-www-form-urlencoded 286 if err = r.Request.ParseForm(); err != nil { 287 panic(gerror.WrapCode(gcode.CodeInvalidRequest, err, "r.Request.ParseForm failed")) 288 } 289 } 290 if repeatableRead { 291 r.MakeBodyRepeatableRead(true) 292 } 293 if len(r.PostForm) > 0 { 294 // Parse the form data using united parsing way. 295 params := "" 296 for name, values := range r.PostForm { 297 // Invalid parameter name. 298 // Only allow chars of: '\w', '[', ']', '-'. 299 if !gregex.IsMatchString(`^[\w\-\[\]]+$`, name) && len(r.PostForm) == 1 { 300 // It might be JSON/XML content. 301 if s := gstr.Trim(name + strings.Join(values, " ")); len(s) > 0 { 302 if s[0] == '{' && s[len(s)-1] == '}' || s[0] == '<' && s[len(s)-1] == '>' { 303 r.bodyContent = []byte(s) 304 params = "" 305 break 306 } 307 } 308 } 309 if len(values) == 1 { 310 if len(params) > 0 { 311 params += "&" 312 } 313 params += name + "=" + gurl.Encode(values[0]) 314 } else { 315 if len(name) > 2 && name[len(name)-2:] == "[]" { 316 name = name[:len(name)-2] 317 for _, v := range values { 318 if len(params) > 0 { 319 params += "&" 320 } 321 params += name + "[]=" + gurl.Encode(v) 322 } 323 } else { 324 if len(params) > 0 { 325 params += "&" 326 } 327 params += name + "=" + gurl.Encode(values[len(values)-1]) 328 } 329 } 330 } 331 if params != "" { 332 if r.formMap, err = gstr.Parse(params); err != nil { 333 panic(gerror.WrapCode(gcode.CodeInvalidParameter, err, "Parse request parameters failed")) 334 } 335 } 336 } 337 } 338 // It parses the request body without checking the Content-Type. 339 if r.formMap == nil { 340 if r.Method != http.MethodGet { 341 r.parseBody() 342 } 343 if len(r.bodyMap) > 0 { 344 r.formMap = r.bodyMap 345 } 346 } 347 } 348 349 // GetMultipartForm parses and returns the form as multipart forms. 350 func (r *Request) GetMultipartForm() *multipart.Form { 351 r.parseForm() 352 return r.MultipartForm 353 } 354 355 // GetMultipartFiles parses and returns the post files array. 356 // Note that the request form should be type of multipart. 357 func (r *Request) GetMultipartFiles(name string) []*multipart.FileHeader { 358 form := r.GetMultipartForm() 359 if form == nil { 360 return nil 361 } 362 if v := form.File[name]; len(v) > 0 { 363 return v 364 } 365 // Support "name[]" as array parameter. 366 if v := form.File[name+"[]"]; len(v) > 0 { 367 return v 368 } 369 // Support "name[0]","name[1]","name[2]", etc. as array parameter. 370 var ( 371 key string 372 files = make([]*multipart.FileHeader, 0) 373 ) 374 for i := 0; ; i++ { 375 key = fmt.Sprintf(`%s[%d]`, name, i) 376 if v := form.File[key]; len(v) > 0 { 377 files = append(files, v[0]) 378 } else { 379 break 380 } 381 } 382 if len(files) > 0 { 383 return files 384 } 385 return nil 386 }