trpc.group/trpc-go/trpc-go@v1.0.3/http/serialization_form.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 package http 15 16 import ( 17 "fmt" 18 "net/url" 19 20 "trpc.group/trpc-go/trpc-go/codec" 21 22 "github.com/go-playground/form/v4" 23 "github.com/mitchellh/mapstructure" 24 ) 25 26 // Uses the same tag as json. 27 var tag = "json" 28 29 func init() { 30 codec.RegisterSerializer( 31 codec.SerializationTypeForm, 32 NewFormSerialization(tag), 33 ) 34 } 35 36 // NewFormSerialization initializes the form serialized object. 37 func NewFormSerialization(tag string) codec.Serializer { 38 encoder := form.NewEncoder() 39 encoder.SetTagName(tag) 40 decoder := form.NewDecoder() 41 decoder.SetTagName(tag) 42 return &FormSerialization{ 43 tagname: tag, 44 encode: encoder.Encode, 45 decode: wrapDecodeWithRecovery(decoder.Decode), 46 } 47 } 48 49 // FormSerialization packages the kv structure of http get request. 50 type FormSerialization struct { 51 tagname string 52 encode func(interface{}) (url.Values, error) 53 decode func(interface{}, url.Values) error 54 } 55 56 // Unmarshal unpacks kv structure. 57 func (j *FormSerialization) Unmarshal(in []byte, body interface{}) error { 58 values, err := url.ParseQuery(string(in)) 59 if err != nil { 60 return err 61 } 62 switch body.(type) { 63 // go-playground/form does not support map structure. 64 case map[string]interface{}, *map[string]interface{}, map[string]string, *map[string]string, 65 url.Values, *url.Values: // Essentially, the underlying type of 'url.Values' is also a map. 66 return unmarshalValues(j.tagname, values, body) 67 default: 68 } 69 // First try using go-playground/form, it can handle nested struct. 70 // But it cannot handle Chinese characters in byte slice. 71 err = j.decode(body, values) 72 if err == nil { 73 return nil 74 } 75 // Second try using mapstructure. 76 if e := unmarshalValues(j.tagname, values, body); e != nil { 77 return fmt.Errorf("unmarshal error: first try err = %+v, second try err = %w", err, e) 78 } 79 return nil 80 } 81 82 // wrapDecodeWithRecovery wraps the decode function, adding panic recovery to handle 83 // panics as errors. This function is designed to prevent malformed query parameters 84 // from causing a panic, which is the default behavior of the go-playground/form decoder 85 // implementation. This is because, in certain cases, it's more acceptable to receive 86 // a degraded result rather than experiencing a direct server crash. 87 // Besides, the behavior of not panicking also ensures backward compatibility (<v0.16.0). 88 // The original go-playground/form has an issue with introducing 'strict' behavior 89 // into its underlying implementation to replace hard panic. However, a promising 90 // outcome cannot be foreseen. 91 // Refer to: https://github.com/go-playground/form/issues/28. 92 func wrapDecodeWithRecovery( 93 f func(interface{}, url.Values) error, 94 ) func(interface{}, url.Values) error { 95 return func(v interface{}, values url.Values) (err error) { 96 defer func() { 97 if e := recover(); e != nil { 98 err = fmt.Errorf("panic: %+v", e) 99 } 100 }() 101 return f(v, values) 102 } 103 } 104 105 // unmarshalValues parses the corresponding fields in values according to tagname. 106 func unmarshalValues(tagname string, values url.Values, body interface{}) error { 107 // To handle the scenario where the underlying type of 'body' is 'url.Values'. 108 if b, ok := body.(url.Values); ok && b != nil { 109 for k, v := range values { 110 b[k] = v 111 } 112 return nil 113 } 114 params := map[string]interface{}{} 115 for k, v := range values { 116 if len(v) == 1 { 117 params[k] = v[0] 118 } else { 119 params[k] = v 120 } 121 } 122 config := &mapstructure.DecoderConfig{TagName: tagname, Result: body, WeaklyTypedInput: true, Metadata: nil} 123 decoder, err := mapstructure.NewDecoder(config) 124 if err != nil { 125 return err 126 } 127 return decoder.Decode(params) 128 } 129 130 // Marshal packages kv structure. 131 func (j *FormSerialization) Marshal(body interface{}) ([]byte, error) { 132 if req, ok := body.(url.Values); ok { // Used to send form urlencode post request to backend. 133 return []byte(req.Encode()), nil 134 } 135 val, err := j.encode(body) 136 if err != nil { 137 return nil, err 138 } 139 return []byte(val.Encode()), nil 140 }