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  }