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/
|
test/tmp/
|
||||||
core/config/env/pkg/
|
core/config/env/pkg/
|
||||||
|
|
||||||
|
my save path/
|
||||||
|
|
||||||
profile.out
|
profile.out
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
- Deprecated BeeMap and replace all usage with `sync.map` [4616](https://github.com/beego/beego/pull/4616)
|
- 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)
|
- 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)
|
- 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 bug:reflect.ValueOf(nil) in getFlatParams [4715](https://github.com/beego/beego/pull/4715)
|
||||||
|
|
||||||
## Fix Sonar
|
## Fix Sonar
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -36,5 +36,7 @@ require (
|
|||||||
go.etcd.io/etcd/client/v3 v3.5.0
|
go.etcd.io/etcd/client/v3 v3.5.0
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||||
google.golang.org/grpc v1.38.0
|
google.golang.org/grpc v1.38.0
|
||||||
|
google.golang.org/protobuf v1.26.0
|
||||||
gopkg.in/yaml.v2 v2.4.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/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/beego/beego/v2/core/utils"
|
"github.com/beego/beego/v2/core/utils"
|
||||||
"github.com/beego/beego/v2/server/web/session"
|
"github.com/beego/beego/v2/server/web/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commonly used mime-types
|
// Commonly used mime-types
|
||||||
const (
|
const (
|
||||||
ApplicationJSON = "application/json"
|
ApplicationJSON = "application/json"
|
||||||
ApplicationXML = "application/xml"
|
ApplicationXML = "application/xml"
|
||||||
ApplicationYAML = "application/x-yaml"
|
ApplicationForm = "application/x-www-form-urlencoded"
|
||||||
TextXML = "text/xml"
|
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
|
// NewContext return the Context with Input and Output
|
||||||
@ -65,6 +79,108 @@ type Context struct {
|
|||||||
_xsrfToken string
|
_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
|
// Reset initializes Context, BeegoInput and BeegoOutput
|
||||||
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
|
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx.Request = r
|
ctx.Request = r
|
||||||
@ -91,7 +207,7 @@ func (ctx *Context) Abort(status int, body string) {
|
|||||||
|
|
||||||
// WriteString writes a string to response body.
|
// WriteString writes a string to response body.
|
||||||
func (ctx *Context) WriteString(content string) {
|
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.
|
// 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]
|
sig := parts[2]
|
||||||
|
|
||||||
h := hmac.New(sha256.New, []byte(Secret))
|
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 {
|
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
|
||||||
return "", false
|
return "", false
|
||||||
@ -138,7 +257,7 @@ func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interf
|
|||||||
vs := base64.URLEncoding.EncodeToString([]byte(value))
|
vs := base64.URLEncoding.EncodeToString([]byte(value))
|
||||||
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
|
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
h := hmac.New(sha256.New, []byte(Secret))
|
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))
|
sig := fmt.Sprintf("%02x", h.Sum(nil))
|
||||||
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
|
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
|
||||||
ctx.Output.Cookie(name, cookie, others...)
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
"google.golang.org/protobuf/proto"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeegoOutput does work for sending response header.
|
// 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")
|
fmt.Fprintf(&b, "; HttpOnly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// default empty
|
// default empty
|
||||||
if len(others) > 5 {
|
if len(others) > 5 {
|
||||||
if v, ok := others[5].(string); ok && len(v) > 0 {
|
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)
|
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.
|
// JSONP writes jsonp to the response body.
|
||||||
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
||||||
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
||||||
|
|||||||
@ -17,8 +17,6 @@ package web
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
context2 "context"
|
context2 "context"
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
@ -32,8 +30,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/beego/beego/v2/server/web/context"
|
"github.com/beego/beego/v2/server/web/context"
|
||||||
"github.com/beego/beego/v2/server/web/context/param"
|
"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.
|
// URLMapping register the internal Controller router.
|
||||||
func (c *Controller) URLMapping() {}
|
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 {
|
func (c *Controller) Bind(obj interface{}) error {
|
||||||
ct, exist := c.Ctx.Request.Header["Content-Type"]
|
return c.Ctx.Bind(obj)
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindYAML only read data from http request body
|
||||||
func (c *Controller) BindYAML(obj interface{}) error {
|
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 {
|
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 {
|
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 {
|
// BindProtobuf only read data from http request body
|
||||||
return proto.Unmarshal(c.Ctx.Input.RequestBody, obj.(proto.Message))
|
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 {
|
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
|
// 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 {
|
func (c *Controller) JSONResp(data interface{}) error {
|
||||||
c.Data["json"] = data
|
return c.Ctx.JSONResp(data)
|
||||||
return c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) XMLResp(data interface{}) error {
|
func (c *Controller) XMLResp(data interface{}) error {
|
||||||
c.Data["xml"] = data
|
return c.Ctx.XMLResp(data)
|
||||||
return c.ServeXML()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) YamlResp(data interface{}) error {
|
func (c *Controller) YamlResp(data interface{}) error {
|
||||||
c.Data["yaml"] = data
|
return c.Ctx.YamlResp(data)
|
||||||
return c.ServeYAML()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resp sends response based on the Accept Header
|
// Resp sends response based on the Accept Header
|
||||||
// By default response will be in JSON
|
// 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 {
|
func (c *Controller) Resp(data interface{}) error {
|
||||||
accept := c.Ctx.Input.Header("Accept")
|
return c.Ctx.Resp(data)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeJSON sends a json response with encoding charset.
|
// 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.
|
// ParseForm maps input data map to obj struct.
|
||||||
func (c *Controller) ParseForm(obj interface{}) error {
|
func (c *Controller) ParseForm(obj interface{}) error {
|
||||||
form, err := c.Input()
|
return c.Ctx.BindForm(obj)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ParseForm(form, obj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString returns the input value by key string or the default value while it's present and input is blank
|
// 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) {
|
func TestControllerResp(t *testing.T) {
|
||||||
// test cases
|
// test cases
|
||||||
tcs := []respTestCase{
|
tcs := []respTestCase{
|
||||||
{Accept: context.ApplicationJSON, ExpectedContentLength: 18, ExpectedResponse: "{\n \"foo\": \"bar\"\n}"},
|
{Accept: context.ApplicationJSON, ExpectedContentLength: 13, ExpectedResponse: `{"foo":"bar"}`},
|
||||||
{Accept: context.ApplicationXML, ExpectedContentLength: 25, ExpectedResponse: "<S>\n <foo>bar</foo>\n</S>"},
|
{Accept: context.ApplicationXML, ExpectedContentLength: 21, ExpectedResponse: `<S><foo>bar</foo></S>`},
|
||||||
{Accept: context.ApplicationYAML, ExpectedContentLength: 9, ExpectedResponse: "foo: bar\n"},
|
{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 {
|
for _, tc := range tcs {
|
||||||
|
|||||||
@ -25,13 +25,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
"github.com/beego/beego/v2/server/web/context"
|
||||||
formatTime = "15:04:05"
|
|
||||||
formatDate = "2006-01-02"
|
|
||||||
formatDateTime = "2006-01-02 15:04:05"
|
|
||||||
formatDateTimeT = "2006-01-02T15:04:05"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Substr returns the substr from start to length.
|
// Substr returns the substr from start to length.
|
||||||
@ -266,165 +261,11 @@ func AssetsCSS(text string) template.HTML {
|
|||||||
return template.HTML(text)
|
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.
|
// ParseForm will parse form values to struct via tag.
|
||||||
func ParseForm(form url.Values, obj interface{}) error {
|
func ParseForm(form url.Values, obj interface{}) error {
|
||||||
objT := reflect.TypeOf(obj)
|
return context.ParseForm(form, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
sliceOfInts = reflect.TypeOf([]int(nil))
|
|
||||||
sliceOfStrings = reflect.TypeOf([]string(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
var unKind = map[reflect.Kind]bool{
|
var unKind = map[reflect.Kind]bool{
|
||||||
reflect.Uintptr: true,
|
reflect.Uintptr: true,
|
||||||
reflect.Complex64: true,
|
reflect.Complex64: true,
|
||||||
@ -441,10 +282,11 @@ var unKind = map[reflect.Kind]bool{
|
|||||||
|
|
||||||
// RenderForm will render object to form html.
|
// RenderForm will render object to form html.
|
||||||
// obj must be a struct pointer.
|
// obj must be a struct pointer.
|
||||||
|
// nolint
|
||||||
func RenderForm(obj interface{}) template.HTML {
|
func RenderForm(obj interface{}) template.HTML {
|
||||||
objT := reflect.TypeOf(obj)
|
objT := reflect.TypeOf(obj)
|
||||||
objV := reflect.ValueOf(obj)
|
objV := reflect.ValueOf(obj)
|
||||||
if !isStructPtr(objT) {
|
if objT.Kind() != reflect.Ptr || objT.Elem().Kind() != reflect.Struct {
|
||||||
return template.HTML("")
|
return template.HTML("")
|
||||||
}
|
}
|
||||||
objT = objT.Elem()
|
objT = objT.Elem()
|
||||||
@ -549,10 +391,6 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func isStructPtr(t reflect.Type) bool {
|
|
||||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
|
||||||
}
|
|
||||||
|
|
||||||
// go1.2 added template funcs. begin
|
// go1.2 added template funcs. begin
|
||||||
var (
|
var (
|
||||||
errBadComparisonType = errors.New("invalid type for comparison")
|
errBadComparisonType = errors.New("invalid type for comparison")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user