Refator:
1. Move BindXXX core logic to context.Context for two reasons: 1.1 Controller should be stateless -- Due to historical reason, it's hard for us to do this but we should try it 1.2 If users didn't use Controller to write their functions, they should be allowed to use those methods 2. Move XXXResp to context.Context
This commit is contained in:
parent
0ace49bd46
commit
e7d91a2bed
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,4 +12,6 @@ pkg/_beeTmp2/
|
||||
test/tmp/
|
||||
core/config/env/pkg/
|
||||
|
||||
my save path/
|
||||
|
||||
profile.out
|
||||
|
||||
@ -55,6 +55,7 @@
|
||||
- Deprecated BeeMap and replace all usage with `sync.map` [4616](https://github.com/beego/beego/pull/4616)
|
||||
- TaskManager support graceful shutdown [4635](https://github.com/beego/beego/pull/4635)
|
||||
- Add comments to `web.Config`, rename `RouterXXX` to `CtrlXXX`, define `HandleFunc` [4714](https://github.com/beego/beego/pull/4714)
|
||||
- Refactor: Move `BindXXX` and `XXXResp` methods to `context.Context`. [4718](https://github.com/beego/beego/pull/4718)
|
||||
- fix bug:reflect.ValueOf(nil) in getFlatParams [4715](https://github.com/beego/beego/pull/4715)
|
||||
|
||||
## Fix Sonar
|
||||
|
||||
2
go.mod
2
go.mod
@ -36,5 +36,7 @@ require (
|
||||
go.etcd.io/etcd/client/v3 v3.5.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
@ -27,24 +27,38 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
"github.com/beego/beego/v2/server/web/session"
|
||||
)
|
||||
|
||||
// Commonly used mime-types
|
||||
const (
|
||||
ApplicationJSON = "application/json"
|
||||
ApplicationXML = "application/xml"
|
||||
ApplicationYAML = "application/x-yaml"
|
||||
TextXML = "text/xml"
|
||||
ApplicationJSON = "application/json"
|
||||
ApplicationXML = "application/xml"
|
||||
ApplicationForm = "application/x-www-form-urlencoded"
|
||||
ApplicationProto = "application/x-protobuf"
|
||||
ApplicationYAML = "application/x-yaml"
|
||||
TextXML = "text/xml"
|
||||
|
||||
formatTime = "15:04:05"
|
||||
formatDate = "2006-01-02"
|
||||
formatDateTime = "2006-01-02 15:04:05"
|
||||
formatDateTimeT = "2006-01-02T15:04:05"
|
||||
)
|
||||
|
||||
// NewContext return the Context with Input and Output
|
||||
@ -65,6 +79,108 @@ type Context struct {
|
||||
_xsrfToken string
|
||||
}
|
||||
|
||||
func (ctx *Context) Bind(obj interface{}) error {
|
||||
ct, exist := ctx.Request.Header["Content-Type"]
|
||||
if !exist || len(ct) == 0 {
|
||||
return ctx.BindJSON(obj)
|
||||
}
|
||||
i, l := 0, len(ct[0])
|
||||
for i < l && ct[0][i] != ';' {
|
||||
i++
|
||||
}
|
||||
switch ct[0][0:i] {
|
||||
case ApplicationJSON:
|
||||
return ctx.BindJSON(obj)
|
||||
case ApplicationXML, TextXML:
|
||||
return ctx.BindXML(obj)
|
||||
case ApplicationForm:
|
||||
return ctx.BindForm(obj)
|
||||
case ApplicationProto:
|
||||
return ctx.BindProtobuf(obj.(proto.Message))
|
||||
case ApplicationYAML:
|
||||
return ctx.BindYAML(obj)
|
||||
default:
|
||||
return errors.New("Unsupported Content-Type:" + ct[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Resp sends response based on the Accept Header
|
||||
// By default response will be in JSON
|
||||
func (ctx *Context) Resp(data interface{}) error {
|
||||
accept := ctx.Input.Header("Accept")
|
||||
switch accept {
|
||||
case ApplicationYAML:
|
||||
return ctx.YamlResp(data)
|
||||
case ApplicationXML, TextXML:
|
||||
return ctx.XMLResp(data)
|
||||
case ApplicationProto:
|
||||
return ctx.ProtoResp(data.(proto.Message))
|
||||
default:
|
||||
return ctx.JSONResp(data)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONResp(data interface{}) error {
|
||||
return ctx.Output.JSON(data, false, false)
|
||||
}
|
||||
|
||||
func (ctx *Context) XMLResp(data interface{}) error {
|
||||
return ctx.Output.XML(data, false)
|
||||
}
|
||||
|
||||
func (ctx *Context) YamlResp(data interface{}) error {
|
||||
return ctx.Output.YAML(data)
|
||||
}
|
||||
|
||||
func (ctx *Context) ProtoResp(data proto.Message) error {
|
||||
return ctx.Output.Proto(data)
|
||||
}
|
||||
|
||||
// BindYAML only read data from http request body
|
||||
func (ctx *Context) BindYAML(obj interface{}) error {
|
||||
return yaml.Unmarshal(ctx.Input.RequestBody, obj)
|
||||
}
|
||||
|
||||
// BindForm will parse form values to struct via tag.
|
||||
func (ctx *Context) BindForm(obj interface{}) error {
|
||||
err := ctx.Request.ParseForm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ParseForm(ctx.Request.Form, obj)
|
||||
}
|
||||
|
||||
// BindJSON only read data from http request body
|
||||
func (ctx *Context) BindJSON(obj interface{}) error {
|
||||
return json.Unmarshal(ctx.Input.RequestBody, obj)
|
||||
}
|
||||
|
||||
// BindProtobuf only read data from http request body
|
||||
func (ctx *Context) BindProtobuf(obj proto.Message) error {
|
||||
return proto.Unmarshal(ctx.Input.RequestBody, obj)
|
||||
}
|
||||
|
||||
// BindXML only read data from http request body
|
||||
func (ctx *Context) BindXML(obj interface{}) error {
|
||||
return xml.Unmarshal(ctx.Input.RequestBody, obj)
|
||||
}
|
||||
|
||||
// ParseForm will parse form values to struct via tag.
|
||||
func ParseForm(form url.Values, obj interface{}) error {
|
||||
objT := reflect.TypeOf(obj)
|
||||
objV := reflect.ValueOf(obj)
|
||||
if !isStructPtr(objT) {
|
||||
return fmt.Errorf("%v must be a struct pointer", obj)
|
||||
}
|
||||
objT = objT.Elem()
|
||||
objV = objV.Elem()
|
||||
return parseFormToStruct(form, objT, objV)
|
||||
}
|
||||
|
||||
func isStructPtr(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// Reset initializes Context, BeegoInput and BeegoOutput
|
||||
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx.Request = r
|
||||
@ -91,7 +207,7 @@ func (ctx *Context) Abort(status int, body string) {
|
||||
|
||||
// WriteString writes a string to response body.
|
||||
func (ctx *Context) WriteString(content string) {
|
||||
ctx.ResponseWriter.Write([]byte(content))
|
||||
_, _ = ctx.ResponseWriter.Write([]byte(content))
|
||||
}
|
||||
|
||||
// GetCookie gets a cookie from a request for a given key.
|
||||
@ -124,7 +240,10 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
|
||||
sig := parts[2]
|
||||
|
||||
h := hmac.New(sha256.New, []byte(Secret))
|
||||
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||
_, err := fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
|
||||
return "", false
|
||||
@ -138,7 +257,7 @@ func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interf
|
||||
vs := base64.URLEncoding.EncodeToString([]byte(value))
|
||||
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
h := hmac.New(sha256.New, []byte(Secret))
|
||||
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||
_, _ = fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||
sig := fmt.Sprintf("%02x", h.Sum(nil))
|
||||
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
|
||||
ctx.Output.Cookie(name, cookie, others...)
|
||||
|
||||
189
server/web/context/form.go
Normal file
189
server/web/context/form.go
Normal file
@ -0,0 +1,189 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sliceOfInts = reflect.TypeOf([]int(nil))
|
||||
sliceOfStrings = reflect.TypeOf([]string(nil))
|
||||
)
|
||||
|
||||
// ParseForm will parse form values to struct via tag.
|
||||
// Support for anonymous struct.
|
||||
func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) error {
|
||||
for i := 0; i < objT.NumField(); i++ {
|
||||
fieldV := objV.Field(i)
|
||||
if !fieldV.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldT := objT.Field(i)
|
||||
if fieldT.Anonymous && fieldT.Type.Kind() == reflect.Struct {
|
||||
err := parseFormToStruct(form, fieldT.Type, fieldV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
tag, ok := formTagName(fieldT)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
value, ok := formValue(tag, form, fieldT)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch fieldT.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
b, err := parseFormBoolValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetBool(b)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetInt(x)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
x, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetUint(x)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
x, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetFloat(x)
|
||||
case reflect.Interface:
|
||||
fieldV.Set(reflect.ValueOf(value))
|
||||
case reflect.String:
|
||||
fieldV.SetString(value)
|
||||
case reflect.Struct:
|
||||
if fieldT.Type.String() == "time.Time" {
|
||||
t, err := parseFormTime(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.Set(reflect.ValueOf(t))
|
||||
}
|
||||
case reflect.Slice:
|
||||
if fieldT.Type == sliceOfInts {
|
||||
formVals := form[tag]
|
||||
fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(int(1))), len(formVals), len(formVals)))
|
||||
for i := 0; i < len(formVals); i++ {
|
||||
val, err := strconv.Atoi(formVals[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.Index(i).SetInt(int64(val))
|
||||
}
|
||||
} else if fieldT.Type == sliceOfStrings {
|
||||
formVals := form[tag]
|
||||
fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), len(formVals), len(formVals)))
|
||||
for i := 0; i < len(formVals); i++ {
|
||||
fieldV.Index(i).SetString(formVals[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint
|
||||
func parseFormTime(value string) (time.Time, error) {
|
||||
var pattern string
|
||||
if len(value) >= 25 {
|
||||
value = value[:25]
|
||||
pattern = time.RFC3339
|
||||
} else if strings.HasSuffix(strings.ToUpper(value), "Z") {
|
||||
pattern = time.RFC3339
|
||||
} else if len(value) >= 19 {
|
||||
if strings.Contains(value, "T") {
|
||||
pattern = formatDateTimeT
|
||||
} else {
|
||||
pattern = formatDateTime
|
||||
}
|
||||
value = value[:19]
|
||||
} else if len(value) >= 10 {
|
||||
if len(value) > 10 {
|
||||
value = value[:10]
|
||||
}
|
||||
pattern = formatDate
|
||||
} else if len(value) >= 8 {
|
||||
if len(value) > 8 {
|
||||
value = value[:8]
|
||||
}
|
||||
pattern = formatTime
|
||||
}
|
||||
return time.ParseInLocation(pattern, value, time.Local)
|
||||
}
|
||||
|
||||
func parseFormBoolValue(value string) (bool, error) {
|
||||
if strings.ToLower(value) == "on" || strings.ToLower(value) == "1" || strings.ToLower(value) == "yes" {
|
||||
return true, nil
|
||||
}
|
||||
if strings.ToLower(value) == "off" || strings.ToLower(value) == "0" || strings.ToLower(value) == "no" {
|
||||
return false, nil
|
||||
}
|
||||
return strconv.ParseBool(value)
|
||||
}
|
||||
|
||||
// nolint
|
||||
func formTagName(fieldT reflect.StructField) (string, bool) {
|
||||
tags := strings.Split(fieldT.Tag.Get("form"), ",")
|
||||
var tag string
|
||||
if len(tags) == 0 || tags[0] == "" {
|
||||
tag = fieldT.Name
|
||||
} else if tags[0] == "-" {
|
||||
return "", false
|
||||
} else {
|
||||
tag = tags[0]
|
||||
}
|
||||
return tag, true
|
||||
}
|
||||
|
||||
func formValue(tag string, form url.Values, fieldT reflect.StructField) (string, bool) {
|
||||
formValues := form[tag]
|
||||
var value string
|
||||
if len(formValues) == 0 {
|
||||
defaultValue := fieldT.Tag.Get("default")
|
||||
if defaultValue != "" {
|
||||
value = defaultValue
|
||||
} else {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
if len(formValues) == 1 {
|
||||
value = formValues[0]
|
||||
if value == "" {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
@ -31,7 +31,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// BeegoOutput does work for sending response header.
|
||||
@ -154,7 +155,7 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface
|
||||
fmt.Fprintf(&b, "; HttpOnly")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// default empty
|
||||
if len(others) > 5 {
|
||||
if v, ok := others[5].(string); ok && len(v) > 0 {
|
||||
@ -224,6 +225,19 @@ func (output *BeegoOutput) YAML(data interface{}) error {
|
||||
return output.Body(content)
|
||||
}
|
||||
|
||||
// Proto writes protobuf to the response body.
|
||||
func (output *BeegoOutput) Proto(data proto.Message) error {
|
||||
output.Header("Content-Type", "application/x-protobuf; charset=utf-8")
|
||||
var content []byte
|
||||
var err error
|
||||
content, err = proto.Marshal(data)
|
||||
if err != nil {
|
||||
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
return output.Body(content)
|
||||
}
|
||||
|
||||
// JSONP writes jsonp to the response body.
|
||||
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
||||
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
||||
|
||||
@ -17,8 +17,6 @@ package web
|
||||
import (
|
||||
"bytes"
|
||||
context2 "context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@ -32,8 +30,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"gopkg.in/yaml.v2"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/context"
|
||||
"github.com/beego/beego/v2/server/web/context/param"
|
||||
@ -244,49 +241,35 @@ func (c *Controller) HandlerFunc(fnname string) bool {
|
||||
// URLMapping register the internal Controller router.
|
||||
func (c *Controller) URLMapping() {}
|
||||
|
||||
// Bind if the content type is form, we read data from form
|
||||
// otherwise, read data from request body
|
||||
func (c *Controller) Bind(obj interface{}) error {
|
||||
ct, exist := c.Ctx.Request.Header["Content-Type"]
|
||||
if !exist || len(ct) == 0 {
|
||||
return c.BindJSON(obj)
|
||||
}
|
||||
i, l := 0, len(ct[0])
|
||||
for i < l && ct[0][i] != ';' {
|
||||
i++
|
||||
}
|
||||
switch ct[0][0:i] {
|
||||
case "application/json":
|
||||
return c.BindJSON(obj)
|
||||
case "application/xml", "text/xml":
|
||||
return c.BindXML(obj)
|
||||
case "application/x-www-form-urlencoded":
|
||||
return c.BindForm(obj)
|
||||
case "application/x-protobuf":
|
||||
return c.BindProtobuf(obj)
|
||||
case "application/x-yaml":
|
||||
return c.BindYAML(obj)
|
||||
default:
|
||||
return errors.New("Unsupported Content-Type:" + ct[0])
|
||||
}
|
||||
return c.Ctx.Bind(obj)
|
||||
}
|
||||
|
||||
// BindYAML only read data from http request body
|
||||
func (c *Controller) BindYAML(obj interface{}) error {
|
||||
return yaml.Unmarshal(c.Ctx.Input.RequestBody, obj)
|
||||
return c.Ctx.BindYAML(obj)
|
||||
}
|
||||
|
||||
// BindForm read data from form
|
||||
func (c *Controller) BindForm(obj interface{}) error {
|
||||
return c.ParseForm(obj)
|
||||
return c.Ctx.BindForm(obj)
|
||||
}
|
||||
|
||||
// BindJSON only read data from http request body
|
||||
func (c *Controller) BindJSON(obj interface{}) error {
|
||||
return json.Unmarshal(c.Ctx.Input.RequestBody, obj)
|
||||
return c.Ctx.BindJSON(obj)
|
||||
}
|
||||
|
||||
func (c *Controller) BindProtobuf(obj interface{}) error {
|
||||
return proto.Unmarshal(c.Ctx.Input.RequestBody, obj.(proto.Message))
|
||||
// BindProtobuf only read data from http request body
|
||||
func (c *Controller) BindProtobuf(obj proto.Message) error {
|
||||
return c.Ctx.BindProtobuf(obj)
|
||||
}
|
||||
|
||||
// BindXML only read data from http request body
|
||||
func (c *Controller) BindXML(obj interface{}) error {
|
||||
return xml.Unmarshal(c.Ctx.Input.RequestBody, obj)
|
||||
return c.Ctx.BindXML(obj)
|
||||
}
|
||||
|
||||
// Mapping the method to function
|
||||
@ -440,35 +423,23 @@ func (c *Controller) URLFor(endpoint string, values ...interface{}) string {
|
||||
}
|
||||
|
||||
func (c *Controller) JSONResp(data interface{}) error {
|
||||
c.Data["json"] = data
|
||||
return c.ServeJSON()
|
||||
return c.Ctx.JSONResp(data)
|
||||
}
|
||||
|
||||
func (c *Controller) XMLResp(data interface{}) error {
|
||||
c.Data["xml"] = data
|
||||
return c.ServeXML()
|
||||
return c.Ctx.XMLResp(data)
|
||||
}
|
||||
|
||||
func (c *Controller) YamlResp(data interface{}) error {
|
||||
c.Data["yaml"] = data
|
||||
return c.ServeYAML()
|
||||
return c.Ctx.YamlResp(data)
|
||||
}
|
||||
|
||||
// Resp sends response based on the Accept Header
|
||||
// By default response will be in JSON
|
||||
// it's different from ServeXXX methods
|
||||
// because we don't store the data to Data field
|
||||
func (c *Controller) Resp(data interface{}) error {
|
||||
accept := c.Ctx.Input.Header("Accept")
|
||||
switch accept {
|
||||
case context.ApplicationYAML:
|
||||
c.Data["yaml"] = data
|
||||
return c.ServeYAML()
|
||||
case context.ApplicationXML, context.TextXML:
|
||||
c.Data["xml"] = data
|
||||
return c.ServeXML()
|
||||
default:
|
||||
c.Data["json"] = data
|
||||
return c.ServeJSON()
|
||||
}
|
||||
return c.Ctx.Resp(data)
|
||||
}
|
||||
|
||||
// ServeJSON sends a json response with encoding charset.
|
||||
@ -518,11 +489,7 @@ func (c *Controller) Input() (url.Values, error) {
|
||||
|
||||
// ParseForm maps input data map to obj struct.
|
||||
func (c *Controller) ParseForm(obj interface{}) error {
|
||||
form, err := c.Input()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ParseForm(form, obj)
|
||||
return c.Ctx.BindForm(obj)
|
||||
}
|
||||
|
||||
// GetString returns the input value by key string or the default value while it's present and input is blank
|
||||
|
||||
@ -269,10 +269,10 @@ type respTestCase struct {
|
||||
func TestControllerResp(t *testing.T) {
|
||||
// test cases
|
||||
tcs := []respTestCase{
|
||||
{Accept: context.ApplicationJSON, ExpectedContentLength: 18, ExpectedResponse: "{\n \"foo\": \"bar\"\n}"},
|
||||
{Accept: context.ApplicationXML, ExpectedContentLength: 25, ExpectedResponse: "<S>\n <foo>bar</foo>\n</S>"},
|
||||
{Accept: context.ApplicationJSON, ExpectedContentLength: 13, ExpectedResponse: `{"foo":"bar"}`},
|
||||
{Accept: context.ApplicationXML, ExpectedContentLength: 21, ExpectedResponse: `<S><foo>bar</foo></S>`},
|
||||
{Accept: context.ApplicationYAML, ExpectedContentLength: 9, ExpectedResponse: "foo: bar\n"},
|
||||
{Accept: "OTHER", ExpectedContentLength: 18, ExpectedResponse: "{\n \"foo\": \"bar\"\n}"},
|
||||
{Accept: "OTHER", ExpectedContentLength: 13, ExpectedResponse: `{"foo":"bar"}`},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
|
||||
@ -25,13 +25,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
formatTime = "15:04:05"
|
||||
formatDate = "2006-01-02"
|
||||
formatDateTime = "2006-01-02 15:04:05"
|
||||
formatDateTimeT = "2006-01-02T15:04:05"
|
||||
"github.com/beego/beego/v2/server/web/context"
|
||||
)
|
||||
|
||||
// Substr returns the substr from start to length.
|
||||
@ -266,165 +261,11 @@ func AssetsCSS(text string) template.HTML {
|
||||
return template.HTML(text)
|
||||
}
|
||||
|
||||
// ParseForm will parse form values to struct via tag.
|
||||
// Support for anonymous struct.
|
||||
func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) error {
|
||||
for i := 0; i < objT.NumField(); i++ {
|
||||
fieldV := objV.Field(i)
|
||||
if !fieldV.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldT := objT.Field(i)
|
||||
if fieldT.Anonymous && fieldT.Type.Kind() == reflect.Struct {
|
||||
err := parseFormToStruct(form, fieldT.Type, fieldV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
tags := strings.Split(fieldT.Tag.Get("form"), ",")
|
||||
var tag string
|
||||
if len(tags) == 0 || len(tags[0]) == 0 {
|
||||
tag = fieldT.Name
|
||||
} else if tags[0] == "-" {
|
||||
continue
|
||||
} else {
|
||||
tag = tags[0]
|
||||
}
|
||||
|
||||
formValues := form[tag]
|
||||
var value string
|
||||
if len(formValues) == 0 {
|
||||
defaultValue := fieldT.Tag.Get("default")
|
||||
if defaultValue != "" {
|
||||
value = defaultValue
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(formValues) == 1 {
|
||||
value = formValues[0]
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch fieldT.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
if strings.ToLower(value) == "on" || strings.ToLower(value) == "1" || strings.ToLower(value) == "yes" {
|
||||
fieldV.SetBool(true)
|
||||
continue
|
||||
}
|
||||
if strings.ToLower(value) == "off" || strings.ToLower(value) == "0" || strings.ToLower(value) == "no" {
|
||||
fieldV.SetBool(false)
|
||||
continue
|
||||
}
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetBool(b)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetInt(x)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
x, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetUint(x)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
x, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.SetFloat(x)
|
||||
case reflect.Interface:
|
||||
fieldV.Set(reflect.ValueOf(value))
|
||||
case reflect.String:
|
||||
fieldV.SetString(value)
|
||||
case reflect.Struct:
|
||||
switch fieldT.Type.String() {
|
||||
case "time.Time":
|
||||
var (
|
||||
t time.Time
|
||||
err error
|
||||
)
|
||||
if len(value) >= 25 {
|
||||
value = value[:25]
|
||||
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
|
||||
} else if strings.HasSuffix(strings.ToUpper(value), "Z") {
|
||||
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
|
||||
} else if len(value) >= 19 {
|
||||
if strings.Contains(value, "T") {
|
||||
value = value[:19]
|
||||
t, err = time.ParseInLocation(formatDateTimeT, value, time.Local)
|
||||
} else {
|
||||
value = value[:19]
|
||||
t, err = time.ParseInLocation(formatDateTime, value, time.Local)
|
||||
}
|
||||
} else if len(value) >= 10 {
|
||||
if len(value) > 10 {
|
||||
value = value[:10]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatDate, value, time.Local)
|
||||
} else if len(value) >= 8 {
|
||||
if len(value) > 8 {
|
||||
value = value[:8]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatTime, value, time.Local)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.Set(reflect.ValueOf(t))
|
||||
}
|
||||
case reflect.Slice:
|
||||
if fieldT.Type == sliceOfInts {
|
||||
formVals := form[tag]
|
||||
fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(int(1))), len(formVals), len(formVals)))
|
||||
for i := 0; i < len(formVals); i++ {
|
||||
val, err := strconv.Atoi(formVals[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldV.Index(i).SetInt(int64(val))
|
||||
}
|
||||
} else if fieldT.Type == sliceOfStrings {
|
||||
formVals := form[tag]
|
||||
fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), len(formVals), len(formVals)))
|
||||
for i := 0; i < len(formVals); i++ {
|
||||
fieldV.Index(i).SetString(formVals[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseForm will parse form values to struct via tag.
|
||||
func ParseForm(form url.Values, obj interface{}) error {
|
||||
objT := reflect.TypeOf(obj)
|
||||
objV := reflect.ValueOf(obj)
|
||||
if !isStructPtr(objT) {
|
||||
return fmt.Errorf("%v must be a struct pointer", obj)
|
||||
}
|
||||
objT = objT.Elem()
|
||||
objV = objV.Elem()
|
||||
|
||||
return parseFormToStruct(form, objT, objV)
|
||||
return context.ParseForm(form, obj)
|
||||
}
|
||||
|
||||
var (
|
||||
sliceOfInts = reflect.TypeOf([]int(nil))
|
||||
sliceOfStrings = reflect.TypeOf([]string(nil))
|
||||
)
|
||||
|
||||
var unKind = map[reflect.Kind]bool{
|
||||
reflect.Uintptr: true,
|
||||
reflect.Complex64: true,
|
||||
@ -441,10 +282,11 @@ var unKind = map[reflect.Kind]bool{
|
||||
|
||||
// RenderForm will render object to form html.
|
||||
// obj must be a struct pointer.
|
||||
// nolint
|
||||
func RenderForm(obj interface{}) template.HTML {
|
||||
objT := reflect.TypeOf(obj)
|
||||
objV := reflect.ValueOf(obj)
|
||||
if !isStructPtr(objT) {
|
||||
if objT.Kind() != reflect.Ptr || objT.Elem().Kind() != reflect.Struct {
|
||||
return template.HTML("")
|
||||
}
|
||||
objT = objT.Elem()
|
||||
@ -549,10 +391,6 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str
|
||||
return
|
||||
}
|
||||
|
||||
func isStructPtr(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// go1.2 added template funcs. begin
|
||||
var (
|
||||
errBadComparisonType = errors.New("invalid type for comparison")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user