Skip to content

Commit

Permalink
chore: release v0.7.3 (#1018)
Browse files Browse the repository at this point in the history
  • Loading branch information
Duslia authored Dec 7, 2023
2 parents 32c40e5 + 76afd67 commit e2119d8
Show file tree
Hide file tree
Showing 16 changed files with 439 additions and 51 deletions.
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

0 comments on commit e2119d8

Please sign in to comment.