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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user