|
1 | 1 | package api |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/json" |
| 5 | + "fmt" |
| 6 | + "io" |
| 7 | + "io/ioutil" |
| 8 | + "net/http" |
4 | 9 | "reflect" |
5 | 10 |
|
| 11 | + "github.com/ghodss/yaml" |
6 | 12 | "github.com/gin-gonic/gin" |
7 | | - "github.com/loopfz/gadgeto/tonic" |
8 | 13 | ) |
9 | 14 |
|
10 | | -// bodyBindHook is a wrapper around the default binding hook of tonic. |
| 15 | +const ( |
| 16 | + // default max body bytes: 256KB |
| 17 | + // this can be overridden via configuration |
| 18 | + defaultMaxBodyBytes = 256 * 1024 |
| 19 | + |
| 20 | + // absolute upper limit for configuration max body bytes: 10MB |
| 21 | + upperLimitMaxBodyBytes = 10 * 1024 * 1024 |
| 22 | + |
| 23 | + // absolute lower limit for configuration max body bytes: 1KB |
| 24 | + lowerLimitMaxBodyBytes = 1024 |
| 25 | +) |
| 26 | + |
| 27 | +var yamlBind = yamlBinding{} |
| 28 | + |
| 29 | +type yamlBinding struct{} |
| 30 | + |
| 31 | +func (yamlBinding) Name() string { return "yamlBinding" } |
| 32 | +func (yamlBinding) Bind(req *http.Request, obj interface{}) error { |
| 33 | + bodyBytes, err := ioutil.ReadAll(req.Body) |
| 34 | + if err != nil { |
| 35 | + return err |
| 36 | + } |
| 37 | + defer req.Body.Close() |
| 38 | + return yaml.Unmarshal(bodyBytes, obj, jsonNumberOpt) |
| 39 | +} |
| 40 | + |
| 41 | +// defaultBindingHook is a wrapper around the yaml binding. |
11 | 42 | // It adds the possibility to bind a specific field in an object rather than |
12 | 43 | // unconditionally binding the whole object. |
13 | | -func bodyBindHook(c *gin.Context, v interface{}) error { |
14 | | - val := reflect.ValueOf(v) |
15 | | - typ := reflect.TypeOf(v).Elem() |
16 | | - |
17 | | - for i := 0; i < typ.NumField(); i++ { |
18 | | - ft := typ.Field(i) |
19 | | - if _, ok := ft.Tag.Lookup("body"); !ok { |
20 | | - continue |
21 | | - } |
22 | | - flt := ft.Type |
23 | | - var fv reflect.Value |
24 | | - if flt.Kind() == reflect.Map { |
25 | | - fv = reflect.New(flt) |
26 | | - } else { |
27 | | - fv = reflect.New(flt.Elem()) |
| 44 | +func defaultBindingHook(maxBodyBytes int64) func(*gin.Context, interface{}) error { |
| 45 | + if maxBodyBytes == 0 { |
| 46 | + maxBodyBytes = defaultMaxBodyBytes |
| 47 | + } else if maxBodyBytes > upperLimitMaxBodyBytes { |
| 48 | + maxBodyBytes = upperLimitMaxBodyBytes |
| 49 | + } else if maxBodyBytes < lowerLimitMaxBodyBytes { |
| 50 | + maxBodyBytes = lowerLimitMaxBodyBytes |
| 51 | + } |
| 52 | + |
| 53 | + return func(c *gin.Context, v interface{}) error { |
| 54 | + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxBodyBytes) |
| 55 | + if c.Request.ContentLength == 0 || c.Request.Method == http.MethodGet { |
| 56 | + return nil |
28 | 57 | } |
29 | | - if err := tonic.DefaultBindingHook(c, fv.Interface()); err != nil { |
30 | | - return err |
| 58 | + |
| 59 | + val := reflect.ValueOf(v) |
| 60 | + typ := reflect.TypeOf(v).Elem() |
| 61 | + |
| 62 | + for i := 0; i < typ.NumField(); i++ { |
| 63 | + ft := typ.Field(i) |
| 64 | + if _, ok := ft.Tag.Lookup("body"); !ok { |
| 65 | + continue |
| 66 | + } |
| 67 | + flt := ft.Type |
| 68 | + var fv reflect.Value |
| 69 | + if flt.Kind() == reflect.Map { |
| 70 | + fv = reflect.New(flt) |
| 71 | + } else { |
| 72 | + fv = reflect.New(flt.Elem()) |
| 73 | + } |
| 74 | + if err := c.ShouldBindWith(fv.Interface(), yamlBind); err != nil && err != io.EOF { |
| 75 | + return fmt.Errorf("error parsing request body: %s", err.Error()) |
| 76 | + } |
| 77 | + if flt.Kind() == reflect.Map { |
| 78 | + val.Elem().Field(i).Set(fv.Elem()) |
| 79 | + } else { |
| 80 | + val.Elem().Field(i).Set(fv) |
| 81 | + } |
31 | 82 | } |
32 | | - if flt.Kind() == reflect.Map { |
33 | | - val.Elem().Field(i).Set(fv.Elem()) |
34 | | - } else { |
35 | | - val.Elem().Field(i).Set(fv) |
| 83 | + |
| 84 | + if err := c.ShouldBindWith(v, yamlBind); err != nil && err != io.EOF { |
| 85 | + return fmt.Errorf("error parsing request body: %s", err.Error()) |
36 | 86 | } |
| 87 | + return nil |
37 | 88 | } |
| 89 | +} |
38 | 90 |
|
39 | | - return tonic.DefaultBindingHook(c, v) |
| 91 | +func jsonNumberOpt(dec *json.Decoder) *json.Decoder { |
| 92 | + dec.UseNumber() |
| 93 | + return dec |
40 | 94 | } |
0 commit comments