Skip to content

Commit 8eebf1b

Browse files
authored
Merge pull request #13 from go-spring-projects/fix-scope-param
Ignore non-existent parameters when binding parameters
2 parents ab3b8c6 + 7781a3c commit 8eebf1b

File tree

7 files changed

+110
-84
lines changed

7 files changed

+110
-84
lines changed

web/bind.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package web
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"net/http"
2223
"reflect"
@@ -165,21 +166,21 @@ func validMappingFunc(fnType reflect.Type) error {
165166
}
166167

167168
if fnType.NumIn() < 1 || fnType.NumIn() > 2 {
168-
return fmt.Errorf("%s: invalid input parameter count", fnType.String())
169+
return fmt.Errorf("%s: expect func(ctx context.Context, [T]) [R, error]", fnType.String())
169170
}
170171

171172
if fnType.NumOut() > 2 {
172-
return fmt.Errorf("%s: invalid output parameter count", fnType.String())
173+
return fmt.Errorf("%s: expect func(ctx context.Context, [T]) [(R, error)]", fnType.String())
173174
}
174175

175176
if !utils.IsContextType(fnType.In(0)) {
176-
return fmt.Errorf("%s: first input param type (%s) must be context", fnType.String(), fnType.In(0).String())
177+
return fmt.Errorf("%s: expect func(ctx context.Context, [T]) [(R, error)", fnType.String())
177178
}
178179

179180
if fnType.NumIn() > 1 {
180181
argType := fnType.In(1)
181182
if !(reflect.Struct == argType.Kind() || (reflect.Ptr == argType.Kind() && reflect.Struct == argType.Elem().Kind())) {
182-
return fmt.Errorf("%s: second input param type (%s) must be struct/*struct", fnType.String(), argType.String())
183+
return fmt.Errorf("%s: input param type (%s) must be struct/*struct", fnType.String(), argType.String())
183184
}
184185
}
185186

@@ -188,11 +189,11 @@ func validMappingFunc(fnType reflect.Type) error {
188189
case 1: // R | error
189190
case 2: // (R, error)
190191
if utils.IsErrorType(fnType.Out(0)) {
191-
return fmt.Errorf("%s: first output param type not be error", fnType.String())
192+
return fmt.Errorf("%s: expect func(...) (R, error)", fnType.String())
192193
}
193194

194195
if !utils.IsErrorType(fnType.Out(1)) {
195-
return fmt.Errorf("%s: second output type (%s) must a error", fnType.String(), fnType.Out(1).String())
196+
return fmt.Errorf("%s: expect func(...) (R, error)", fnType.String())
196197
}
197198
}
198199

@@ -210,3 +211,31 @@ func requestWithCtx(r *http.Request, webCtx *Context) *http.Request {
210211
ctx := WithContext(r.Context(), webCtx)
211212
return r.WithContext(ctx)
212213
}
214+
215+
func defaultJsonRender(ctx *Context, err error, result interface{}) {
216+
217+
var code = 0
218+
var message = ""
219+
if nil != err {
220+
var e HttpError
221+
if errors.As(err, &e) {
222+
code = e.Code
223+
message = e.Message
224+
} else {
225+
code = http.StatusInternalServerError
226+
message = err.Error()
227+
228+
if errors.Is(err, binding.ErrBinding) || errors.Is(err, binding.ErrValidate) {
229+
code = http.StatusBadRequest
230+
}
231+
}
232+
}
233+
234+
type response struct {
235+
Code int `json:"code"`
236+
Message string `json:"message,omitempty"`
237+
Data interface{} `json:"data"`
238+
}
239+
240+
ctx.JSON(http.StatusOK, response{Code: code, Message: message, Data: result})
241+
}

web/binding/binding.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ const (
4444

4545
type Request interface {
4646
ContentType() string
47-
Header(key string) string
48-
Cookie(name string) string
49-
PathParam(name string) string
50-
QueryParam(name string) string
47+
Header(key string) (string, bool)
48+
Cookie(name string) (string, bool)
49+
PathParam(name string) (string, bool)
50+
QueryParam(name string) (string, bool)
5151
FormParams() (url.Values, error)
5252
MultipartParams(maxMemory int64) (*multipart.Form, error)
5353
RequestBody() io.Reader
@@ -70,7 +70,7 @@ var scopeTags = map[BindScope]string{
7070
BindScopeCookie: "cookie",
7171
}
7272

73-
var scopeGetters = map[BindScope]func(r Request, name string) string{
73+
var scopeGetters = map[BindScope]func(r Request, name string) (string, bool){
7474
BindScopeURI: Request.PathParam,
7575
BindScopeQuery: Request.QueryParam,
7676
BindScopeHeader: Request.Header,
@@ -95,6 +95,11 @@ func RegisterBodyBinder(mime string, binder BodyBinder) {
9595
bodyBinders[mime] = binder
9696
}
9797

98+
// Bind checks the Method and Content-Type to select a binding engine automatically,
99+
// Depending on the "Content-Type" header different bindings are used, for example:
100+
//
101+
// "application/json" --> JSON binding
102+
// "application/xml" --> XML binding
98103
func Bind(i interface{}, r Request) error {
99104
if err := bindScope(i, r); err != nil {
100105
return fmt.Errorf("%w: %v", ErrBinding, err)
@@ -138,8 +143,7 @@ func bindScope(i interface{}, r Request) error {
138143
fv := ev.Field(j)
139144
ft := et.Field(j)
140145
for scope := BindScopeURI; scope < BindScopeBody; scope++ {
141-
err := bindScopeField(scope, fv, ft, r)
142-
if err != nil {
146+
if err := bindScopeField(scope, fv, ft, r); err != nil {
143147
return err
144148
}
145149
}
@@ -149,14 +153,11 @@ func bindScope(i interface{}, r Request) error {
149153

150154
func bindScopeField(scope BindScope, v reflect.Value, field reflect.StructField, r Request) error {
151155
if tag, loaded := scopeTags[scope]; loaded {
152-
if name, ok := field.Tag.Lookup(tag); ok {
153-
if name == "-" {
154-
return nil // ignore bind
155-
}
156-
val := scopeGetters[scope](r, name)
157-
err := bindData(v, val)
158-
if err != nil {
159-
return err
156+
if name, ok := field.Tag.Lookup(tag); ok && name != "-" {
157+
if val, exists := scopeGetters[scope](r, name); exists {
158+
if err := bindData(v, val); err != nil {
159+
return err
160+
}
160161
}
161162
}
162163
}

web/binding/binding_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,24 @@ func (r *MockRequest) ContentType() string {
4444
return r.contentType
4545
}
4646

47-
func (r *MockRequest) Header(key string) string {
48-
return r.headers[key]
47+
func (r *MockRequest) Header(key string) (string, bool) {
48+
value, ok := r.headers[key]
49+
return value, ok
4950
}
5051

51-
func (r *MockRequest) Cookie(name string) string {
52-
return r.cookies[name]
52+
func (r *MockRequest) Cookie(name string) (string, bool) {
53+
value, ok := r.cookies[name]
54+
return value, ok
5355
}
5456

55-
func (r *MockRequest) QueryParam(name string) string {
56-
return r.queryParams[name]
57+
func (r *MockRequest) QueryParam(name string) (string, bool) {
58+
value, ok := r.queryParams[name]
59+
return value, ok
5760
}
5861

59-
func (r *MockRequest) PathParam(name string) string {
60-
return r.pathParams[name]
62+
func (r *MockRequest) PathParam(name string) (string, bool) {
63+
value, ok := r.pathParams[name]
64+
return value, ok
6165
}
6266

6367
func (r *MockRequest) FormParams() (url.Values, error) {

web/context.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"mime/multipart"
2424
"net"
2525
"net/http"
26+
"net/textproto"
2627
"net/url"
2728
"strings"
2829
"unicode"
@@ -71,38 +72,43 @@ func (c *Context) ContentType() string {
7172
}
7273

7374
// Header returns the named header in the request.
74-
func (c *Context) Header(key string) string {
75-
return c.Request.Header.Get(key)
75+
func (c *Context) Header(key string) (string, bool) {
76+
if values, ok := c.Request.Header[textproto.CanonicalMIMEHeaderKey(key)]; ok && len(values) > 0 {
77+
return values[0], true
78+
}
79+
return "", false
7680
}
7781

7882
// Cookie returns the named cookie provided in the request.
79-
func (c *Context) Cookie(name string) string {
83+
func (c *Context) Cookie(name string) (string, bool) {
8084
cookie, err := c.Request.Cookie(name)
8185
if err != nil {
82-
return ""
86+
return "", false
87+
}
88+
if val, err := url.QueryUnescape(cookie.Value); nil == err {
89+
return val, true
8390
}
84-
val, _ := url.QueryUnescape(cookie.Value)
85-
return val
91+
return cookie.Value, true
8692
}
8793

8894
// PathParam returns the named variables in the request.
89-
func (c *Context) PathParam(name string) string {
95+
func (c *Context) PathParam(name string) (string, bool) {
9096
if params := mux.Vars(c.Request); nil != params {
9197
if value, ok := params[name]; ok {
92-
return value
98+
return value, true
9399
}
94100
}
95-
return ""
101+
return "", false
96102
}
97103

98104
// QueryParam returns the named query in the request.
99-
func (c *Context) QueryParam(name string) string {
105+
func (c *Context) QueryParam(name string) (string, bool) {
100106
if values := c.Request.URL.Query(); nil != values {
101107
if value, ok := values[name]; ok && len(value) > 0 {
102-
return value[0]
108+
return value[0], true
103109
}
104110
}
105-
return ""
111+
return "", false
106112
}
107113

108114
// FormParams returns the form in the request.
@@ -183,6 +189,15 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string,
183189
})
184190
}
185191

192+
// Bind checks the Method and Content-Type to select a binding engine automatically,
193+
// Depending on the "Content-Type" header different bindings are used, for example:
194+
//
195+
// "application/json" --> JSON binding
196+
// "application/xml" --> XML binding
197+
func (c *Context) Bind(r interface{}) error {
198+
return binding.Bind(r, c)
199+
}
200+
186201
// Render writes the response headers and calls render.Render to render data.
187202
func (c *Context) Render(code int, render render.Renderer) error {
188203
if code > 0 {

web/examples/greeting/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ func (g *Greeting) OnInit(ctx context.Context) error {
4343
g.Server.Use(func(handler http.Handler) http.Handler {
4444

4545
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
46-
4746
start := time.Now()
4847
handler.ServeHTTP(writer, request)
4948
g.Logger.Info("http handle cost",

web/options.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package web
1818

19-
import "time"
19+
import (
20+
"crypto/tls"
21+
"time"
22+
)
2023

2124
type Options struct {
2225
// Addr optionally specifies the TCP address for the server to listen on,
@@ -74,3 +77,15 @@ type Options struct {
7477
// If zero, DefaultMaxHeaderBytes is used.
7578
MaxHeaderBytes int `value:"${max-header-bytes:=0}"`
7679
}
80+
81+
func (options Options) IsTls() bool {
82+
return len(options.CertFile) > 0 && len(options.KeyFile) > 0
83+
}
84+
85+
func (options Options) GetCertificate(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
86+
cert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
87+
if err != nil {
88+
return nil, err
89+
}
90+
return &cert, nil
91+
}

web/server.go

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ package web
1919
import (
2020
"context"
2121
"crypto/tls"
22-
"errors"
2322
"net/http"
24-
25-
"github.com/go-spring-projects/go-spring/web/binding"
2623
)
2724

2825
// A Server defines parameters for running an HTTP server.
@@ -42,53 +39,18 @@ func NewServer(router *Router, options Options) *Server {
4239
}
4340

4441
var tlsConfig *tls.Config
45-
if len(options.CertFile) > 0 && len(options.KeyFile) > 0 {
42+
if options.IsTls() {
4643
tlsConfig = &tls.Config{
47-
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
48-
cert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
49-
if err != nil {
50-
return nil, err
51-
}
52-
return &cert, nil
53-
},
54-
}
55-
}
56-
57-
var jsonRenderer = func(ctx *Context, err error, result interface{}) {
58-
59-
var code = 0
60-
var message = ""
61-
if nil != err {
62-
var e HttpError
63-
if errors.As(err, &e) {
64-
code = e.Code
65-
message = e.Message
66-
} else {
67-
code = http.StatusInternalServerError
68-
message = err.Error()
69-
70-
if errors.Is(err, binding.ErrBinding) || errors.Is(err, binding.ErrValidate) {
71-
code = http.StatusBadRequest
72-
}
73-
}
44+
GetCertificate: options.GetCertificate,
7445
}
75-
76-
type response struct {
77-
Code int `json:"code"`
78-
Message string `json:"message,omitempty"`
79-
Data interface{} `json:"data"`
80-
}
81-
82-
ctx.JSON(http.StatusOK, response{Code: code, Message: message, Data: result})
8346
}
8447

8548
return &Server{
8649
options: options,
8750
router: router,
88-
renderer: RendererFunc(jsonRenderer),
51+
renderer: RendererFunc(defaultJsonRender),
8952
httpSvr: &http.Server{
9053
Addr: addr,
91-
Handler: router,
9254
TLSConfig: tlsConfig,
9355
ReadTimeout: options.ReadTimeout,
9456
ReadHeaderTimeout: options.ReadHeaderTimeout,
@@ -108,6 +70,7 @@ func (s *Server) Addr() string {
10870
// calls Serve to handle requests on incoming connections.
10971
// Accepted connections are configured to enable TCP keep-alives.
11072
func (s *Server) Run() error {
73+
s.httpSvr.Handler = s
11174
if nil != s.httpSvr.TLSConfig {
11275
return s.httpSvr.ListenAndServeTLS(s.options.CertFile, s.options.KeyFile)
11376
}

0 commit comments

Comments
 (0)