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

chore: release v0.7.3 #1018

Merged
merged 10 commits into from
Dec 7, 2023
15 changes: 8 additions & 7 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ This document shows key roadmap of Hertz development from the year of 2022 to 20

# New Features:
- Community Build
- Support more middlewares for users, like sessions、gzip.
- Support reverse proxy.
- [x] Support more middlewares for users, like sessions、gzip.
- [x] Support reverse proxy.
- Support swagger.
- Protocol
- Support Websocket.
- Support HTTP2.
- [x] Support Websocket.
- [x] Support HTTP2.
- [x] Support HTTP3.
- Service Governance
- Support more extension for users.
- Performance Optimization
- Improve the server throughput in small packet case.
- Improve the server throughput in tiny packet case.
- User Experience Optimization
- Provide good development practices for users to develop with Hertz more easily.
- Improve code generation tool(hz) usability.
- [x] Provide good development practices for users to develop with Hertz more easily.
- [x] Improve code generation tool(hz) usability.


All developers are welcome to contribute your extension to [hertz-contrib](https://github.com/hertz-contrib).
2 changes: 1 addition & 1 deletion cmd/hz/generator/custom_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func renderImportTpl(tplInfo *Template, data interface{}) ([]string, error) {

// renderAppendContent used to render append content for 'update' command
func renderAppendContent(tplInfo *Template, renderInfo interface{}) (string, error) {
tpl, err := template.New(tplInfo.Path).Parse(tplInfo.UpdateBehavior.AppendTpl)
tpl, err := template.New(tplInfo.Path).Funcs(funcMap).Parse(tplInfo.UpdateBehavior.AppendTpl)
if err != nil {
return "", fmt.Errorf("parse append content template(%s) failed, err: %v", tplInfo.Path, err)
}
Expand Down
20 changes: 13 additions & 7 deletions cmd/hz/generator/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ type Handler struct {
Methods []*HttpMethod
}

type SingleHandler struct {
*HttpMethod
FilePath string
PackageName string
ProjPackage string
}

type Client struct {
Handler
ServiceName string
Expand Down Expand Up @@ -234,13 +241,12 @@ func (pkgGen *HttpPackageGenerator) updateHandler(handler interface{}, handlerTp
if handlerSingleTpl == nil {
return fmt.Errorf("tpl %s not found", handlerSingleTplName)
}
data := make(map[string]string, 5)
data["Comment"] = method.Comment
data["Name"] = method.Name
data["RequestTypeName"] = method.RequestTypeName
data["ReturnTypeName"] = method.ReturnTypeName
data["Serializer"] = method.Serializer
data["OutputDir"] = method.OutputDir
data := SingleHandler{
HttpMethod: method,
FilePath: handler.(Handler).FilePath,
PackageName: handler.(Handler).PackageName,
ProjPackage: handler.(Handler).ProjPackage,
}
handlerFunc := bytes.NewBuffer(nil)
err = handlerSingleTpl.Execute(handlerFunc, data)
if err != nil {
Expand Down
34 changes: 34 additions & 0 deletions pkg/app/server/binding/binder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,40 @@ func Test_Issue964(t *testing.T) {
}
}

type reqSameType struct {
Parent *reqSameType `json:"parent"`
Children []reqSameType `json:"children"`
Foo1 reqSameType2 `json:"foo1"`
A string `json:"a"`
}

type reqSameType2 struct {
Foo1 *reqSameType `json:"foo1"`
}

func TestBind_Issue1015(t *testing.T) {
req := newMockRequest().
SetJSONContentType().
SetBody([]byte(`{"parent":{"parent":{}, "children":[{},{}], "foo1":{"foo1":{}}}, "children":[{},{}], "a":"asd"}`))

var result reqSameType

err := DefaultBinder().Bind(req.Req, &result, nil)
if err != nil {
t.Error(err)
}
assert.NotNil(t, result.Parent)
assert.NotNil(t, result.Parent.Parent)
assert.Nil(t, result.Parent.Parent.Parent)
assert.NotNil(t, result.Parent.Children)
assert.DeepEqual(t, 2, len(result.Parent.Children))
assert.NotNil(t, result.Parent.Foo1.Foo1)
assert.DeepEqual(t, "", result.Parent.A)
assert.DeepEqual(t, 2, len(result.Children))
assert.Nil(t, result.Foo1.Foo1)
assert.DeepEqual(t, "asd", result.A)
}

func Benchmark_Binding(b *testing.B) {
type Req struct {
Version string `path:"v"`
Expand Down
49 changes: 37 additions & 12 deletions pkg/app/server/binding/internal/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func GetReqDecoder(rt reflect.Type, byTag string, config *DecodeConfig) (Decoder
continue
}

dec, needValidate2, err := getFieldDecoder(el.Field(i), i, []int{}, "", byTag, config)
dec, needValidate2, err := getFieldDecoder(parentInfos{[]reflect.Type{el}, []int{}, ""}, el.Field(i), i, byTag, config)
if err != nil {
return nil, false, err
}
Expand All @@ -103,7 +103,13 @@ func GetReqDecoder(rt reflect.Type, byTag string, config *DecodeConfig) (Decoder
}, needValidate, nil
}

func getFieldDecoder(field reflect.StructField, index int, parentIdx []int, parentJSONName string, byTag string, config *DecodeConfig) ([]fieldDecoder, bool, error) {
type parentInfos struct {
Types []reflect.Type
Indexes []int
JSONName string
}

func getFieldDecoder(pInfo parentInfos, field reflect.StructField, index int, byTag string, config *DecodeConfig) ([]fieldDecoder, bool, error) {
for field.Type.Kind() == reflect.Ptr {
field.Type = field.Type.Elem()
}
Expand All @@ -116,7 +122,7 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int, pare
}

// JSONName is like 'a.b.c' for 'required validate'
fieldTagInfos, newParentJSONName, needValidate := lookupFieldTags(field, parentJSONName, config)
fieldTagInfos, newParentJSONName, needValidate := lookupFieldTags(field, pInfo.JSONName, config)
if len(fieldTagInfos) == 0 && !config.DisableDefaultTag {
fieldTagInfos = getDefaultFieldTags(field)
}
Expand All @@ -126,19 +132,19 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int, pare

// customized type decoder has the highest priority
if customizedFunc, exist := config.TypeUnmarshalFuncs[field.Type]; exist {
dec, err := getCustomizedFieldDecoder(field, index, fieldTagInfos, parentIdx, customizedFunc, config)
dec, err := getCustomizedFieldDecoder(field, index, fieldTagInfos, pInfo.Indexes, customizedFunc, config)
return dec, needValidate, err
}

// slice/array field decoder
if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
dec, err := getSliceFieldDecoder(field, index, fieldTagInfos, parentIdx, config)
dec, err := getSliceFieldDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)
return dec, needValidate, err
}

// map filed decoder
if field.Type.Kind() == reflect.Map {
dec, err := getMapTypeTextDecoder(field, index, fieldTagInfos, parentIdx, config)
dec, err := getMapTypeTextDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)
return dec, needValidate, err
}

Expand All @@ -149,11 +155,11 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int, pare
// todo: more built-in common struct binding, ex. time...
switch el {
case reflect.TypeOf(multipart.FileHeader{}): // file binding
dec, err := getMultipartFileDecoder(field, index, fieldTagInfos, parentIdx, config)
dec, err := getMultipartFileDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)
return dec, needValidate, err
}
if !config.DisableStructFieldResolve { // decode struct type separately
structFieldDecoder, err := getStructTypeFieldDecoder(field, index, fieldTagInfos, parentIdx, config)
structFieldDecoder, err := getStructTypeFieldDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)
if err != nil {
return nil, needValidate, err
}
Expand All @@ -162,17 +168,26 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int, pare
}
}

// prevent infinite recursion when struct field with the same name as a struct
if hasSameType(pInfo.Types, el) {
return decoders, needValidate, nil
}

pIdx := pInfo.Indexes
for i := 0; i < el.NumField(); i++ {
if el.Field(i).PkgPath != "" && !el.Field(i).Anonymous {
// ignore unexported field
continue
}
var idxes []int
if len(parentIdx) > 0 {
idxes = append(idxes, parentIdx...)
if len(pInfo.Indexes) > 0 {
idxes = append(idxes, pIdx...)
}
idxes = append(idxes, index)
dec, needValidate2, err := getFieldDecoder(el.Field(i), i, idxes, newParentJSONName, byTag, config)
pInfo.Indexes = idxes
pInfo.Types = append(pInfo.Types, el)
pInfo.JSONName = newParentJSONName
dec, needValidate2, err := getFieldDecoder(pInfo, el.Field(i), i, byTag, config)
needValidate = needValidate || needValidate2
if err != nil {
return nil, false, err
Expand All @@ -186,6 +201,16 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int, pare
}

// base type decoder
dec, err := getBaseTypeTextDecoder(field, index, fieldTagInfos, parentIdx, config)
dec, err := getBaseTypeTextDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)
return dec, needValidate, err
}

// hasSameType determine if the same type is present in the parent-child relationship
func hasSameType(pts []reflect.Type, ft reflect.Type) bool {
for _, pt := range pts {
if reflect.DeepEqual(getElemType(pt), getElemType(ft)) {
return true
}
}
return false
}
2 changes: 1 addition & 1 deletion pkg/protocol/http1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
begin := req.Options().StartTime()

dialTimeout := rc.dialTimeout
if reqTimeout < dialTimeout || dialTimeout == 0 {
if (reqTimeout > 0 && reqTimeout < dialTimeout) || dialTimeout == 0 {
dialTimeout = reqTimeout
}
cc, inPool, err := c.acquireConn(dialTimeout)
Expand Down
49 changes: 35 additions & 14 deletions pkg/protocol/http1/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestHostClientMaxConnWaitTimeoutWithEarlierDeadline(t *testing.T) {

c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return mock.SlowReadDialer(addr)
}),
MaxConns: 1,
Expand Down Expand Up @@ -212,16 +212,16 @@ func testContinueReadResponseBodyStream(t *testing.T, header, body string, maxBo
}
}

func newSlowConnDialer(dialer func(network, addr string) (network.Conn, error)) network.Dialer {
func newSlowConnDialer(dialer func(network, addr string, timeout time.Duration) (network.Conn, error)) network.Dialer {
return &mockDialer{customDialConn: dialer}
}

type mockDialer struct {
customDialConn func(network, addr string) (network.Conn, error)
customDialConn func(network, addr string, timeout time.Duration) (network.Conn, error)
}

func (m *mockDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {
return m.customDialConn(network, address)
return m.customDialConn(network, address, timeout)
}

func (m *mockDialer) DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {
Expand All @@ -244,7 +244,7 @@ func (s *slowDialer) DialConnection(network, address string, timeout time.Durati
func TestReadTimeoutPriority(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return mock.SlowReadDialer(addr)
}),
MaxConns: 1,
Expand Down Expand Up @@ -274,7 +274,7 @@ func TestReadTimeoutPriority(t *testing.T) {
func TestDoNonNilReqResp(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return &writeErrConn{
Conn: mock.NewConn("HTTP/1.1 400 OK\nContent-Length: 6\n\n123456"),
},
Expand All @@ -295,7 +295,7 @@ func TestDoNonNilReqResp(t *testing.T) {
func TestDoNonNilReqResp1(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return &writeErrConn{
Conn: mock.NewConn(""),
},
Expand All @@ -314,7 +314,7 @@ func TestDoNonNilReqResp1(t *testing.T) {
func TestWriteTimeoutPriority(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return mock.SlowWriteDialer(addr)
}),
MaxConns: 1,
Expand Down Expand Up @@ -376,7 +376,7 @@ func TestStateObserve(t *testing.T) {
}{}
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return mock.SlowReadDialer(addr)
}),
StateObserve: func(hcs config.HostClientState) {
Expand Down Expand Up @@ -404,7 +404,7 @@ func TestStateObserve(t *testing.T) {
func TestCachedTLSConfig(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return mock.SlowReadDialer(addr)
}),
TLSConfig: &tls.Config{
Expand All @@ -426,7 +426,7 @@ func TestRetry(t *testing.T) {
var times int32
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
times++
if times < 3 {
return &retryConn{
Expand Down Expand Up @@ -486,7 +486,7 @@ func (w retryConn) SetWriteTimeout(t time.Duration) error {
func TestConnInPoolRetry(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return mock.NewOneTimeConn("HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789"), nil
}),
},
Expand Down Expand Up @@ -518,7 +518,7 @@ func TestConnInPoolRetry(t *testing.T) {
func TestConnNotRetry(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return mock.NewBrokenConn(""), nil
}),
},
Expand Down Expand Up @@ -558,7 +558,7 @@ func TestStreamNoContent(t *testing.T) {

c := &HostClient{
ClientOptions: &ClientOptions{
Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
return conn, nil
}),
},
Expand All @@ -576,3 +576,24 @@ func TestStreamNoContent(t *testing.T) {

assert.True(t, conn.isClose)
}

func TestDialTimeout(t *testing.T) {
c := &HostClient{
ClientOptions: &ClientOptions{
DialTimeout: time.Second * 10,
Dialer: &mockDialer{
customDialConn: func(network, addr string, timeout time.Duration) (network.Conn, error) {
assert.DeepEqual(t, time.Second*10, timeout)
return nil, errors.New("test error")
},
},
},
Addr: "foobar",
}

req := protocol.AcquireRequest()
req.SetRequestURI("http://foobar/baz")
resp := protocol.AcquireResponse()

c.Do(context.Background(), req, resp)
}
Loading