Repository: cloudwego/hertz
Pull request #1039 (2024-01-02 08:31:10)

optimize: filter invalid char in header
#### What type of PR is this?
<!--
Add one of the following kinds:

build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
docs: Documentation only changes
feat: A new feature
optimize: A new optimization
fix: A bug fix
perf: A code change that improves performance
refactor: A code change that neither fixes a bug nor adds a feature
style: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc)
test: Adding missing tests or correcting existing tests
chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
-->

#### Check the PR title.
<!--
The description of the title will be attached in Release Notes, 
so please describe it from user-oriented, what this PR does / why we need it.
Please check your PR title with the below requirements:
-->
- [x] This PR title match the format: \<type\>(optional scope): \<description\>
- [x] The description of this PR title is user-oriented and clear enough for others to understand.
- [ ] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)


#### (Optional) Translate the PR title into Chinese.


#### (Optional) More detailed description for this PR(en: English/zh: Chinese).
<!--
Provide more detailed info for review(e.g., it's recommended to provide perf data if this is a perf type PR).
-->
en:
![image](https://github.com/cloudwego/hertz/assets/9826697/db303f1b-488f-4363-8e7c-d607857c6147)

zh(optional):

#### (Optional) Which issue(s) this PR fixes:
<!--
Automatically closes linked issue when PR is merged.
Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->

#### (Optional) The PR that updates user documentation:
<!--
If the current PR requires user awareness at the usage level, please submit a PR to update user docs. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)
-->

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cloudwego/hertz/pull/1039?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`9cd43c4`)](https://app.codecov.io/gh/cloudwego/hertz/commit/9cd43c4886da0480786207467b0629d65f257971?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 82.56% compared to head [(`d2f2fa6`)](https://app.codecov.io/gh/cloudwego/hertz/pull/1039?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 82.41%.


<details><summary>Additional details and impacted files</summary>


```diff
@@                           Coverage Diff                           @@
##           optimize/get_rid_of_invalid_headers    #1039      +/-   ##
=======================================================================
- Coverage                                82.56%   82.41%   -0.15%     
=======================================================================
  Files                                       98       98              
  Lines                                     9982     9988       +6     
=======================================================================
- Hits                                      8242     8232      -10     
- Misses                                    1243     1260      +17     
+ Partials                                   497      496       -1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cloudwego/hertz/pull/1039?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).


Patch:
diff --git a/go.mod b/go.mod
index 509c881..3619b83 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,6 @@ require (
 	github.com/cloudwego/netpoll v0.5.0
 	github.com/fsnotify/fsnotify v1.5.4
 	github.com/tidwall/gjson v1.14.4
-	golang.org/x/net v0.0.0-20190311183353-d8887717615a
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
 	google.golang.org/protobuf v1.27.1
diff --git a/go.sum b/go.sum
index 26da89b..3cf3c2f 100644
--- a/go.sum
+++ b/go.sum
@@ -64,7 +64,6 @@ golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5P
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -72,7 +71,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
diff --git a/internal/bytesconv/bytesconv_table.go b/internal/bytesconv/bytesconv_table.go
index c501d92..5954957 100644
--- a/internal/bytesconv/bytesconv_table.go
+++ b/internal/bytesconv/bytesconv_table.go
@@ -52,4 +52,6 @@ const (
 	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 2e2722d..eff361b 100644
--- a/internal/bytesconv/bytesconv_table_gen.go
+++ b/internal/bytesconv/bytesconv_table_gen.go
@@ -178,6 +178,65 @@ func main() {
 		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 (\n")
@@ -188,6 +247,8 @@ func main() {
 	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 {
diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go
index 7edf19b..d53f12a 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -1657,12 +1657,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/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))


Test patch:
diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go
index 4218a82..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"
 
@@ -25,7 +26,6 @@ import (
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 	"github.com/cloudwego/hertz/pkg/common/test/mock"
 	"github.com/cloudwego/hertz/pkg/network"
-	"golang.org/x/net/http/httpguts"
 )
 
 func TestAppendDate(t *testing.T) {
@@ -185,21 +185,65 @@ func TestParseHTTPDate(t *testing.T) {
 	}
 }
 
-func TestValidHeaderFieldValueTable(t *testing.T) {
+// 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))
 	}
-	for _, s := range allBytes {
-		ss := []byte{s}
-		expectedS := httpguts.ValidHeaderFieldValue(string(ss))
-		res := func() bool {
-			return ValidHeaderFieldValueTable[s] != 0
-		}()
 
-		assert.DeepEqual(t, expectedS, res)
+	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
index 6fdd28b..3f913a0 100644
--- a/internal/bytesconv/bytesconv_timing_test.go
+++ b/internal/bytesconv/bytesconv_timing_test.go
@@ -17,37 +17,83 @@
 package bytesconv
 
 import (
+	"strings"
 	"testing"
-
-	"golang.org/x/net/http/httpguts"
 )
 
-func BenchmarkValidHeaderFiledValueTable(b *testing.B) {
+// 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([]string, 0)
+	allBytes := make([]byte, 0)
 	for i := 0; i < 256; i++ {
-		allBytes = append(allBytes, string([]byte{byte(i)}))
+		allBytes = append(allBytes, byte(i))
 	}
 
 	for i := 0; i < b.N; i++ {
 		for _, s := range allBytes {
-			_ = httpguts.ValidHeaderFieldValue(s)
+			_ = func() bool {
+				return ValidHeaderFieldValueTable[s] != 0
+			}()
 		}
 	}
 }
 
-func BenchmarkValidHeaderFiledValueTableHertz(b *testing.B) {
+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++ {
-		for _, s := range allBytes {
-			_ = func() bool {
-				return ValidHeaderFieldValueTable[s] != 0
-			}()
+		_ = 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/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"))
-}