Repository: cloudwego/hertz
Pull request #689 (2023-03-27 06:02:56)

feat: ignore broken pipe and connection reset error on go net
#### 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:
-->
- [ ] This PR title match the format: \<type\>(optional scope): \<description\>
- [ ] The description of this PR title is user-oriented and clear enough for others to understand.


#### (Optional) Translate the PR title into Chinese.
忽略标准网络库 broken pipe 和 connection reset 的错误

#### (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: ignore broken pipe and connection reset error on go net
zh(optional): 忽略标准网络库 broken pipe 和 connection reset

#### Which issue(s) this PR fixes:
<!--
Automatically closes linked issue when PR is merged.
Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->


codecov[bot]: ## [Codecov](https://codecov.io/gh/cloudwego/hertz/pull/689?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
Patch coverage: **`100.00`**% and project coverage change: **`+0.01`** :tada:
> Comparison is base [(`6fa606f`)](https://codecov.io/gh/cloudwego/hertz/commit/6fa606ff23f0dafc7bb74fbc2c611cede4ab1670?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 75.61% compared to head [(`3bbfc90`)](https://codecov.io/gh/cloudwego/hertz/pull/689?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 75.62%.

> :exclamation: Current head 3bbfc90 differs from pull request most recent head 87cbee1. Consider uploading reports for the commit 87cbee1 to get more accurate results

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


```diff
@@             Coverage Diff             @@
##           develop     #689      +/-   ##
===========================================
+ Coverage    75.61%   75.62%   +0.01%     
===========================================
  Files           96       96              
  Lines         9435     9440       +5     
===========================================
+ Hits          7134     7139       +5     
  Misses        1854     1854              
  Partials       447      447              
```


| [Impacted Files](https://codecov.io/gh/cloudwego/hertz/pull/689?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) | Coverage Δ | |
|---|---|---|
| [pkg/network/standard/connection.go](https://codecov.io/gh/cloudwego/hertz/pull/689?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL25ldHdvcmsvc3RhbmRhcmQvY29ubmVjdGlvbi5nbw==) | `81.01% <100.00%> (+0.30%)` | :arrow_up: |

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/689?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/app/client/client.go b/pkg/app/client/client.go
index 1f98cbe..33f9fb5 100644
--- a/pkg/app/client/client.go
+++ b/pkg/app/client/client.go
@@ -66,7 +66,10 @@ import (
 	"github.com/cloudwego/hertz/pkg/protocol/suite"
 )
 
-var errorInvalidURI = errors.NewPublic("invalid uri")
+var (
+	errorInvalidURI          = errors.NewPublic("invalid uri")
+	errorLastMiddlewareExist = errors.NewPublic("last middleware already set")
+)
 
 // Do performs the given http request and fills the given http response.
 //
@@ -269,10 +272,11 @@ type Client struct {
 
 	clientFactory suite.ClientFactory
 
-	mLock sync.Mutex
-	m     map[string]client.HostClient
-	ms    map[string]client.HostClient
-	mws   Middleware
+	mLock          sync.Mutex
+	m              map[string]client.HostClient
+	ms             map[string]client.HostClient
+	mws            Middleware
+	lastMiddleware Middleware
 }
 
 func (c *Client) GetOptions() *config.ClientOptions {
@@ -454,6 +458,9 @@ func (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.R
 	if c.mws == nil {
 		return c.do(ctx, req, resp)
 	}
+	if c.lastMiddleware != nil {
+		return c.mws(c.lastMiddleware(c.do))(ctx, req, resp)
+	}
 	return c.mws(c.do)(ctx, req, resp)
 }
 
@@ -616,6 +623,28 @@ func (c *Client) Use(mws ...Middleware) {
 	c.mws = chain(middlewares...)
 }
 
+// UseAsLast is used to add middleware to the end of the middleware chain.
+//
+// Will return an error if last middleware has been set before, to ensure all middleware has the change to work,
+// Please use `TakeOutLastMiddleware` to take out the already set middleware.
+// Chain the middleware after or before is both Okay - but remember to put it back.
+func (c *Client) UseAsLast(mw Middleware) error {
+	if c.lastMiddleware != nil {
+		return errorLastMiddlewareExist
+	}
+	c.lastMiddleware = mw
+	return nil
+}
+
+// TakeOutLastMiddleware will return the set middleware and remove it from client.
+//
+// Remember to set it back after chain it with other middleware.
+func (c *Client) TakeOutLastMiddleware() Middleware {
+	last := c.lastMiddleware
+	c.lastMiddleware = nil
+	return last
+}
+
 func newHttp1OptionFromClient(c *Client) *http1.ClientOptions {
 	return &http1.ClientOptions{
 		Name:                          c.options.Name,
diff --git a/pkg/network/standard/connection.go b/pkg/network/standard/connection.go
index 5388c38..5ab9a8c 100644
--- a/pkg/network/standard/connection.go
+++ b/pkg/network/standard/connection.go
@@ -26,6 +26,7 @@ import (
 	"time"
 
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
+	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/network"
 )
 
@@ -526,6 +527,14 @@ func (c *Conn) Flush() (err error) {
 	return nil
 }
 
+func (c *Conn) HandleSpecificError(err error, rip string) (needIgnore bool) {
+	if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {
+		hlog.SystemLogger().Debugf("Go net library error=%s, remoteAddr=%s", err.Error(), rip)
+		return true
+	}
+	return false
+}
+
 func (c *TLSConn) Handshake() error {
 	return c.c.(network.ConnTLSer).Handshake()
 }


Test patch:
diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go
index 1754044..b6eb263 100644
--- a/pkg/app/client/client_test.go
+++ b/pkg/app/client/client_test.go
@@ -55,6 +55,7 @@ import (
 	"net/url"
 	"os"
 	"path/filepath"
+	"reflect"
 	"regexp"
 	"strings"
 	"sync"
@@ -1803,13 +1804,82 @@ func TestClientMiddleware(t *testing.T) {
 	client.Use(mw1)
 	client.Use(mw2)
 
-	req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
-	err := client.Do(context.Background(), req, resp)
+	request, response := protocol.AcquireRequest(), protocol.AcquireResponse()
+	defer func() {
+		protocol.ReleaseRequest(request)
+		protocol.ReleaseResponse(response)
+	}()
+	err := client.Do(context.Background(), request, response)
 	if err != nil {
 		t.Errorf("unexpected error: %s", err.Error())
 	}
 }
 
+func TestClientLastMiddleware(t *testing.T) {
+	client, _ := NewClient()
+	mw0 := func(next Endpoint) Endpoint {
+		return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {
+			finalValue0 := ctx.Value("final0")
+			assert.DeepEqual(t, "final3", finalValue0)
+			finalValue1 := ctx.Value("final1")
+			assert.DeepEqual(t, "final1", finalValue1)
+			finalValue2 := ctx.Value("final2")
+			assert.DeepEqual(t, "final2", finalValue2)
+			return nil
+		}
+	}
+	mw1 := func(next Endpoint) Endpoint {
+		return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {
+			ctx = context.WithValue(ctx, "final0", "final0")
+			return next(ctx, req, resp)
+		}
+	}
+	mw2 := func(next Endpoint) Endpoint {
+		return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {
+			ctx = context.WithValue(ctx, "final1", "final1")
+			return next(ctx, req, resp)
+		}
+	}
+	mw3 := func(next Endpoint) Endpoint {
+		return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {
+			ctx = context.WithValue(ctx, "final2", "final2")
+			return next(ctx, req, resp)
+		}
+	}
+	mw4 := func(next Endpoint) Endpoint {
+		return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {
+			ctx = context.WithValue(ctx, "final0", "final3")
+			return next(ctx, req, resp)
+		}
+	}
+	err := client.UseAsLast(mw0)
+	assert.Nil(t, err)
+	err = client.UseAsLast(func(endpoint Endpoint) Endpoint {
+		return nil
+	})
+	assert.DeepEqual(t, errorLastMiddlewareExist, err)
+	client.Use(mw1)
+	client.Use(mw2)
+	client.Use(mw3)
+	client.Use(mw4)
+
+	request, response := protocol.AcquireRequest(), protocol.AcquireResponse()
+	defer func() {
+		protocol.ReleaseRequest(request)
+		protocol.ReleaseResponse(response)
+	}()
+	err = client.Do(context.Background(), request, response)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err.Error())
+	}
+
+	last := client.TakeOutLastMiddleware()
+
+	assert.DeepEqual(t, reflect.ValueOf(last).Pointer(), reflect.ValueOf(mw0).Pointer())
+	last = client.TakeOutLastMiddleware()
+	assert.Nil(t, last)
+}
+
 func TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {
 	part1 := ""
 	for i := 0; i < 8192; i++ {
diff --git a/pkg/network/standard/connection_test.go b/pkg/network/standard/connection_test.go
index 8108f1c..9b0fafd 100644
--- a/pkg/network/standard/connection_test.go
+++ b/pkg/network/standard/connection_test.go
@@ -23,6 +23,7 @@ import (
 	"io"
 	"net"
 	"strings"
+	"syscall"
 	"testing"
 	"time"
 
@@ -263,6 +264,12 @@ func TestInitializeTLSConn(t *testing.T) {
 	assert.DeepEqual(t, tls.ConnectionState{}, tlsConn.ConnectionState())
 }
 
+func TestHandleSpecificError(t *testing.T) {
+	conn := &Conn{}
+	assert.DeepEqual(t, false, conn.HandleSpecificError(nil, ""))
+	assert.DeepEqual(t, true, conn.HandleSpecificError(syscall.EPIPE, ""))
+}
+
 type mockConn struct {
 	buffer     bytes.Buffer
 	localAddr  net.Addr