Repository: cloudwego/hertz
Pull request #1045 (2024-01-12 03:09:33)

chore: release v0.8.0


Patch:
diff --git a/README.md b/README.md
index ae09350..23fcc2e 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ Hertz [həːts] is a high-usability, high-performance and high-extensibility Gol
   Hertz adopts a layered design, providing more interfaces and default extension implementations. Users can also extend by themselves. At the same time, thanks to the layered design of the framework, the extensibility of the framework will be much greater. At present, only stable capabilities are open-sourced to the community. More planning refers to [RoadMap](ROADMAP.md).
 - Multi-protocol support
 
-  The Hertz framework provides HTTP1.1, ALPN protocol support natively. In addition, due to the layered design, Hertz even supports custom build protocol resolution logic to meet any needs of protocol layer extensions.
+  The Hertz framework provides HTTP/1.1, HTTP/2, HTTP/3, ALPN protocol support natively. In addition, due to the layered design, Hertz even supports custom build protocol resolution logic to meet any needs of protocol layer extensions.
 - Network layer switching capability
 
   Hertz implements the function to switch between Netpoll and Go Net on demand. Users can choose the appropriate network library for different scenarios. And Hertz also supports the extension of network library in the form of plug-ins.
diff --git a/README_cn.md b/README_cn.md
index 8ff215d..6468418 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -18,7 +18,7 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了
   在开发过程中，快速写出来正确的代码往往是更重要的。因此，在 Hertz 在迭代过程中，积极听取用户意见，持续打磨框架，希望为用户提供一个更好的使用体验，帮助用户更快的写出正确的代码。
 - 高性能
 
-  Hertz 默认使用自研的高性能网络库 Netpoll，在一些特殊场景相较于 go net，Hertz 在 QPS、时延上均具有一定优势。关于性能数据，可参考下图 Echo 数据。
+  Hertz 默认使用自研的高性能网络库 Netpoll，在一些特殊场景相较于 go net，Hertz 在 QPS、时延上均具有一定优势。关于性能数据，可参考下图 Echo 数据。
   
   四个框架的对比:
   ![Performance](images/performance-4.png)
@@ -30,7 +30,7 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了
   Hertz 采用了分层设计，提供了较多的接口以及默认的扩展实现，用户也可以自行扩展。同时得益于框架的分层设计，框架的扩展性也会大很多。目前仅将稳定的能力开源给社区，更多的规划参考 [RoadMap](ROADMAP.md)。
 - 多协议支持
 
-  Hertz 框架原生提供 HTTP1.1、ALPN 协议支持。除此之外，由于分层设计，Hertz 甚至支持自定义构建协议解析逻辑，以满足协议层扩展的任意需求。
+  Hertz 框架原生提供 HTTP/1.1、HTTP/2、HTTP/3 及 ALPN 协议支持。除此之外，由于分层设计，Hertz 甚至支持自定义构建协议解析逻辑，以满足协议层扩展的任意需求。
 - 网络层切换能力
 
   Hertz 实现了 Netpoll 和 Golang 原生网络库 间按需切换能力，用户可以针对不同的场景选择合适的网络库，同时也支持以插件的方式为 Hertz 扩展网络库实现。
diff --git a/cmd/hz/app/app.go b/cmd/hz/app/app.go
index a6c44c0..2313bb8 100644
--- a/cmd/hz/app/app.go
+++ b/cmd/hz/app/app.go
@@ -69,7 +69,7 @@ func New(c *cli.Context) error {
 	if err != nil {
 		return cli.Exit(fmt.Errorf("persist manifest failed: %v", err), meta.PersistError)
 	}
-	if !args.NeedGoMod && args.IsNew() {
+	if !args.NeedGoMod && args.IdlType == meta.IdlThrift {
 		logs.Warn(meta.AddThriftReplace)
 	}
 
@@ -184,7 +184,7 @@ func Init() *cli.App {
 	unsetOmitemptyFlag := cli.BoolFlag{Name: "unset_omitempty", Usage: "Remove 'omitempty' tag for generated struct.", Destination: &globalArgs.UnsetOmitempty}
 	protoCamelJSONTag := cli.BoolFlag{Name: "pb_camel_json_tag", Usage: "Convert Name style for json tag to camel(Only works protobuf).", Destination: &globalArgs.ProtobufCamelJSONTag}
 	snakeNameFlag := cli.BoolFlag{Name: "snake_tag", Usage: "Use snake_case style naming for tags. (Only works for 'form', 'query', 'json')", Destination: &globalArgs.SnakeName}
-	rmTagFlag := cli.StringSliceFlag{Name: "rm_tag", Usage: "Remove the specified tag"}
+	rmTagFlag := cli.StringSliceFlag{Name: "rm_tag", Usage: "Remove the default tag(json/query/form). If the annotation tag is set explicitly, it will not be removed."}
 	customLayout := cli.StringFlag{Name: "customize_layout", Usage: "Specify the path for layout template.", Destination: &globalArgs.CustomizeLayout}
 	customLayoutData := cli.StringFlag{Name: "customize_layout_data_path", Usage: "Specify the path for layout template render data.", Destination: &globalArgs.CustomizeLayoutData}
 	customPackage := cli.StringFlag{Name: "customize_package", Usage: "Specify the path for package template.", Destination: &globalArgs.CustomizePackage}
diff --git a/cmd/hz/config/argument.go b/cmd/hz/config/argument.go
index b8bf41c..defc505 100644
--- a/cmd/hz/config/argument.go
+++ b/cmd/hz/config/argument.go
@@ -226,33 +226,35 @@ func (arg *Argument) checkPackage() error {
 	if len(arg.Gomod) == 0 { // not specified "go module"
 		// search go.mod recursively
 		module, path, ok := util.SearchGoMod(arg.Cwd, true)
-		if ok {
-			logs.Debugf("find module '%s' form '%s/go.mod'", module, path)
+		if ok { // find go.mod in upper level, use it as project module, don't generate go.mod
 			rel, err := filepath.Rel(path, arg.Cwd)
 			if err != nil {
 				return fmt.Errorf("can not get relative path, err :%v", err)
 			}
 			arg.Gomod = filepath.Join(module, rel)
+			logs.Debugf("find module '%s' from '%s/go.mod', so use it as module name", module, path)
 		}
-		if len(arg.Gomod) == 0 {
+		if len(arg.Gomod) == 0 { // don't find go.mod in upper level, use relative path as module name, generate go.mod
+			logs.Debugf("use gopath's relative path '%s' as the module name", arg.Gopkg)
+			// gopkg will be "" under non-gopath
 			arg.Gomod = arg.Gopkg
+			arg.NeedGoMod = true
 		}
 		arg.Gomod = util.PathToImport(arg.Gomod, "")
 	} else { // specified "go module"
 		// search go.mod in current path
 		module, path, ok := util.SearchGoMod(arg.Cwd, false)
-		if ok {
-			// go.mod exists in current path
+		if ok { // go.mod exists in current path, check module name, don't generate go.mod
 			if module != arg.Gomod {
-				return fmt.Errorf("module name given by the '-module' option ('%s') is not consist with the name defined in go.mod ('%s' from %s)\n", arg.Gomod, module, path)
+				return fmt.Errorf("module name given by the '-module/mod' option ('%s') is not consist with the name defined in go.mod ('%s' from %s), try to remove '-module/mod' option in your command\n", arg.Gomod, module, path)
 			}
-		} else {
+		} else { // go.mod don't exist in current path, generate go.mod
 			arg.NeedGoMod = true
 		}
 	}
 
 	if len(arg.Gomod) == 0 {
-		return fmt.Errorf("can not get go module, please specify a module name with the '-module' flag")
+		return fmt.Errorf("can not get go module, please specify a module name with the '-module/mod' flag")
 	}
 
 	if len(arg.RawOptPkg) > 0 {
diff --git a/cmd/hz/generator/model/model.go b/cmd/hz/generator/model/model.go
index 2bb786b..f402804 100644
--- a/cmd/hz/generator/model/model.go
+++ b/cmd/hz/generator/model/model.go
@@ -377,8 +377,9 @@ type Choice struct {
 type Tags []Tag
 
 type Tag struct {
-	Key   string
-	Value string
+	Key       string
+	Value     string
+	IsDefault bool // default tag
 }
 
 func (ts Tags) String() string {
diff --git a/cmd/hz/generator/package_tpl.go b/cmd/hz/generator/package_tpl.go
index b6f6055..b46aa0e 100644
--- a/cmd/hz/generator/package_tpl.go
+++ b/cmd/hz/generator/package_tpl.go
@@ -324,6 +324,7 @@ type Option struct {
 
 type Options struct {
 	hostUrl               string
+	enumAsInt             bool
 	doer                  client.Doer
 	header                http.Header
 	requestBodyBind       bindRequestBodyFunc
@@ -375,6 +376,13 @@ func WithResponseResultDecider(decider ResponseResultDecider) Option {
 	}}
 }
 
+// WithQueryEnumAsInt is used to set enum as int for query parameters
+func WithQueryEnumAsInt(enable bool) Option {
+	return Option{func(op *Options) {
+		op.enumAsInt = enable
+	}}
+}
+
 func withHostUrl(HostUrl string) Option {
 	return Option{func(op *Options) {
 		op.hostUrl = HostUrl
@@ -384,6 +392,7 @@ func withHostUrl(HostUrl string) Option {
 // underlying client
 type cli struct {
 	hostUrl               string
+	enumAsInt             bool
 	doer                  client.Doer
 	header                http.Header
 	bindRequestBody       bindRequestBodyFunc
@@ -419,6 +428,7 @@ func newClient(opts *Options) (*cli, error) {
 
 	c := &cli{
 		hostUrl:               opts.hostUrl,
+		enumAsInt:             opts.enumAsInt,
 		doer:                  opts.doer,
 		header:                opts.header,
 		bindRequestBody:       opts.requestBodyBind,
@@ -573,6 +583,11 @@ func (r *request) setHeader(header, value string) *request {
 	return r
 }
 
+func (r *request) addHeader(header, value string) *request {
+	r.header.Add(header, value)
+	return r
+}
+
 func (r *request) setQueryParam(param string, value interface{}) *request {
 	v := reflect.ValueOf(value)
 	switch v.Kind() {
@@ -580,6 +595,12 @@ func (r *request) setQueryParam(param string, value interface{}) *request {
 		for index := 0; index < v.Len(); index++ {
 			r.queryParam.Add(param, fmt.Sprint(v.Index(index).Interface()))
 		}
+	case reflect.Int32, reflect.Int64:
+		if r.client.enumAsInt {
+			r.queryParam.Add(param, fmt.Sprintf("%d", v.Interface()))
+		} else {
+			r.queryParam.Add(param, fmt.Sprint(v))
+		}
 	default:
 		r.queryParam.Set(param, fmt.Sprint(v))
 	}
@@ -911,7 +932,7 @@ func (s *{{$.ServiceName}}Client) {{$MethodInfo.Name}}(context context.Context,
 		setPathParams(map[string]string{
 			{{$MethodInfo.PathParamsCode}}
 		}).
-		setHeaders(map[string]string{
+		addHeaders(map[string]string{
 			{{$MethodInfo.HeaderParamsCode}}
 		}).
 		setFormParams(map[string]string{
diff --git a/cmd/hz/meta/const.go b/cmd/hz/meta/const.go
index 3f4d916..dfeafd2 100644
--- a/cmd/hz/meta/const.go
+++ b/cmd/hz/meta/const.go
@@ -19,7 +19,7 @@ package meta
 import "runtime"
 
 // Version hz version
-const Version = "v0.7.1"
+const Version = "v0.8.0"
 
 const DefaultServiceName = "hertz_service"
 
diff --git a/cmd/hz/protobuf/plugin.go b/cmd/hz/protobuf/plugin.go
index e258038..2603340 100644
--- a/cmd/hz/protobuf/plugin.go
+++ b/cmd/hz/protobuf/plugin.go
@@ -509,7 +509,7 @@ func genMessageField(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, fie
 		}...)
 	}
 
-	err := injectTagsToStructTags(field.Desc, &tags, true)
+	err := injectTagsToStructTags(field.Desc, &tags, true, rmTags)
 	if err != nil {
 		return err
 	}
@@ -518,16 +518,6 @@ func genMessageField(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, fie
 		tags = append(tags, gotrackTags...)
 	}
 
-	if len(rmTags) > 0 {
-		tmp := structTags{}
-		for _, tag := range tags {
-			if !rmTags.Exist(tag[0]) {
-				tmp = append(tmp, tag)
-			}
-		}
-		tags = tmp
-	}
-
 	name := field.GoName
 	if field.Desc.IsWeak() {
 		name = WeakFieldPrefix_goname + name
diff --git a/cmd/hz/protobuf/tags.go b/cmd/hz/protobuf/tags.go
index 1abf231..26c6cc7 100644
--- a/cmd/hz/protobuf/tags.go
+++ b/cmd/hz/protobuf/tags.go
@@ -340,14 +340,18 @@ func defaultBindingStructTags(f protoreflect.FieldDescriptor) []model.Tag {
 		val := getStructJsonValue(f, v.(string))
 		out[0] = tag("json", val)
 	} else {
-		out[0] = reflectJsonTag(f)
+		t := reflectJsonTag(f)
+		t.IsDefault = true
+		out[0] = t
 	}
 	if v := checkFirstOption(api.E_Query, opts); v != nil {
 		val := checkStructRequire(f, v.(string))
 		out[1] = tag(BindingTags[api.E_Query], val)
 	} else {
 		val := checkStructRequire(f, checkSnakeName(string(f.Name())))
-		out[1] = tag(BindingTags[api.E_Query], val)
+		t := tag(BindingTags[api.E_Query], val)
+		t.IsDefault = true
+		out[1] = t
 	}
 	if v := checkFirstOption(api.E_Form, opts); v != nil {
 		val := checkStructRequire(f, v.(string))
@@ -355,16 +359,20 @@ func defaultBindingStructTags(f protoreflect.FieldDescriptor) []model.Tag {
 	} else {
 		if v := checkFirstOption(api.E_FormCompatible, opts); v != nil { // compatible form_compatible
 			val := checkStructRequire(f, v.(string))
-			out[2] = tag(BindingTags[api.E_Form], val)
+			t := tag(BindingTags[api.E_Form], val)
+			t.IsDefault = true
+			out[2] = t
 		} else {
 			val := checkStructRequire(f, checkSnakeName(string(f.Name())))
-			out[2] = tag(BindingTags[api.E_Form], val)
+			t := tag(BindingTags[api.E_Form], val)
+			t.IsDefault = true
+			out[2] = t
 		}
 	}
 	return out
 }
 
-func injectTagsToStructTags(f protoreflect.FieldDescriptor, out *structTags, needDefault bool) error {
+func injectTagsToStructTags(f protoreflect.FieldDescriptor, out *structTags, needDefault bool, rmTags RemoveTags) error {
 	as := f.Options()
 	// binding tags
 	tags := model.Tags(make([]model.Tag, 0, 6))
@@ -424,6 +432,11 @@ func injectTagsToStructTags(f protoreflect.FieldDescriptor, out *structTags, nee
 			disableTag = true
 		}
 	}
+	for _, t := range tags {
+		if t.IsDefault && rmTags.Exist(t.Key) {
+			tags.Remove(t.Key)
+		}
+	}
 	// protobuf tag as first
 	sort.Sort(tags[1:])
 	for _, t := range tags {
diff --git a/cmd/hz/thrift/plugin.go b/cmd/hz/thrift/plugin.go
index 8b840e7..c315c9d 100644
--- a/cmd/hz/thrift/plugin.go
+++ b/cmd/hz/thrift/plugin.go
@@ -418,7 +418,11 @@ func getTagString(f *parser.Field, rmTags []string) (string, error) {
 	}
 
 	for _, rmTag := range rmTags {
-		field.Tags.Remove(rmTag)
+		for _, t := range field.Tags {
+			if t.IsDefault && strings.EqualFold(t.Key, rmTag) {
+				field.Tags.Remove(t.Key)
+			}
+		}
 	}
 
 	var tagString string
diff --git a/cmd/hz/thrift/tags.go b/cmd/hz/thrift/tags.go
index 96c414d..115d79b 100644
--- a/cmd/hz/thrift/tags.go
+++ b/cmd/hz/thrift/tags.go
@@ -205,21 +205,27 @@ func defaultBindingTags(f *parser.Field) []model.Tag {
 		val := getJsonValue(f, v[0])
 		out[0] = tag("json", val)
 	} else {
-		out[0] = jsonTag(f)
+		t := jsonTag(f)
+		t.IsDefault = true
+		out[0] = t
 	}
 	if v := getAnnotation(f.Annotations, AnnotationQuery); len(v) > 0 {
 		val := checkRequire(f, v[0])
 		out[1] = tag(BindingTags[AnnotationQuery], val)
 	} else {
 		val := checkRequire(f, checkSnakeName(f.Name))
-		out[1] = tag(BindingTags[AnnotationQuery], val)
+		t := tag(BindingTags[AnnotationQuery], val)
+		t.IsDefault = true
+		out[1] = t
 	}
 	if v := getAnnotation(f.Annotations, AnnotationForm); len(v) > 0 {
 		val := checkRequire(f, v[0])
 		out[2] = tag(BindingTags[AnnotationForm], val)
 	} else {
 		val := checkRequire(f, checkSnakeName(f.Name))
-		out[2] = tag(BindingTags[AnnotationForm], val)
+		t := tag(BindingTags[AnnotationForm], val)
+		t.IsDefault = true
+		out[2] = t
 	}
 	return out
 }
diff --git a/internal/bytesconv/bytesconv_table.go b/internal/bytesconv/bytesconv_table.go
index 5668f2e..5954957 100644
--- a/internal/bytesconv/bytesconv_table.go
+++ b/internal/bytesconv/bytesconv_table.go
@@ -51,4 +51,7 @@ const (
 	QuotedArgShouldEscapeTable  = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
 	QuotedPathShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
 	ValidCookieValueTable       = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	ValidHeaderFieldValueTable  = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+	NewlineToSpaceTable         = "\x00\x01\x02\x03\x04\x05\x06\a\b\t \v\f \x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
+	ValidHeaderFieldNameTable   = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x01\x01\x01\x00\x00\x01\x01\x00\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
 )
diff --git a/internal/bytesconv/bytesconv_table_gen.go b/internal/bytesconv/bytesconv_table_gen.go
index 11fdfd9..eff361b 100644
--- a/internal/bytesconv/bytesconv_table_gen.go
+++ b/internal/bytesconv/bytesconv_table_gen.go
@@ -159,14 +159,97 @@ func main() {
 		return a
 	}()
 
+	validHeaderFieldValueTable := func() [256]byte {
+		// The implementation here is equal to httpguts.ValidHeaderFieldValue
+		var a [256]byte
+		for i := 0; i < 256; i++ {
+			a[i] = 1
+		}
+
+		for i := 0; i < ' '; i++ {
+			a[i] = 0
+		}
+
+		// del CTL
+		a[0x7f] = 0
+		// tab
+		a['\t'] = 1
+
+		return a
+	}()
+
+	newlineToSpaceTable := func() [256]byte {
+		var a [256]byte
+		for i := 0; i < 256; i++ {
+			c := byte(i)
+			if c == '\r' || c == '\n' {
+				c = ' '
+			}
+			a[i] = c
+		}
+		return a
+	}()
+
+	validHeaderFieldNameTable := func() [256]byte {
+		// The implementation here is equal to httpguts ValidHeaderFieldName(string)
+		// see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
+		//
+		//  RFC 7230 says:
+		//   header-field   = field-name ":" OWS field-value OWS
+		//   field-name     = token
+		//   token          = 1*tchar
+		//   tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
+		//           "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
+		var a [256]byte
+		for i := 0; i < 256; i++ {
+			a[i] = 0
+		}
+
+		a['!'] = 1
+		a['#'] = 1
+		a['$'] = 1
+		a['%'] = 1
+		a['&'] = 1
+		a['\''] = 1
+		a['*'] = 1
+		a['+'] = 1
+		a['-'] = 1
+		a['.'] = 1
+		a['^'] = 1
+		a['_'] = 1
+		a['`'] = 1
+		a['|'] = 1
+		a['~'] = 1
+
+		// ALPHA
+		for i := int('a'); i <= int('z'); i++ {
+			a[i] = 1
+		}
+		for i := int('A'); i <= int('Z'); i++ {
+			a[i] = 1
+		}
+
+		// DIGIT
+		for i := int('0'); i <= int('9'); i++ {
+			a[i] = 1
+		}
+
+		return a
+	}()
+
 	w := new(bytes.Buffer)
 	w.WriteString(pre)
-	fmt.Fprintf(w, "const Hex2intTable = %q\n", hex2intTable)
-	fmt.Fprintf(w, "const ToLowerTable = %q\n", toLowerTable)
-	fmt.Fprintf(w, "const ToUpperTable = %q\n", toUpperTable)
-	fmt.Fprintf(w, "const QuotedArgShouldEscapeTable = %q\n", quotedArgShouldEscapeTable)
-	fmt.Fprintf(w, "const QuotedPathShouldEscapeTable = %q\n", quotedPathShouldEscapeTable)
-	fmt.Fprintf(w, "const ValidCookieValueTable = %q\n", validCookieValueTable)
+	fmt.Fprintf(w, "const (\n")
+	fmt.Fprintf(w, "\tHex2intTable = %q\n", hex2intTable)
+	fmt.Fprintf(w, "\tToLowerTable = %q\n", toLowerTable)
+	fmt.Fprintf(w, "\tToUpperTable = %q\n", toUpperTable)
+	fmt.Fprintf(w, "\tQuotedArgShouldEscapeTable = %q\n", quotedArgShouldEscapeTable)
+	fmt.Fprintf(w, "\tQuotedPathShouldEscapeTable = %q\n", quotedPathShouldEscapeTable)
+	fmt.Fprintf(w, "\tValidCookieValueTable = %q\n", validCookieValueTable)
+	fmt.Fprintf(w, "\tValidHeaderFieldValueTable = %q\n", validHeaderFieldValueTable)
+	fmt.Fprintf(w, "\tNewlineToSpaceTable = %q\n", newlineToSpaceTable)
+	fmt.Fprintf(w, "\tValidHeaderFieldNameTable = %q\n", validHeaderFieldNameTable)
+	fmt.Fprintf(w, ")\n")
 
 	if err := ioutil.WriteFile("bytesconv_table.go", w.Bytes(), 0o660); err != nil {
 		log.Fatal(err)
diff --git a/pkg/app/context.go b/pkg/app/context.go
index 4e2d4be..510c972 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -328,19 +328,71 @@ func (ctx *RequestContext) GetIndex() int8 {
 	return ctx.index
 }
 
+// SetIndex reset the handler's execution index
+// Disclaimer: You can loop yourself to deal with this, use wisely.
+func (ctx *RequestContext) SetIndex(index int8) {
+	ctx.index = index
+}
+
 type HandlerFunc func(c context.Context, ctx *RequestContext)
 
 // HandlersChain defines a HandlerFunc array.
 type HandlersChain []HandlerFunc
 
-var handlerNames = make(map[uintptr]string)
+type HandlerNameOperator interface {
+	SetHandlerName(handler HandlerFunc, name string)
+	GetHandlerName(handler HandlerFunc) string
+}
+
+func SetHandlerNameOperator(o HandlerNameOperator) {
+	inbuiltHandlerNameOperator = o
+}
+
+type inbuiltHandlerNameOperatorStruct struct {
+	handlerNames map[uintptr]string
+}
+
+func (o *inbuiltHandlerNameOperatorStruct) SetHandlerName(handler HandlerFunc, name string) {
+	o.handlerNames[getFuncAddr(handler)] = name
+}
+
+func (o *inbuiltHandlerNameOperatorStruct) GetHandlerName(handler HandlerFunc) string {
+	return o.handlerNames[getFuncAddr(handler)]
+}
+
+type concurrentHandlerNameOperatorStruct struct {
+	handlerNames map[uintptr]string
+	lock         sync.RWMutex
+}
+
+func (o *concurrentHandlerNameOperatorStruct) SetHandlerName(handler HandlerFunc, name string) {
+	o.lock.Lock()
+	defer o.lock.Unlock()
+	o.handlerNames[getFuncAddr(handler)] = name
+}
+
+func (o *concurrentHandlerNameOperatorStruct) GetHandlerName(handler HandlerFunc) string {
+	o.lock.RLock()
+	defer o.lock.RUnlock()
+	return o.handlerNames[getFuncAddr(handler)]
+}
+
+func SetConcurrentHandlerNameOperator() {
+	SetHandlerNameOperator(&concurrentHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}})
+}
+
+func init() {
+	inbuiltHandlerNameOperator = &inbuiltHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}}
+}
+
+var inbuiltHandlerNameOperator HandlerNameOperator
 
 func SetHandlerName(handler HandlerFunc, name string) {
-	handlerNames[getFuncAddr(handler)] = name
+	inbuiltHandlerNameOperator.SetHandlerName(handler, name)
 }
 
 func GetHandlerName(handler HandlerFunc) string {
-	return handlerNames[getFuncAddr(handler)]
+	return inbuiltHandlerNameOperator.GetHandlerName(handler)
 }
 
 func getFuncAddr(v interface{}) uintptr {
diff --git a/pkg/common/adaptor/response.go b/pkg/common/adaptor/response.go
index e981007..4dbc0fa 100644
--- a/pkg/common/adaptor/response.go
+++ b/pkg/common/adaptor/response.go
@@ -54,7 +54,7 @@ func (c *compatResponse) WriteHeader(statusCode int) {
 				}
 				if k == consts.HeaderSetCookie {
 					cookie := protocol.AcquireCookie()
-					cookie.Parse(vv)
+					_ = cookie.Parse(vv)
 					c.h.Header.SetCookie(cookie)
 					continue
 				}
diff --git a/pkg/protocol/cookie.go b/pkg/protocol/cookie.go
index 7ca22c6..4477a89 100644
--- a/pkg/protocol/cookie.go
+++ b/pkg/protocol/cookie.go
@@ -65,6 +65,8 @@ const (
 	CookieSameSiteStrictMode
 	// CookieSameSiteNoneMode sets the SameSite flag with the "None" parameter
 	// see https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
+	// third-party cookies are phasing out, use Partitioned cookies instead
+	// see https://developers.google.com/privacy-sandbox/3pcd
 	CookieSameSiteNoneMode
 )
 
@@ -101,7 +103,10 @@ type Cookie struct {
 
 	httpOnly bool
 	secure   bool
-	sameSite CookieSameSite
+	// A partitioned third-party cookie is tied to the top-level site
+	// where it's initially set and cannot be accessed from elsewhere.
+	partitioned bool
+	sameSite    CookieSameSite
 
 	bufKV argsKV
 	buf   []byte
@@ -222,6 +227,12 @@ func (c *Cookie) AppendBytes(dst []byte) []byte {
 	case CookieSameSiteNoneMode:
 		dst = appendCookiePart(dst, bytestr.StrCookieSameSite, bytestr.StrCookieSameSiteNone)
 	}
+
+	if c.partitioned {
+		dst = append(dst, ';', ' ')
+		dst = append(dst, bytestr.StrCookiePartitioned...)
+	}
+
 	return dst
 }
 
@@ -337,6 +348,7 @@ func (c *Cookie) Reset() {
 	c.httpOnly = false
 	c.secure = false
 	c.sameSite = CookieSameSiteDisabled
+	c.partitioned = false
 }
 
 // Value returns cookie value.
@@ -438,9 +450,14 @@ func (c *Cookie) ParseBytes(src []byte) error {
 				} else if utils.CaseInsensitiveCompare(bytestr.StrCookieSameSite, kv.value) {
 					c.sameSite = CookieSameSiteDefaultMode
 				}
+			case 'p': // "partitioned"
+				if utils.CaseInsensitiveCompare(bytestr.StrCookiePartitioned, kv.value) {
+					c.partitioned = true
+				}
 			}
 		} // else empty or no match
 	}
+
 	return nil
 }
 
@@ -496,6 +513,11 @@ func (c *Cookie) SameSite() CookieSameSite {
 	return c.sameSite
 }
 
+// Partitioned returns if cookie is partitioned.
+func (c *Cookie) Partitioned() bool {
+	return c.partitioned
+}
+
 // SetSameSite sets the cookie's SameSite flag to the given value.
 // set value CookieSameSiteNoneMode will set Secure to true also to avoid browser rejection
 func (c *Cookie) SetSameSite(mode CookieSameSite) {
@@ -515,6 +537,14 @@ func (c *Cookie) SetHTTPOnly(httpOnly bool) {
 	c.httpOnly = httpOnly
 }
 
+// SetPartitioned sets cookie as partitioned. Setting Partitioned to true will also set Secure.
+func (c *Cookie) SetPartitioned(partitioned bool) {
+	c.partitioned = partitioned
+	if partitioned {
+		c.SetSecure(true)
+	}
+}
+
 // String returns cookie representation.
 func (c *Cookie) String() string {
 	return string(c.Cookie())
diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go
index 7edf19b..278b137 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -1298,7 +1298,6 @@ func (h *RequestHeader) SetCookie(key, value string) {
 }
 
 // SetCookie sets the given response cookie.
-//
 // It is save re-using the cookie after the function returns.
 func (h *ResponseHeader) SetCookie(cookie *Cookie) {
 	h.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), ArgsHasValue)
@@ -1657,12 +1656,28 @@ func (h *ResponseHeader) GetAll(key string) []string {
 }
 
 func appendHeaderLine(dst, key, value []byte) []byte {
+	for _, k := range key {
+		// if header field contains invalid key, just skip it.
+		if bytesconv.ValidHeaderFieldNameTable[k] == 0 {
+			return dst
+		}
+	}
 	dst = append(dst, key...)
 	dst = append(dst, bytestr.StrColonSpace...)
-	dst = append(dst, value...)
+	dst = append(dst, newlineToSpace(value)...)
 	return append(dst, bytestr.StrCRLF...)
 }
 
+// newlineToSpace will return a copy of the original byte slice.
+func newlineToSpace(val []byte) []byte {
+	filteredVal := make([]byte, len(val))
+	copy(filteredVal, val)
+	for i := 0; i < len(filteredVal); i++ {
+		filteredVal[i] = bytesconv.NewlineToSpaceTable[filteredVal[i]]
+	}
+	return filteredVal
+}
+
 func UpdateServerDate() {
 	refreshServerDate()
 	go func() {
diff --git a/pkg/protocol/http1/req/header.go b/pkg/protocol/http1/req/header.go
index 4d571a3..442e344 100644
--- a/pkg/protocol/http1/req/header.go
+++ b/pkg/protocol/http1/req/header.go
@@ -47,6 +47,7 @@ import (
 	"fmt"
 	"io"
 
+	"github.com/cloudwego/hertz/internal/bytesconv"
 	"github.com/cloudwego/hertz/internal/bytestr"
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/utils"
@@ -165,6 +166,16 @@ func parseFirstLine(h *protocol.RequestHeader, buf []byte) (int, error) {
 	return len(buf) - len(bNext), nil
 }
 
+// validHeaderFieldValue is equal to httpguts.ValidHeaderFieldValue（shares the same context）
+func validHeaderFieldValue(val []byte) bool {
+	for _, v := range val {
+		if bytesconv.ValidHeaderFieldValueTable[v] == 0 {
+			return false
+		}
+	}
+	return true
+}
+
 func parseHeaders(h *protocol.RequestHeader, buf []byte) (int, error) {
 	h.InitContentLengthWithValue(-2)
 
@@ -178,7 +189,13 @@ func parseHeaders(h *protocol.RequestHeader, buf []byte) (int, error) {
 			// See RFC 7230, Section 3.2.4.
 			if bytes.IndexByte(s.Key, ' ') != -1 || bytes.IndexByte(s.Key, '\t') != -1 {
 				err = fmt.Errorf("invalid header key %q", s.Key)
-				continue
+				return 0, err
+			}
+
+			// Check the invalid chars in header value
+			if !validHeaderFieldValue(s.Value) {
+				err = fmt.Errorf("invalid header value %q", s.Value)
+				return 0, err
 			}
 
 			switch s.Key[0] | 0x20 {
diff --git a/pkg/protocol/http1/resp/header.go b/pkg/protocol/http1/resp/header.go
index 18a0f8a..ff12cb2 100644
--- a/pkg/protocol/http1/resp/header.go
+++ b/pkg/protocol/http1/resp/header.go
@@ -85,7 +85,7 @@ func ReadHeader(h *protocol.ResponseHeader, r network.Reader) error {
 	}
 }
 
-// Write writes response header to w.
+// WriteHeader writes response header to w.
 func WriteHeader(h *protocol.ResponseHeader, w network.Writer) error {
 	header := h.Header()
 	h.SetHeaderLength(len(header))
diff --git a/version.go b/version.go
index fef300f..b6c0efd 100644
--- a/version.go
+++ b/version.go
@@ -19,5 +19,5 @@ package hertz
 // Name and Version info of this framework, used for statistics and debug
 const (
 	Name    = "Hertz"
-	Version = "v0.7.3"
+	Version = "v0.8.0"
 )


Test patch:
diff --git a/cmd/hz/protobuf/tag_test.go b/cmd/hz/protobuf/tag_test.go
index 7e6528b..2e7a9ca 100644
--- a/cmd/hz/protobuf/tag_test.go
+++ b/cmd/hz/protobuf/tag_test.go
@@ -126,7 +126,7 @@ func TestTagGenerate(t *testing.T) {
 					tags := structTags{
 						{"protobuf", fieldProtobufTagValue(field)},
 					}
-					err = injectTagsToStructTags(field.Desc, &tags, true)
+					err = injectTagsToStructTags(field.Desc, &tags, true, nil)
 					if err != nil {
 						t.Fatal(err)
 					}
diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go
index cf7a9a4..316c917 100644
--- a/internal/bytesconv/bytesconv_test.go
+++ b/internal/bytesconv/bytesconv_test.go
@@ -18,6 +18,7 @@ package bytesconv
 
 import (
 	"net/url"
+	"strings"
 	"testing"
 	"time"
 
@@ -183,3 +184,66 @@ func TestParseHTTPDate(t *testing.T) {
 		assert.DeepEqual(t, t1, t2)
 	}
 }
+
+// For test only, but it will import golang.org/x/net/http.
+// So comment out all this code. Keep this for the full context.
+//func TestValidHeaderFieldValueTable(t *testing.T) {
+//	t.Parallel()
+//
+//	// Test all characters
+//	allBytes := make([]byte, 0)
+//	for i := 0; i < 256; i++ {
+//		allBytes = append(allBytes, byte(i))
+//	}
+//	for _, s := range allBytes {
+//		ss := []byte{s}
+//		expectedS := httpguts.ValidHeaderFieldValue(string(ss))
+//		res := func() bool {
+//			return ValidHeaderFieldValueTable[s] != 0
+//		}()
+//
+//		assert.DeepEqual(t, expectedS, res)
+//	}
+//}
+
+func TestNewlineToSpaceTable(t *testing.T) {
+	t.Parallel()
+	// Test all characters
+	allBytes := make([]byte, 0)
+	for i := 0; i < 256; i++ {
+		allBytes = append(allBytes, byte(i))
+	}
+
+	headerNewlineToSpace := strings.NewReplacer("\n", " ", "\r", " ")
+
+	expectedS := headerNewlineToSpace.Replace(string(allBytes))
+
+	res := make([]byte, len(allBytes))
+	copy(res, allBytes)
+	for i := 0; i < len(res); i++ {
+		res[i] = NewlineToSpaceTable[res[i]]
+	}
+
+	assert.DeepEqual(t, expectedS, string(res))
+}
+
+// For test only, but it will import golang.org/x/net/http.
+// So comment out all this code. Keep this for the full context.
+//func TestValidHeaderFieldNameTable(t *testing.T) {
+//	t.Parallel()
+//
+//	// Test all characters
+//	allBytes := make([]byte, 0)
+//	for i := 0; i < 256; i++ {
+//		allBytes = append(allBytes, byte(i))
+//	}
+//	for _, s := range allBytes {
+//		ss := []byte{s}
+//		expectedS := httpguts.ValidHeaderFieldName(string(ss))
+//		res := func() bool {
+//			return ValidHeaderFieldNameTable[s] != 0
+//		}()
+//
+//		assert.DeepEqual(t, expectedS, res)
+//	}
+//}
diff --git a/internal/bytesconv/bytesconv_timing_test.go b/internal/bytesconv/bytesconv_timing_test.go
new file mode 100644
index 0000000..3f913a0
--- /dev/null
+++ b/internal/bytesconv/bytesconv_timing_test.go
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2024 CloudWeGo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package bytesconv
+
+import (
+	"strings"
+	"testing"
+)
+
+// For test only, but it will import golang.org/x/net/http.
+// So comment out all this code. Keep this for the full context.
+//func BenchmarkValidHeaderFiledValueTable(b *testing.B) {
+//	// Test all characters
+//	allBytes := make([]string, 0)
+//	for i := 0; i < 256; i++ {
+//		allBytes = append(allBytes, string([]byte{byte(i)}))
+//	}
+//
+//	for i := 0; i < b.N; i++ {
+//		for _, s := range allBytes {
+//			_ = httpguts.ValidHeaderFieldValue(s)
+//		}
+//	}
+//}
+
+func BenchmarkValidHeaderFiledValueTableHertz(b *testing.B) {
+	// Test all characters
+	allBytes := make([]byte, 0)
+	for i := 0; i < 256; i++ {
+		allBytes = append(allBytes, byte(i))
+	}
+
+	for i := 0; i < b.N; i++ {
+		for _, s := range allBytes {
+			_ = func() bool {
+				return ValidHeaderFieldValueTable[s] != 0
+			}()
+		}
+	}
+}
+
+func BenchmarkNewlineToSpace(b *testing.B) {
+	// Test all characters
+	allBytes := make([]byte, 0)
+	for i := 0; i < 256; i++ {
+		allBytes = append(allBytes, byte(i))
+	}
+	headerNewlineToSpace := strings.NewReplacer("\n", " ", "\r", " ")
+
+	for i := 0; i < b.N; i++ {
+		_ = headerNewlineToSpace.Replace(string(allBytes))
+	}
+}
+
+func BenchmarkNewlineToSpaceHertz01(b *testing.B) {
+	// Test all characters
+	allBytes := make([]byte, 0)
+	for i := 0; i < 256; i++ {
+		allBytes = append(allBytes, byte(i))
+	}
+
+	for i := 0; i < b.N; i++ {
+		filteredVal := make([]byte, 0, len(allBytes))
+		for i := 0; i < len(allBytes); i++ {
+			filteredVal = append(filteredVal, NewlineToSpaceTable[allBytes[i]])
+		}
+		_ = filteredVal
+	}
+}
+
+func BenchmarkNewlineToSpaceHertz02(b *testing.B) {
+	// Test all characters
+	allBytes := make([]byte, 0)
+	for i := 0; i < 256; i++ {
+		allBytes = append(allBytes, byte(i))
+	}
+
+	for i := 0; i < b.N; i++ {
+		filteredVal := make([]byte, len(allBytes))
+		copy(filteredVal, allBytes)
+		for ii := 0; ii < len(allBytes); ii++ {
+			filteredVal[ii] = NewlineToSpaceTable[filteredVal[ii]]
+		}
+	}
+}
diff --git a/internal/bytestr/bytes.go b/internal/bytestr/bytes.go
index a93bd2d..bbf3dcb 100644
--- a/internal/bytestr/bytes.go
+++ b/internal/bytestr/bytes.go
@@ -97,6 +97,7 @@ var (
 	StrCookieSameSiteLax    = []byte("Lax")
 	StrCookieSameSiteStrict = []byte("Strict")
 	StrCookieSameSiteNone   = []byte("None")
+	StrCookiePartitioned    = []byte("Partitioned")
 
 	StrClose               = []byte("close")
 	StrGzip                = []byte("gzip")
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
index c065d48..85d686c 100644
--- a/pkg/app/context_test.go
+++ b/pkg/app/context_test.go
@@ -1344,16 +1344,44 @@ func TestGetWriter(t *testing.T) {
 func TestIndex(t *testing.T) {
 	ctx := NewContext(0)
 	ctx.ResetWithoutConn()
-	res := ctx.GetIndex()
 	exc := int8(-1)
+	res := ctx.GetIndex()
+	assert.DeepEqual(t, exc, res)
+	ctx.SetIndex(int8(1))
+	res = ctx.GetIndex()
+	exc = int8(1)
 	assert.DeepEqual(t, exc, res)
 }
 
+func TestConcurrentHandlerName(t *testing.T) {
+	SetConcurrentHandlerNameOperator()
+	defer SetHandlerNameOperator(&inbuiltHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}})
+	h := func(c context.Context, ctx *RequestContext) {}
+	SetHandlerName(h, "test1")
+	for i := 0; i < 50; i++ {
+		go func() {
+			name := GetHandlerName(h)
+			assert.DeepEqual(t, "test1", name)
+		}()
+	}
+
+	time.Sleep(time.Second)
+
+	go func() {
+		SetHandlerName(h, "test2")
+	}()
+
+	time.Sleep(time.Second)
+
+	name := GetHandlerName(h)
+	assert.DeepEqual(t, "test2", name)
+}
+
 func TestHandlerName(t *testing.T) {
 	h := func(c context.Context, ctx *RequestContext) {}
-	SetHandlerName(h, "test")
+	SetHandlerName(h, "test1")
 	name := GetHandlerName(h)
-	assert.DeepEqual(t, "test", name)
+	assert.DeepEqual(t, "test1", name)
 }
 
 func TestHijack(t *testing.T) {
@@ -1644,3 +1672,23 @@ func TestRequestContext_VisitAll(t *testing.T) {
 			})
 	})
 }
+
+func BenchmarkInbuiltHandlerNameOperator(b *testing.B) {
+	for n := 0; n < b.N; n++ {
+		fn := func(c context.Context, ctx *RequestContext) {
+		}
+		SetHandlerName(fn, fmt.Sprintf("%d", n))
+		GetHandlerName(fn)
+	}
+}
+
+func BenchmarkConcurrentHandlerNameOperator(b *testing.B) {
+	SetConcurrentHandlerNameOperator()
+	defer SetHandlerNameOperator(&inbuiltHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}})
+	for n := 0; n < b.N; n++ {
+		fn := func(c context.Context, ctx *RequestContext) {
+		}
+		SetHandlerName(fn, fmt.Sprintf("%d", n))
+		GetHandlerName(fn)
+	}
+}
diff --git a/pkg/protocol/cookie_test.go b/pkg/protocol/cookie_test.go
index f7637e5..d799441 100644
--- a/pkg/protocol/cookie_test.go
+++ b/pkg/protocol/cookie_test.go
@@ -67,6 +67,9 @@ func TestCookieAppendBytes(t *testing.T) {
 
 	c.SetExpire(CookieExpireDelete)
 	testCookieAppendBytes(t, c, "xxx", "yyy", "xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b")
+
+	c.SetPartitioned(true)
+	testCookieAppendBytes(t, c, "xxx", "yyy", "xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b; secure; Partitioned")
 }
 
 func testCookieAppendBytes(t *testing.T, c *Cookie, key, value, expectedS string) {
@@ -257,6 +260,31 @@ func TestCookieSameSite(t *testing.T) {
 	}
 }
 
+func TestCookiePartitioned(t *testing.T) {
+	t.Parallel()
+
+	var c Cookie
+
+	if err := c.Parse("__Host-name=value; Secure; Path=/; SameSite=None; Partitioned;"); err != nil {
+		t.Fatalf("unexpected error for valid paritionedd cookie: %s", err)
+	}
+	if !c.Partitioned() {
+		t.Fatalf("partitioned must be set")
+	}
+
+	if err := c.Parse("foo=bar"); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	c.SetPartitioned(true)
+	s := c.String()
+	if !strings.Contains(s, "; Partitioned") {
+		t.Fatalf("missing Partitioned flag in cookie %q", s)
+	}
+	if !strings.Contains(s, "; secure") {
+		t.Fatalf("missing Secure flag in cookie %q", s)
+	}
+}
+
 func TestCookieMaxAge(t *testing.T) {
 	t.Parallel()
 
diff --git a/pkg/protocol/http1/resp/response_test.go b/pkg/protocol/http1/resp/response_test.go
index 0ff010f..cff0a57 100644
--- a/pkg/protocol/http1/resp/response_test.go
+++ b/pkg/protocol/http1/resp/response_test.go
@@ -820,9 +820,3 @@ func TestResponseReadBodyStreamBadTrailer(t *testing.T) {
 	testResponseReadBodyStreamBadTrailer(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\ncontent-type: bar\r\n\r\n")
 	testResponseReadBodyStreamBadTrailer(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nproxy-connection: bar2\r\n\r\n")
 }
-
-func TestResponseString(t *testing.T) {
-	resp := protocol.Response{}
-	resp.Header.Set("Location", "foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n")
-	assert.True(t, strings.Contains(GetHTTP1Response(&resp).String(), "Location: foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n"))
-}