Repository: cloudwego/hertz (go)

Issue:
Hertz server wrong scheme when keep alive
**Describe the bug**

Hertz server with listen tls and app.RequestContext will return scheme from ctx.Request.Scheme() is "https" for first time, and after will return is "http" when client keep alive connection.

** Reproducible Code**

```go
package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/client"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/network/standard"
	"github.com/cloudwego/hertz/pkg/protocol"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"time"
)

func main() {
	cfg := &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		},
	}

	cert, err := tls.LoadX509KeyPair("./tls/server.crt", "./tls/server.key")
	if err != nil {
		fmt.Println(err.Error())
	}

	cfg.Certificates = append(cfg.Certificates, cert)

	hz := server.Default(server.WithTLS(cfg), server.WithHostPorts(":8443"))

	hz.GET("/hello/:action", func(c context.Context, ctx *app.RequestContext) {
		fmt.Printf("Scheme: %s, action: %s", ctx.Request.Scheme(), ctx.Param("action"))
		ctx.SetStatusCode(consts.StatusOK)
	})

	go func() {
		hz.Spin()
	}()

	time.Sleep(time.Millisecond * 50)
	doTlsRequest()
}

func doTlsRequest() {
	clientCfg := &tls.Config{
		InsecureSkipVerify: true,
	}

	c, err := client.NewClient(
		client.WithTLSConfig(clientCfg),
		client.WithDialer(standard.NewDialer()),
		client.WithKeepAlive(true),
	)
	if err != nil {
		fmt.Println(err.Error())
	}

	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
	defer func() {
		protocol.ReleaseRequest(req)
		protocol.ReleaseResponse(res)
	}()

	for i, num := 0, 3; i < num; i++ {
		req.SetMethod(consts.MethodGet)                             // set request method
		req.Header.SetContentTypeBytes([]byte("application/json"))  // set request header
		req.SetRequestURI(fmt.Sprintf("https://localhost:8443/hello/ping-%d", i)) // set request url

		err = c.Do(context.Background(), req, res)
		if err != nil {
			fmt.Println(err.Error())
		}

		fmt.Printf("%v\n", string(res.Body())) // read response body
		time.Sleep(time.Second)
	}
}
```

**Expected behavior**
Always return "https" when client request by "https" scheme

**Screenshots**
<img width="1401" alt="image" src="https://github.com/cloudwego/hertz/assets/8767534/d3efc4c9-2bcb-4c96-9f4f-f510ffaf0f31">

**Hertz version:**
v0.6.6


Pull request:
fix: keep isTLS field in keepalive when reset the request
#### What type of PR is this?
fix

#### Check the PR title.
- [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.
长链接场景重置请求时保留，请求中和连接相关的字段（isTLS）

#### (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: 
There is a special filed (isTLS) in the request, which is a link-related field, and its life cycle follows the link. Therefore, in the keepalive scenario, this field needs to be skipped when resetting the request, otherwise it will affect the scheme judgment of the subsequent request of the long link.

zh(optional):
请求里有个特殊filed（isTLS），它是一个链接相关字段，生命周期跟随链接，因此在长链接复用场景，重置请求的时候需要跳过该字段，否则影响长链接后续请求的scheme判断。

#### (Optional) Which issue(s) this PR fixes:
fixes #874 

#### (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)
-->


Patch:
diff --git a/pkg/app/context.go b/pkg/app/context.go
index e4fcd0a..559b84d 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -754,7 +754,8 @@ func (ctx *RequestContext) ResetWithoutConn() {
 		close(ctx.finished)
 		ctx.finished = nil
 	}
-	ctx.Request.Reset()
+
+	ctx.Request.ResetWithoutConn()
 	ctx.Response.Reset()
 	if ctx.IsEnableTrace() {
 		ctx.traceInfo.Reset()
diff --git a/pkg/protocol/request.go b/pkg/protocol/request.go
index af29db1..ef70f92 100644
--- a/pkg/protocol/request.go
+++ b/pkg/protocol/request.go
@@ -176,16 +176,25 @@ func (req *Request) MayContinue() bool {
 	return bytes.Equal(req.Header.peek(bytestr.StrExpect), bytestr.Str100Continue)
 }
 
+// Scheme returns the scheme of the request.
+// uri will be parsed in ServeHTTP(before user's process), so that there is no need for uri nil-check.
 func (req *Request) Scheme() []byte {
 	return req.uri.Scheme()
 }
 
-func (req *Request) ResetSkipHeader() {
+// For keepalive connection reuse.
+// It is roughly the same as ResetSkipHeader, except that the connection related fields are removed:
+// - req.isTLS
+func (req *Request) resetSkipHeaderAndConn() {
 	req.ResetBody()
 	req.uri.Reset()
 	req.parsedURI = false
 	req.parsedPostArgs = false
 	req.postArgs.Reset()
+}
+
+func (req *Request) ResetSkipHeader() {
+	req.resetSkipHeaderAndConn()
 	req.isTLS = false
 }
 
@@ -814,6 +823,14 @@ func (req *Request) SetConnectionClose() {
 	req.Header.SetConnectionClose(true)
 }
 
+func (req *Request) ResetWithoutConn() {
+	req.Header.Reset()
+	req.resetSkipHeaderAndConn()
+	req.CloseBodyStream()
+
+	req.options = nil
+}
+
 // AcquireRequest returns an empty Request instance from request pool.
 //
 // The returned Request instance may be passed to ReleaseRequest when it is


Test patch:
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
index b9e9d2f..8bf52f4 100644
--- a/pkg/app/context_test.go
+++ b/pkg/app/context_test.go
@@ -654,8 +654,10 @@ func TestContextReset(t *testing.T) {
 	c.Params = param.Params{param.Param{}}
 	c.Error(errors.New("test")) // nolint: errcheck
 	c.Set("foo", "bar")
+	c.Request.SetIsTLS(true)
 	c.ResetWithoutConn()
-
+	c.Request.URI()
+	assert.DeepEqual(t, "https", string(c.Request.Scheme()))
 	assert.False(t, c.IsAborted())
 	assert.DeepEqual(t, 0, len(c.Errors))
 	assert.Nil(t, c.Errors.Errors())