Skip to content

Commit

Permalink
Merge branch 'develop' into refactor/client_query_enum
Browse files Browse the repository at this point in the history
  • Loading branch information
FGYFFFF authored Apr 18, 2024
2 parents 9d07631 + 3b3296c commit 3bc1be6
Show file tree
Hide file tree
Showing 23 changed files with 420 additions and 143 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ Hertz [həːts] is a high-usability, high-performance and high-extensibility Gol
The Hertz-Examples repository provides code out of the box. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/example/)
### Basic Features
Contains introduction and use of general middleware, context selection, data binding, data rendering, direct access, logging, error handling. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/)
### Observability
Contains instrumentation, logging, tracing, monitoring, OpenTelemetry integration. [more](https://www.cloudwego.io/docs/hertz/tutorials/observability/)
### Service Governance
Contains tracer monitor. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/service-governance/)
Contains service registration and discovery extensions, Sentinel integration. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/service-governance/)
### Framework Extension
Contains network library extensions. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/framework-exten/)
### Reference
Expand All @@ -51,10 +53,10 @@ Hertz [həːts] is a high-usability, high-performance and high-extensibility Gol
Frequently Asked Questions. [more](https://www.cloudwego.io/zh/docs/hertz/faq/)
## Performance
Performance testing can only provide a relative reference. In production, there are many factors that can affect actual performance.
We provide the hertz-benchmark project to track and compare the performance of Hertz and other frameworks in different situations for reference.
We provide the [hertz-benchmark](https://github.com/cloudwego/hertz-benchmark) project to track and compare the performance of Hertz and other frameworks in different situations for reference.
## Related Projects
- [Netpoll](https://github.com/cloudwego/netpoll): A high-performance network library. Hertz integrated by default.
- [Hertz-Contrib](https://github.com/hertz-contrib): A partial extension library of Hertz, which users can integrate into Hertz through options according to their needs.
- [Hertz-contrib](https://github.com/hertz-contrib): A partial extension library of Hertz, which users can integrate into Hertz through options according to their needs.
- [Example](https://github.com/cloudwego/hertz-examples): Use examples of Hertz.
## Extensions

Expand Down Expand Up @@ -120,7 +122,7 @@ Thank you for your contribution to Hertz!
## Landscapes

<p align="center">
<img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>&nbsp;&nbsp;<img src="https://landscape.cncf.io/images/right-logo.svg" width="200"/>
<img src="https://landscape.cncf.io/images/cncf-landscape-horizontal-color.svg" width="150"/>&nbsp;&nbsp;<img src="https://www.cncf.io/wp-content/uploads/2023/04/cncf-main-site-logo.svg" width="200"/>
<br/><br/>
CloudWeGo enriches the <a href="https://landscape.cncf.io/">CNCF CLOUD NATIVE Landscape</a>.
</p>
10 changes: 6 additions & 4 deletions README_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架,在设计之初参考了
### 用户指南
### 基本特性
包含通用中间件的介绍和使用,上下文选择,数据绑定,数据渲染,直连访问,日志,错误处理,[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/)
### 可观测性
包含日志,链路追踪,埋点,监控,OpenTelemetry 集成,[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/observability/)
### 治理特性
包含 trace monitor[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/service-governance/)
包含服务注册与发现扩展,Sentinel 集成[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/service-governance/)
### 框架扩展
包含网络库扩展,[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/framework-exten/)
### 参考
Expand All @@ -51,10 +53,10 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架,在设计之初参考了
常见问题排查,[详见文档](https://www.cloudwego.io/zh/docs/hertz/faq/)
## 框架性能
性能测试只能提供相对参考,工业场景下,有诸多因素可以影响实际的性能表现
我们提供了 hertz-benchmark 项目用来长期追踪和比较 Hertz 与其他框架在不同情况下的性能数据以供参考
我们提供了 [hertz-benchmark](https://github.com/cloudwego/hertz-benchmark) 项目用来长期追踪和比较 Hertz 与其他框架在不同情况下的性能数据以供参考
## 相关项目
- [Netpoll](https://github.com/cloudwego/netpoll): 自研高性能网络库,Hertz 默认集成
- [Hertz-Contrib](https://github.com/hertz-contrib): Hertz 扩展仓库,提供中间件、tracer 等能力
- [hertz-Contrib](https://github.com/hertz-contrib): Hertz 扩展仓库,提供可观测、安全、流量治理、协议、HTTP 通用能力等扩展
- [Example](https://github.com/cloudwego/hertz-examples): Hertz 使用例子
## 相关拓展

Expand Down Expand Up @@ -121,7 +123,7 @@ Hertz 基于[Apache License 2.0](https://github.com/cloudwego/hertz/blob/main/LI
## Landscapes

<p align="center">
<img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>&nbsp;&nbsp;<img src="https://landscape.cncf.io/images/right-logo.svg" width="200"/>
<img src="https://landscape.cncf.io/images/cncf-landscape-horizontal-color.svg" width="150"/>&nbsp;&nbsp;<img src="https://www.cncf.io/wp-content/uploads/2023/04/cncf-main-site-logo.svg" width="200"/>
<br/><br/>
CloudWeGo 丰富了 <a href="https://landscape.cncf.io/">CNCF 云原生生态</a>。
</p>
6 changes: 5 additions & 1 deletion _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ HeaderReferer = "HeaderReferer"
expectedReferer = "expectedReferer"
Referer = "Referer"
O_WRONLY = "O_WRONLY"
WRONLY = "WRONLY"
WRONLY = "WRONLY"
ome = "ome"
ifModifiedSice = "ifModifiedSice"
hd = "hd"
pn = "pn"
Binary file removed images/wechat_group_cn.png
Binary file not shown.
Binary file removed images/wechat_group_en.png
Binary file not shown.
51 changes: 47 additions & 4 deletions pkg/app/server/binding/binder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,11 @@ func TestBind_ZeroValueBind(t *testing.T) {

func TestBind_DefaultValueBind(t *testing.T) {
var s struct {
A int `default:"15"`
B float64 `query:"b" default:"17"`
C []int `default:"15"`
D []string `default:"qwe"`
A int `default:"15"`
B float64 `query:"b" default:"17"`
C []int `default:"[15]"`
D []string `default:"['qwe','asd']"`
F [2]string `default:"['qwe','asd','zxc']"`
}
req := newMockRequest().
SetRequestURI("http://foobar.com")
Expand All @@ -432,7 +433,23 @@ func TestBind_DefaultValueBind(t *testing.T) {
assert.DeepEqual(t, 15, s.A)
assert.DeepEqual(t, float64(17), s.B)
assert.DeepEqual(t, 15, s.C[0])
assert.DeepEqual(t, 2, len(s.D))
assert.DeepEqual(t, "qwe", s.D[0])
assert.DeepEqual(t, "asd", s.D[1])
assert.DeepEqual(t, 2, len(s.F))
assert.DeepEqual(t, "qwe", s.F[0])
assert.DeepEqual(t, "asd", s.F[1])

var s2 struct {
F [2]string `default:"['qwe']"`
}
err = DefaultBinder().Bind(req.Req, &s2, nil)
if err != nil {
t.Fatal(err)
}
assert.DeepEqual(t, 2, len(s2.F))
assert.DeepEqual(t, "qwe", s2.F[0])
assert.DeepEqual(t, "", s2.F[1])

var d struct {
D [2]string `default:"qwe"`
Expand Down Expand Up @@ -1549,6 +1566,32 @@ func TestBind_Issue1015(t *testing.T) {
assert.DeepEqual(t, "asd", result.A)
}

func TestBind_JSONWithDefault(t *testing.T) {
type Req struct {
J1 string `json:"j1" default:"j1default"`
}

req := newMockRequest().
SetJSONContentType().
SetBody([]byte(`{"j1":"j1"}`))
var result Req
err := DefaultBinder().Bind(req.Req, &result, nil)
if err != nil {
t.Error(err)
}
assert.DeepEqual(t, "j1", result.J1)

result = Req{}
req = newMockRequest().
SetJSONContentType().
SetBody([]byte(`{"j2":"j2"}`))
err = DefaultBinder().Bind(req.Req, &result, nil)
if err != nil {
t.Error(err)
}
assert.DeepEqual(t, "j1default", result.J1)
}

func TestBind_WithoutPreBindForTag(t *testing.T) {
type BaseQuery struct {
Action string `query:"Action" binding:"required"`
Expand Down
7 changes: 5 additions & 2 deletions pkg/app/server/binding/internal/decoder/base_type_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,17 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Pa
var defaultValue string
for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
defaultValue = tagInfo.Default
if tagInfo.Key == jsonTag {
defaultValue = tagInfo.Default
found := checkRequireJSON(req, tagInfo)
if found {
err = nil
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request body does not have this parameter '%s'", d.fieldName, tagInfo.JSONName)
}
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
}
}
continue
}
Expand All @@ -94,7 +97,7 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Pa
return err
}
if len(text) == 0 && len(defaultValue) != 0 {
text = defaultValue
text = toDefaultValue(d.fieldType, defaultValue)
}
if !exist && len(text) == 0 {
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ func (d *customizedFieldTextDecoder) Decode(req *protocol.Request, params param.
var defaultValue string
for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
defaultValue = tagInfo.Default
if tagInfo.Key == jsonTag {
defaultValue = tagInfo.Default
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
}
}
continue
}
text, exist = tagInfo.Getter(req, params, tagInfo.Value)
Expand All @@ -73,7 +78,7 @@ func (d *customizedFieldTextDecoder) Decode(req *protocol.Request, params param.
return nil
}
if len(text) == 0 && len(defaultValue) != 0 {
text = defaultValue
text = toDefaultValue(d.fieldType, defaultValue)
}

v, err := d.decodeFunc(req, params, text)
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/server/binding/internal/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func getFieldDecoder(pInfo parentInfos, field reflect.StructField, index int, by
// JSONName is like 'a.b.c' for 'required validate'
fieldTagInfos, newParentJSONName, needValidate := lookupFieldTags(field, pInfo.JSONName, config)
if len(fieldTagInfos) == 0 && !config.DisableDefaultTag {
fieldTagInfos = getDefaultFieldTags(field)
fieldTagInfos, newParentJSONName = getDefaultFieldTags(field, pInfo.JSONName)
}
if len(byTag) != 0 {
fieldTagInfos = getFieldTagInfoByTag(field, byTag)
Expand Down
9 changes: 9 additions & 0 deletions pkg/app/server/binding/internal/decoder/gjson_required.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,12 @@ func checkRequireJSON(req *protocol.Request, tagInfo TagInfo) bool {
}
return true
}

func keyExist(req *protocol.Request, tagInfo TagInfo) bool {
ct := bytesconv.B2s(req.Header.ContentType())
if utils.FilterContentType(ct) != consts.MIMEApplicationJSON {
return false
}
result := gjson.GetBytes(req.Body(), tagInfo.JSONName)
return result.Exists()
}
7 changes: 5 additions & 2 deletions pkg/app/server/binding/internal/decoder/map_type_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,17 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Par
var defaultValue string
for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
defaultValue = tagInfo.Default
if tagInfo.Key == jsonTag {
defaultValue = tagInfo.Default
found := checkRequireJSON(req, tagInfo)
if found {
err = nil
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", d.fieldName)
}
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
}
}
continue
}
Expand All @@ -86,7 +89,7 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Par
return err
}
if len(text) == 0 && len(defaultValue) != 0 {
text = defaultValue
text = toDefaultValue(d.fieldType, defaultValue)
}
if !exist && len(text) == 0 {
return nil
Expand Down
47 changes: 15 additions & 32 deletions pkg/app/server/binding/internal/decoder/slice_type_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,20 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P
var texts []string
var defaultValue string
var bindRawBody bool
var isDefault bool
for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
defaultValue = tagInfo.Default
if tagInfo.Key == jsonTag {
defaultValue = tagInfo.Default
found := checkRequireJSON(req, tagInfo)
if found {
err = nil
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", d.fieldName)
}
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { //
defaultValue = ""
}
}
continue
}
Expand All @@ -91,7 +95,9 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P
return err
}
if len(texts) == 0 && len(defaultValue) != 0 {
defaultValue = toDefaultValue(d.fieldType, defaultValue)
texts = append(texts, defaultValue)
isDefault = true
}
if len(texts) == 0 {
return nil
Expand All @@ -113,7 +119,7 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P
}

if d.isArray {
if len(texts) != field.Len() {
if len(texts) != field.Len() && !isDefault {
return fmt.Errorf("%q is not valid value for %s", texts, field.Type().String())
}
} else {
Expand All @@ -135,6 +141,13 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P
elemKind = t.Kind()
ptrDepth++
}
if isDefault {
err = hJson.Unmarshal(bytesconv.S2b(texts[0]), reqValue.Field(d.index).Addr().Interface())
if err != nil {
return fmt.Errorf("using '%s' to unmarshal field '%s: %s' failed, %v", texts[0], d.fieldName, d.fieldType.String(), err)
}
return nil
}

for idx, text := range texts {
var vv reflect.Value
Expand Down Expand Up @@ -218,33 +231,3 @@ func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagIn
isArray: isArray,
}}, nil
}

func stringToValue(elemType reflect.Type, text string, req *protocol.Request, params param.Params, config *DecodeConfig) (v reflect.Value, err error) {
v = reflect.New(elemType).Elem()
if customizedFunc, exist := config.TypeUnmarshalFuncs[elemType]; exist {
val, err := customizedFunc(req, params, text)
if err != nil {
return reflect.Value{}, err
}
return val, nil
}
switch elemType.Kind() {
case reflect.Struct:
err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface())
case reflect.Map:
err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface())
case reflect.Array, reflect.Slice:
// do nothing
default:
decoder, err := SelectTextDecoder(elemType)
if err != nil {
return reflect.Value{}, fmt.Errorf("unsupported type %s for slice/array", elemType.String())
}
err = decoder.UnmarshalString(text, v, config.LooseZeroMode)
if err != nil {
return reflect.Value{}, fmt.Errorf("unable to decode '%s' as %s: %w", text, elemType.String(), err)
}
}

return v, err
}
9 changes: 9 additions & 0 deletions pkg/app/server/binding/internal/decoder/sonic_required.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,12 @@ func stringSliceForInterface(s string) (ret []interface{}) {
}
return
}

func keyExist(req *protocol.Request, tagInfo TagInfo) bool {
ct := bytesconv.B2s(req.Header.ContentType())
if utils.FilterContentType(ct) != consts.MIMEApplicationJSON {
return false
}
node, _ := sonic.Get(req.Body(), stringSliceForInterface(tagInfo.JSONName)...)
return node.Exists()
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,17 @@ func (d *structTypeFieldTextDecoder) Decode(req *protocol.Request, params param.
var defaultValue string
for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
defaultValue = tagInfo.Default
if tagInfo.Key == jsonTag {
defaultValue = tagInfo.Default
found := checkRequireJSON(req, tagInfo)
if found {
err = nil
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", d.fieldName)
}
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
}
}
continue
}
Expand All @@ -63,7 +66,7 @@ func (d *structTypeFieldTextDecoder) Decode(req *protocol.Request, params param.
return err
}
if len(text) == 0 && len(defaultValue) != 0 {
text = defaultValue
text = toDefaultValue(d.fieldType, defaultValue)
}
if !exist && len(text) == 0 {
return nil
Expand Down
Loading

0 comments on commit 3bc1be6

Please sign in to comment.