Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add in:header tag cache for http request to enhance performance #2923

Merged
merged 11 commits into from
Sep 25, 2023
9 changes: 6 additions & 3 deletions net/ghttp/ghttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gsession"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/util/gtag"
)

Expand Down Expand Up @@ -74,9 +75,11 @@ type (

// handlerFuncInfo contains the HandlerFunc address and its reflection type.
handlerFuncInfo struct {
Func HandlerFunc // Handler function address.
Type reflect.Type // Reflect type information for current handler, which is used for extensions of the handler feature.
Value reflect.Value // Reflect value information for current handler, which is used for extensions of the handler feature.
Func HandlerFunc // Handler function address.
Type reflect.Type // Reflect type information for current handler, which is used for extensions of the handler feature.
Value reflect.Value // Reflect value information for current handler, which is used for extensions of the handler feature.
IsOpenAPI bool // Is OpenAPI handler.
hailaz marked this conversation as resolved.
Show resolved Hide resolved
ReqFields []gstructs.Field
hailaz marked this conversation as resolved.
Show resolved Hide resolved
}

// HandlerItem is the registered handler for route handling,
Expand Down
38 changes: 1 addition & 37 deletions net/ghttp/ghttp_request_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package ghttp
import (
"context"
"net/http"
"reflect"

"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
Expand Down Expand Up @@ -131,42 +130,7 @@ func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) {
if funcInfo.Func != nil {
funcInfo.Func(m.request)
} else {
var inputValues = []reflect.Value{
reflect.ValueOf(m.request.Context()),
}
if funcInfo.Type.NumIn() == 2 {
var inputObject reflect.Value
if funcInfo.Type.In(1).Kind() == reflect.Ptr {
inputObject = reflect.New(funcInfo.Type.In(1).Elem())
m.request.error = m.request.Parse(inputObject.Interface())
} else {
inputObject = reflect.New(funcInfo.Type.In(1).Elem()).Elem()
m.request.error = m.request.Parse(inputObject.Addr().Interface())
}
if m.request.error != nil {
return
}
inputValues = append(inputValues, inputObject)
}

// Call handler with dynamic created parameter values.
results := funcInfo.Value.Call(inputValues)
switch len(results) {
case 1:
if !results[0].IsNil() {
if err, ok := results[0].Interface().(error); ok {
m.request.error = err
}
}

case 2:
m.request.handlerResponse = results[0].Interface()
if !results[1].IsNil() {
if err, ok := results[1].Interface().(error); ok {
m.request.error = err
}
}
}
m.request.error = gerror.New("Handler function not found.")
hailaz marked this conversation as resolved.
Show resolved Hide resolved
}
})
}
98 changes: 57 additions & 41 deletions net/ghttp/ghttp_request_param_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/gogf/gf/v2/net/goai"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
"github.com/gogf/gf/v2/util/gutil"
)

Expand Down Expand Up @@ -192,23 +191,42 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string]

// mergeDefaultStructValue merges the request parameters with default values from struct tag definition.
func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error {
// TODO: https://github.com/gogf/gf/pull/2450
tagFields, err := gstructs.TagFields(pointer, defaultValueTags)
if err != nil {
return err
}
if len(tagFields) > 0 {
fields := r.serveHandler.Handler.Info.ReqFields
if len(fields) > 0 {
var (
foundKey string
foundValue interface{}
)
for _, field := range tagFields {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = field.TagValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = field.TagValue
for _, field := range fields {
if tagValue := field.TagDefault(); tagValue != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = tagValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = tagValue
}
}
}
}
} else {
hailaz marked this conversation as resolved.
Show resolved Hide resolved
tagFields, err := gstructs.TagFields(pointer, defaultValueTags)
if err != nil {
return err
}
if len(tagFields) > 0 {
var (
foundKey string
foundValue interface{}
)
for _, field := range tagFields {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = field.TagValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = field.TagValue
}
}
}
}
Expand All @@ -218,12 +236,8 @@ func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer i

// mergeInTagStructValue merges the request parameters with header or cookie values from struct `in` tag definition.
func (r *Request) mergeInTagStructValue(data map[string]interface{}, pointer interface{}) error {
// TODO: https://github.com/gogf/gf/pull/2450
tagFields, err := gstructs.TagFields(pointer, []string{gtag.In})
if err != nil {
return err
}
if len(tagFields) > 0 {
fields := r.serveHandler.Handler.Info.ReqFields
if len(fields) > 0 {
var (
foundKey string
foundValue interface{}
Expand All @@ -241,29 +255,31 @@ func (r *Request) mergeInTagStructValue(data map[string]interface{}, pointer int
cookieMap[cookie.Name] = cookie.Value
}

for _, field := range tagFields {
switch field.TagValue {
case goai.ParameterInHeader:
foundHeaderKey, foundHeaderValue := gutil.MapPossibleItemByKey(headerMap, field.Name())
if foundHeaderKey != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundHeaderKey)
if foundKey == "" {
data[field.Name()] = foundHeaderValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = foundHeaderValue
for _, field := range fields {
if tagValue := field.TagIn(); tagValue != "" {
switch tagValue {
case goai.ParameterInHeader:
foundHeaderKey, foundHeaderValue := gutil.MapPossibleItemByKey(headerMap, field.Name())
if foundHeaderKey != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundHeaderKey)
if foundKey == "" {
data[field.Name()] = foundHeaderValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = foundHeaderValue
}
}
}
}
case goai.ParameterInCookie:
foundCookieKey, foundCookieValue := gutil.MapPossibleItemByKey(cookieMap, field.Name())
if foundCookieKey != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundCookieKey)
if foundKey == "" {
data[field.Name()] = foundCookieValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = foundCookieValue
case goai.ParameterInCookie:
foundCookieKey, foundCookieValue := gutil.MapPossibleItemByKey(cookieMap, field.Name())
if foundCookieKey != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundCookieKey)
if foundKey == "" {
data[field.Name()] = foundCookieValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = foundCookieValue
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion net/ghttp/ghttp_server_openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (s *Server) initOpenApi() {
case HandlerTypeMiddleware, HandlerTypeHook:
continue
}
if item.Handler.Info.Func == nil {
if item.Handler.Info.IsOpenAPI {
methods = []string{item.Method}
if gstr.Equal(item.Method, defaultMethod) {
methods = SupportedMethods()
Expand Down
99 changes: 89 additions & 10 deletions net/ghttp/ghttp_server_service_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/text/gstr"
)

Expand Down Expand Up @@ -145,21 +146,28 @@ func (s *Server) nameToUri(name string) string {
}

func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, methodName string) (info handlerFuncInfo, err error) {
handlerFunc, ok := f.(HandlerFunc)
if !ok {
reflectType := reflect.TypeOf(f)
info.Type = reflect.TypeOf(f)
info.Value = reflect.ValueOf(f)
if handlerFunc, ok := f.(HandlerFunc); ok {
info.Func = handlerFunc
} else {
var (
reflectType = info.Type
inputObject reflect.Value
objectPointer interface{}
)
if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
if pkgPath != "" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizReq)(*BizRes, error)" is required`,
pkgPath, structName, methodName, reflect.TypeOf(f).String(),
pkgPath, structName, methodName, reflectType.String(),
)
} else {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but "func(*ghttp.Request)" or "func(context.Context, *BizReq)(*BizRes, error)" is required`,
reflect.TypeOf(f).String(),
reflectType.String(),
)
}
return
Expand All @@ -169,7 +177,26 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but the first input parameter should be type of "context.Context"`,
reflect.TypeOf(f).String(),
reflectType.String(),
)
return
}

if reflectType.In(1).Kind() != reflect.Ptr ||
(reflectType.In(1).Kind() == reflect.Ptr && reflectType.In(1).Elem().Kind() != reflect.Struct) {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but the second input parameter should be type of pointer to struct(*BizReq)`,
hailaz marked this conversation as resolved.
Show resolved Hide resolved
reflectType.String(),
)
return
}

if reflectType.Out(0).Kind() != reflect.Ptr || (reflectType.Out(0).Kind() == reflect.Ptr && reflectType.Out(0).Elem().Kind() != reflect.Struct) {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but the first output parameter should be type of pointer to struct(*BizRes)`,
hailaz marked this conversation as resolved.
Show resolved Hide resolved
reflectType.String(),
)
return
}
Expand All @@ -178,7 +205,7 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but the last output parameter should be type of "error"`,
reflect.TypeOf(f).String(),
reflectType.String(),
)
return
}
Expand All @@ -204,10 +231,62 @@ func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, structName, meth
)
return
}

info.IsOpenAPI = true

inputObject = reflect.New(info.Type.In(1).Elem())
objectPointer = inputObject.Interface()

// It retrieves and returns the request struct fields.
fields, err := gstructs.Fields(gstructs.FieldsInput{
Pointer: objectPointer,
RecursiveOption: gstructs.RecursiveOptionEmbedded,
})
if err != nil {
return info, err
}
info.ReqFields = fields

// Build handler for processing
info.Func = func(r *Request) {
gqcn marked this conversation as resolved.
Show resolved Hide resolved
var (
inputValues = []reflect.Value{
reflect.ValueOf(r.Context()),
}
inputObject reflect.Value
objectPointer interface{}
)
// Must new a new object for each request.
inputObject = reflect.New(reflectType.In(1).Elem())
objectPointer = inputObject.Interface()

r.error = r.Parse(objectPointer)
if r.error != nil {
return
}
inputValues = append(inputValues, inputObject)
// Call handler with dynamic created parameter values.
results := info.Value.Call(inputValues)
switch len(results) {
case 1:
// TODO: Is this useful?
if !results[0].IsNil() {
if err, ok := results[0].Interface().(error); ok {
r.error = err
}
}

case 2:
r.handlerResponse = results[0].Interface()
if !results[1].IsNil() {
if err, ok := results[1].Interface().(error); ok {
r.error = err
}
}
}
}
}
info.Func = handlerFunc
info.Type = reflect.TypeOf(f)
info.Value = reflect.ValueOf(f)

return
}

Expand Down
5 changes: 3 additions & 2 deletions net/ghttp/ghttp_z_unit_feature_request_param_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ func Benchmark_ParamTag(b *testing.B) {
client := g.Client()
client.SetPrefix(prefix)
client.SetCookie("name", "john")
client.SetHeader("age", "18")

b.StartTimer()
for i := 0; i < b.N; i++ {
client.PostContent(ctx, "/user", "key="+strconv.Itoa(i))
for i := 1; i < b.N; i++ {
client.PostContent(ctx, "/user", "id="+strconv.Itoa(i))
}
}
5 changes: 5 additions & 0 deletions net/ghttp/ghttp_z_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"net/http"
"net/url"
"runtime"
"testing"
"time"

Expand Down Expand Up @@ -170,6 +171,10 @@ func Test_BuildParams(t *testing.T) {
}

func Test_ServerSignal(t *testing.T) {
if runtime.GOOS == "windows" {
t.Log("skip windows")
return
}
s := g.Server(guid.S())
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("hello world")
Expand Down