Repository: cloudwego/hertz
Pull request #751 (2023-04-26 06:16:03)

feat: add GetProtocol and SetProtocol api for ResponseHeader
#### 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
-->
feat
#### 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.
- [x] 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.
为 ResponseHeader 添加 GetProtocol 和 SetProtocol 接口

#### (Optional) More detail description for this PR(en: English/zh: Chinese).
<!--
Provide more detailed info for review. If it is a perf type PR, perf data is suggested to give.
-->
en: hertz client's Response lacks a quick and easy way to determine the HTTP version of the Response, so adding `SetProtocol` and `GetProtocol` allows the user to determine the HTTP version of the Response
zh(optional): hertz client 的 Response 缺乏一种快速简便的方式去判断这个 Response 的 HTTP 版本，所以添加`SetProtocol` 和`GetProtocol`让用户可以判断 Response 的 HTTP 版本

#### (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://codecov.io/gh/cloudwego/hertz/pull/751?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
Patch coverage: **`63.63`**% and project coverage change: **`-0.02`** :warning:
> Comparison is base [(`2800a9b`)](https://codecov.io/gh/cloudwego/hertz/commit/2800a9bfde61d06968f96ef926e7e3acb78bef25?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 75.68% compared to head [(`82576ab`)](https://codecov.io/gh/cloudwego/hertz/pull/751?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 75.67%.

> :exclamation: Current head 82576ab differs from pull request most recent head 4f69592. Consider uploading reports for the commit 4f69592 to get more accurate results

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


```diff
@@             Coverage Diff             @@
##           develop     #751      +/-   ##
===========================================
- Coverage    75.68%   75.67%   -0.02%     
===========================================
  Files           97       97              
  Lines         9464     9474      +10     
===========================================
+ Hits          7163     7169       +6     
- Misses        1853     1857       +4     
  Partials       448      448              
```


| [Impacted Files](https://codecov.io/gh/cloudwego/hertz/pull/751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) | Coverage Δ | |
|---|---|---|
| [pkg/protocol/header.go](https://codecov.io/gh/cloudwego/hertz/pull/751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL3Byb3RvY29sL2hlYWRlci5nbw==) | `76.86% <20.00%> (-0.32%)` | :arrow_down: |
| [pkg/protocol/http1/resp/header.go](https://codecov.io/gh/cloudwego/hertz/pull/751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL3Byb3RvY29sL2h0dHAxL3Jlc3AvaGVhZGVyLmdv) | `80.15% <100.00%> (+0.78%)` | :arrow_up: |

... and [2 files with indirect coverage changes](https://codecov.io/gh/cloudwego/hertz/pull/751/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego)

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego)

</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/cloudwego/hertz/pull/751?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](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/pkg/protocol/header.go b/pkg/protocol/header.go
index 0e1d48a..b914057 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -66,8 +66,6 @@ type RequestHeader struct {
 	noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used
 
 	disableNormalizing   bool
-	noHTTP11             bool
-	protocol             string
 	connectionClose      bool
 	noDefaultContentType bool
 
@@ -82,8 +80,10 @@ type RequestHeader struct {
 	requestURI  []byte
 	host        []byte
 	contentType []byte
-	userAgent   []byte
-	mulHeader   [][]byte
+
+	userAgent []byte
+	mulHeader [][]byte
+	protocol  string
 
 	h       []argsKV
 	bufKV   argsKV
@@ -111,7 +111,6 @@ type ResponseHeader struct {
 	noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used
 
 	disableNormalizing   bool
-	noHTTP11             bool
 	connectionClose      bool
 	noDefaultContentType bool
 	noDefaultDate        bool
@@ -124,6 +123,7 @@ type ResponseHeader struct {
 	contentType []byte
 	server      []byte
 	mulHeader   [][]byte
+	protocol    string
 
 	h       []argsKV
 	bufKV   argsKV
@@ -173,8 +173,17 @@ func (h *ResponseHeader) PeekArgBytes(key []byte) []byte {
 	return peekArgBytes(h.h, key)
 }
 
+// Deprecated: Use ResponseHeader.SetProtocol(consts.HTTP11) instead
+//
+//	Now SetNoHTTP11(true) equal to SetProtocol(consts.HTTP10)
+//		SetNoHTTP11(false) equal to SetProtocol(consts.HTTP11)
 func (h *ResponseHeader) SetNoHTTP11(b bool) {
-	h.noHTTP11 = b
+	if b {
+		h.protocol = consts.HTTP10
+		return
+	}
+
+	h.protocol = consts.HTTP11
 }
 
 // Cookie fills cookie for the given cookie.Key.
@@ -196,7 +205,7 @@ func (h *ResponseHeader) FullCookie() []byte {
 
 // IsHTTP11 returns true if the response is HTTP/1.1.
 func (h *ResponseHeader) IsHTTP11() bool {
-	return !h.noHTTP11
+	return h.protocol == consts.HTTP11
 }
 
 // SetContentType sets Content-Type header value.
@@ -222,7 +231,6 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) {
 	dst.Reset()
 
 	dst.disableNormalizing = h.disableNormalizing
-	dst.noHTTP11 = h.noHTTP11
 	dst.connectionClose = h.connectionClose
 	dst.noDefaultContentType = h.noDefaultContentType
 	dst.noDefaultDate = h.noDefaultDate
@@ -289,7 +297,7 @@ func (h *ResponseHeader) VisitAll(f func(key, value []byte)) {
 
 // IsHTTP11 returns true if the request is HTTP/1.1.
 func (h *RequestHeader) IsHTTP11() bool {
-	return !h.noHTTP11
+	return h.protocol == consts.HTTP11
 }
 
 func (h *RequestHeader) SetProtocol(p string) {
@@ -300,8 +308,17 @@ func (h *RequestHeader) GetProtocol() string {
 	return h.protocol
 }
 
+// Deprecated: Use RequestHeader.SetProtocol(consts.HTTP11) instead
+//
+//	Now SetNoHTTP11(true) equal to SetProtocol(consts.HTTP10)
+//		SetNoHTTP11(false) equal to SetProtocol(consts.HTTP11)
 func (h *RequestHeader) SetNoHTTP11(b bool) {
-	h.noHTTP11 = b
+	if b {
+		h.protocol = consts.HTTP10
+		return
+	}
+
+	h.protocol = consts.HTTP11
 }
 
 func (h *RequestHeader) InitBufValue(size int) {
@@ -486,7 +503,7 @@ func checkWriteHeaderCode(code int) {
 }
 
 func (h *ResponseHeader) ResetSkipNormalize() {
-	h.noHTTP11 = false
+	h.protocol = ""
 	h.connectionClose = false
 
 	h.statusCode = 0
@@ -1075,7 +1092,6 @@ func (h *RequestHeader) CopyTo(dst *RequestHeader) {
 	dst.Reset()
 
 	dst.disableNormalizing = h.disableNormalizing
-	dst.noHTTP11 = h.noHTTP11
 	dst.connectionClose = h.connectionClose
 	dst.noDefaultContentType = h.noDefaultContentType
 
@@ -1395,7 +1411,6 @@ func (h *RequestHeader) SetCanonical(key, value []byte) {
 }
 
 func (h *RequestHeader) ResetSkipNormalize() {
-	h.noHTTP11 = false
 	h.connectionClose = false
 	h.protocol = ""
 	h.noDefaultContentType = false
@@ -1803,3 +1818,11 @@ func (h *RequestHeader) Trailer() *Trailer {
 	}
 	return h.trailer
 }
+
+func (h *ResponseHeader) SetProtocol(p string) {
+	h.protocol = p
+}
+
+func (h *ResponseHeader) GetProtocol() string {
+	return h.protocol
+}
diff --git a/pkg/protocol/http1/req/header.go b/pkg/protocol/http1/req/header.go
index da7e729..4d571a3 100644
--- a/pkg/protocol/http1/req/header.go
+++ b/pkg/protocol/http1/req/header.go
@@ -153,13 +153,11 @@ func parseFirstLine(h *protocol.RequestHeader, buf []byte) (int, error) {
 	// parse requestURI
 	n = bytes.LastIndexByte(b, ' ')
 	if n < 0 {
-		h.SetNoHTTP11(true)
 		h.SetProtocol(consts.HTTP10)
 		n = len(b)
 	} else if n == 0 {
 		return 0, fmt.Errorf("requestURI cannot be empty in %q", buf)
 	} else if !bytes.Equal(b[n+1:], bytestr.StrHTTP11) {
-		h.SetNoHTTP11(true)
 		h.SetProtocol(consts.HTTP10)
 	}
 	h.SetRequestURIBytes(b[:n])
diff --git a/pkg/protocol/http1/resp/header.go b/pkg/protocol/http1/resp/header.go
index 622c72b..18a0f8a 100644
--- a/pkg/protocol/http1/resp/header.go
+++ b/pkg/protocol/http1/resp/header.go
@@ -239,7 +239,14 @@ func parseFirstLine(h *protocol.ResponseHeader, buf []byte) (int, error) {
 	if n < 0 {
 		return 0, fmt.Errorf("cannot find whitespace in the first line of response %q", buf)
 	}
-	h.SetNoHTTP11(!bytes.Equal(b[:n], bytestr.StrHTTP11))
+
+	isHTTP11 := bytes.Equal(b[:n], bytestr.StrHTTP11)
+	if !isHTTP11 {
+		h.SetProtocol(consts.HTTP10)
+	} else {
+		h.SetProtocol(consts.HTTP11)
+	}
+
 	b = b[n+1:]
 
 	// parse status code


Test patch:
diff --git a/pkg/protocol/header_test.go b/pkg/protocol/header_test.go
index a48e663..f7c8557 100644
--- a/pkg/protocol/header_test.go
+++ b/pkg/protocol/header_test.go
@@ -70,18 +70,18 @@ func TestResponseHeaderSetHeaderLength(t *testing.T) {
 func TestSetNoHTTP11(t *testing.T) {
 	rh := ResponseHeader{}
 	rh.SetNoHTTP11(true)
-	assert.True(t, rh.noHTTP11)
+	assert.DeepEqual(t, consts.HTTP10, rh.protocol)
 
 	rh.SetNoHTTP11(false)
-	assert.False(t, rh.noHTTP11)
+	assert.DeepEqual(t, consts.HTTP11, rh.protocol)
 	assert.True(t, rh.IsHTTP11())
 
 	h := RequestHeader{}
 	h.SetNoHTTP11(true)
-	assert.True(t, h.noHTTP11)
+	assert.DeepEqual(t, consts.HTTP10, h.protocol)
 
 	h.SetNoHTTP11(false)
-	assert.False(t, h.noHTTP11)
+	assert.DeepEqual(t, consts.HTTP11, h.protocol)
 	assert.True(t, h.IsHTTP11())
 }
 
diff --git a/pkg/protocol/http1/resp/response_test.go b/pkg/protocol/http1/resp/response_test.go
index af146f0..01a38c1 100644
--- a/pkg/protocol/http1/resp/response_test.go
+++ b/pkg/protocol/http1/resp/response_test.go
@@ -157,11 +157,11 @@ func testResponseReadError(t *testing.T, resp *protocol.Response, response strin
 	}
 
 	testResponseReadSuccess(t, resp, "HTTP/1.1 303 Redisred sedfs sdf\r\nContent-Type: aaa\r\nContent-Length: 5\r\n\r\nHELLOaaa",
-		consts.StatusSeeOther, 5, "aaa", "HELLO", nil)
+		consts.StatusSeeOther, 5, "aaa", "HELLO", nil, consts.HTTP11)
 }
 
 func testResponseReadSuccess(t *testing.T, resp *protocol.Response, response string, expectedStatusCode, expectedContentLength int,
-	expectedContentType, expectedBody string, expectedTrailer map[string]string,
+	expectedContentType, expectedBody string, expectedTrailer map[string]string, expectedProtocol string,
 ) {
 	zr := mock.NewZeroCopyReader(response)
 	err := Read(resp, zr)
@@ -169,7 +169,7 @@ func testResponseReadSuccess(t *testing.T, resp *protocol.Response, response str
 		t.Fatalf("Unexpected error: %s", err)
 	}
 
-	verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "")
+	verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "", expectedProtocol)
 	if !bytes.Equal(resp.Body(), []byte(expectedBody)) {
 		t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody))
 	}
@@ -183,46 +183,46 @@ func TestResponseReadSuccess(t *testing.T) {
 
 	// usual response
 	testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789",
-		consts.StatusOK, 10, "foo/bar", "0123456789", nil)
+		consts.StatusOK, 10, "foo/bar", "0123456789", nil, consts.HTTP11)
 
 	// zero response
 	testResponseReadSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n",
-		consts.StatusInternalServerError, 0, "foo/bar", "", nil)
+		consts.StatusInternalServerError, 0, "foo/bar", "", nil, consts.HTTP11)
 
 	// response with trailer
 	testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n",
-		consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"Foo": "bar"})
+		consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"Foo": "bar"}, consts.HTTP11)
 
 	// response with trailer disableNormalizing
 	resp.Header.DisableNormalizing()
 	resp.Header.Trailer().DisableNormalizing()
 	testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n",
-		consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"foo": "bar"})
+		consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"foo": "bar"}, consts.HTTP11)
 
 	// no content-length ('identity' transfer-encoding)
 	testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxxx",
-		consts.StatusOK, 5, "foobar", "zxxxx", nil)
+		consts.StatusOK, 5, "foobar", "zxxxx", nil, consts.HTTP11)
 
 	// explicitly stated 'Transfer-Encoding: identity'
 	testResponseReadSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag",
-		234, 3, "xxx", "xag", nil)
+		234, 3, "xxx", "xag", nil, consts.HTTP11)
 
 	// big 'identity' response
 	body := string(mock.CreateFixedBody(100500))
 	testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body,
-		consts.StatusOK, 100500, "aa", body, nil)
+		consts.StatusOK, 100500, "aa", body, nil, consts.HTTP11)
 
 	// chunked response
 	testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo2\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nFoo2: bar2\r\n\r\n",
-		200, 6, "text/html", "qwerty", map[string]string{"Foo2": "bar2"})
+		200, 6, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}, consts.HTTP11)
 
 	// chunked response with non-chunked Transfer-Encoding.
 	testResponseReadSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTrailer: Foo3\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\nFoo3: bar3\r\n\r\n",
-		230, 4, "text", "erty", map[string]string{"Foo3": "bar3"})
+		230, 4, "text", "erty", map[string]string{"Foo3": "bar3"}, consts.HTTP11)
 
 	// chunked response with empty body
 	testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo5\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo5: bar5\r\n\r\n",
-		consts.StatusOK, 0, "text/html", "", map[string]string{"Foo5": "bar5"})
+		consts.StatusOK, 0, "text/html", "", map[string]string{"Foo5": "bar5"}, consts.HTTP11)
 }
 
 func TestResponseReadError(t *testing.T) {
@@ -416,23 +416,23 @@ func TestResponseReadWithoutBody(t *testing.T) {
 	var resp protocol.Response
 
 	testResponseReadWithoutBody(t, &resp, "HTTP/1.1 304 Not Modified\r\nContent-Type: aa\r\nContent-Encoding: gzip\r\nContent-Length: 1235\r\n\r\n", false,
-		consts.StatusNotModified, 1235, "aa", nil, "gzip")
+		consts.StatusNotModified, 1235, "aa", nil, "gzip", consts.HTTP11)
 
 	testResponseReadWithoutBody(t, &resp, "HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTrailer: Foo\r\nContent-Encoding: deflate\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo: bar\r\n\r\n", false,
-		consts.StatusNoContent, -1, "aab", map[string]string{"Foo": "bar"}, "deflate")
+		consts.StatusNoContent, -1, "aab", map[string]string{"Foo": "bar"}, "deflate", consts.HTTP11)
 
 	testResponseReadWithoutBody(t, &resp, "HTTP/1.1 123 AAA\r\nContent-Type: xxx\r\nContent-Encoding: gzip\r\nContent-Length: 3434\r\n\r\n", false,
-		123, 3434, "xxx", nil, "gzip")
+		123, 3434, "xxx", nil, "gzip", consts.HTTP11)
 
 	testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Encoding: deflate\r\nContent-Length: 123\r\n\r\nfoobar\r\n", true,
-		consts.StatusOK, 123, "text/xml", nil, "deflate")
+		consts.StatusOK, 123, "text/xml", nil, "deflate", consts.HTTP10)
 
 	// '100 Continue' must be skipped.
 	testResponseReadWithoutBody(t, &resp, "HTTP/1.1 100 Continue\r\nFoo-bar: baz\r\n\r\nHTTP/1.1 329 aaa\r\nContent-Type: qwe\r\nContent-Encoding: gzip\r\nContent-Length: 894\r\n\r\n", true,
-		329, 894, "qwe", nil, "gzip")
+		329, 894, "qwe", nil, "gzip", consts.HTTP11)
 }
 
-func verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding string) {
+func verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding, expectedProtocol string) {
 	if h.StatusCode() != expectedStatusCode {
 		t.Fatalf("Unexpected status code %d. Expected %d", h.StatusCode(), expectedStatusCode)
 	}
@@ -445,6 +445,10 @@ func verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStat
 	if string(h.ContentEncoding()) != expectedContentEncoding {
 		t.Fatalf("Unexpected content encoding %q. Expected %q", h.ContentEncoding(), expectedContentEncoding)
 	}
+
+	if h.GetProtocol() != expectedProtocol {
+		t.Fatalf("Unexpected protocol %q. Expected %q", h.GetProtocol(), expectedProtocol)
+	}
 }
 
 func testResponseSuccess(t *testing.T, statusCode int, contentType, serverName, body string,
@@ -492,7 +496,7 @@ func testResponseSuccess(t *testing.T, statusCode int, contentType, serverName,
 }
 
 func testResponseReadWithoutBody(t *testing.T, resp *protocol.Response, s string, skipBody bool,
-	expectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string, expectedContentEncoding string,
+	expectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string, expectedContentEncoding, expectedProtocol string,
 ) {
 	zr := mock.NewZeroCopyReader(s)
 	resp.SkipBody = skipBody
@@ -504,13 +508,13 @@ func testResponseReadWithoutBody(t *testing.T, resp *protocol.Response, s string
 		t.Fatalf("Unexpected response body %q. Expected %q. response=%q", resp.Body(), "", s)
 	}
 
-	verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, expectedContentEncoding)
+	verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, expectedContentEncoding, expectedProtocol)
 	verifyResponseTrailer(t, &resp.Header, expectedTrailer)
 
 	// verify that ordinal response is read after null-body response
 	resp.SkipBody = false
 	testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789",
-		consts.StatusMultipleChoices, 5, "bar", "56789", nil)
+		consts.StatusMultipleChoices, 5, "bar", "56789", nil, consts.HTTP11)
 }
 
 func verifyResponseTrailer(t *testing.T, h *protocol.ResponseHeader, expectedTrailers map[string]string) {
@@ -696,7 +700,7 @@ func testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[str
 }
 
 func testResponseReadBodyStreamSuccess(t *testing.T, resp *protocol.Response, response string, expectedStatusCode, expectedContentLength int,
-	expectedContentType, expectedBody string, expectedTrailer map[string]string,
+	expectedContentType, expectedBody string, expectedTrailer map[string]string, expectedProtocol string,
 ) {
 	zr := mock.NewZeroCopyReader(response)
 	err := ReadBodyStream(resp, zr, 0, nil)
@@ -709,7 +713,7 @@ func testResponseReadBodyStreamSuccess(t *testing.T, resp *protocol.Response, re
 	if err != nil && err != io.EOF {
 		t.Fatalf("Unexpected error: %s", err)
 	}
-	verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "")
+	verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "", expectedProtocol)
 	if !bytes.Equal(body, []byte(expectedBody)) {
 		t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody))
 	}
@@ -737,46 +741,46 @@ func TestResponseReadBodyStream(t *testing.T) {
 
 	// usual response
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789",
-		consts.StatusOK, 10, "foo/bar", "0123456789", nil)
+		consts.StatusOK, 10, "foo/bar", "0123456789", nil, consts.HTTP11)
 
 	// zero response
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n",
-		consts.StatusInternalServerError, 0, "foo/bar", "", nil)
+		consts.StatusInternalServerError, 0, "foo/bar", "", nil, consts.HTTP11)
 
 	// response with trailer
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: Foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n",
-		consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"Foo": "bar"})
+		consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"Foo": "bar"}, consts.HTTP11)
 
 	// response with trailer disableNormalizing
 	resp.Header.DisableNormalizing()
 	resp.Header.Trailer().DisableNormalizing()
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n",
-		consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"foo": "bar"})
+		consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"foo": "bar"}, consts.HTTP11)
 
 	// no content-length ('identity' transfer-encoding)
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxxx",
-		consts.StatusOK, -2, "foobar", "zxxxx", nil)
+		consts.StatusOK, -2, "foobar", "zxxxx", nil, consts.HTTP11)
 
 	// explicitly stated 'Transfer-Encoding: identity'
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag",
-		234, -2, "xxx", "xag", nil)
+		234, -2, "xxx", "xag", nil, consts.HTTP11)
 
 	// big 'identity' response
 	body := string(mock.CreateFixedBody(100500))
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body,
-		consts.StatusOK, -2, "aa", body, nil)
+		consts.StatusOK, -2, "aa", body, nil, consts.HTTP11)
 
 	// chunked response
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo2\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nFoo2: bar2\r\n\r\n",
-		200, -1, "text/html", "qwerty", map[string]string{"Foo2": "bar2"})
+		200, -1, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}, consts.HTTP11)
 
 	// chunked response with non-chunked Transfer-Encoding.
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTrailer: Foo3\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\nFoo3: bar3\r\n\r\n",
-		230, -1, "text", "erty", map[string]string{"Foo3": "bar3"})
+		230, -1, "text", "erty", map[string]string{"Foo3": "bar3"}, consts.HTTP11)
 
 	// chunked response with empty body
 	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo5\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo5: bar5\r\n\r\n",
-		consts.StatusOK, -1, "text/html", "", map[string]string{"Foo5": "bar5"})
+		consts.StatusOK, -1, "text/html", "", map[string]string{"Foo5": "bar5"}, consts.HTTP11)
 }
 
 func TestResponseReadBodyStreamBadTrailer(t *testing.T) {