Merge pull request #4718 from flycash/refactor-ctx
Refator: Remove methods to web.context.Context
This commit is contained in:
commit
419e395d78
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