Repository: cloudwego/hertz
Pull request #45 (2022-06-06 12:43:13)

feat: add ctx.Body()
#### What type of PR is this?

feat: add ctx.Body()

#### What this PR does / why we need it (English/Chinese):

en: add ctx.Body() method
zh: 添加 ctx.Body() 方法



Patch:
diff --git a/README.md b/README.md
index 6b32f8f..5dc0d04 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,16 @@
 
 English | [中文](README_cn.md)
 
+[![Release](https://img.shields.io/github/v/release/cloudwego/hertz)](https://github.com/cloudwego/hertz/releases)
+[![WebSite](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/)
+[![License](https://img.shields.io/github/license/cloudwego/hertz)](https://github.com/cloudwego/hertz/blob/main/LICENSE)
+[![Go Report Card](https://goreportcard.com/badge/github.com/cloudwego/hertz)](https://goreportcard.com/report/github.com/cloudwego/hertz)
+[![OpenIssue](https://img.shields.io/github/issues/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues)
+[![ClosedIssue](https://img.shields.io/github/issues-closed/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues?q=is%3Aissue+is%3Aclosed)
+![Stars](https://img.shields.io/github/stars/cloudwego/hertz)
+![Forks](https://img.shields.io/github/forks/cloudwego/hertz)
+
+
 Hertz [həːts] is a high-usability, high-performance and high-extensibility Golang HTTP framework that helps developers build microservices. It was designed with reference to other open-source frameworks like [fasthttp](https://github.com/valyala/fasthttp)、[gin](https://github.com/gin-gonic/gin)、[echo](https://github.com/labstack/echo) and combined with the internal requirements in ByteDance. At present, it has been widely used inside ByteDance. Nowadays, more and more microservices use Golang. If you have requirements for microservice performance and hope that the framework can fully meet the internal customizable requirements, Hertz will be a good choice.
 ## Basic Features
 - High usability
diff --git a/README_cn.md b/README_cn.md
index 61971c9..9079a48 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -2,6 +2,15 @@
 
 [English](README.md) | 中文
 
+[![Release](https://img.shields.io/github/v/release/cloudwego/hertz)](https://github.com/cloudwego/hertz/releases)
+[![WebSite](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/)
+[![License](https://img.shields.io/github/license/cloudwego/hertz)](https://github.com/cloudwego/hertz/blob/main/LICENSE)
+[![Go Report Card](https://goreportcard.com/badge/github.com/cloudwego/hertz)](https://goreportcard.com/report/github.com/cloudwego/hertz)
+[![OpenIssue](https://img.shields.io/github/issues/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues)
+[![ClosedIssue](https://img.shields.io/github/issues-closed/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues?q=is%3Aissue+is%3Aclosed)
+![Stars](https://img.shields.io/github/stars/cloudwego/hertz)
+![Forks](https://img.shields.io/github/forks/cloudwego/hertz)
+
 Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了其他开源框架 [fasthttp](https://github.com/valyala/fasthttp)、[gin](https://github.com/gin-gonic/gin)、[echo](https://github.com/labstack/echo) 的优势，并结合字节跳动内部的需求，使其具有高易用性、高性能、高扩展性等特点，目前在字节跳动内部已广泛使用。如今越来越多的微服务选择使用 Golang，如果对微服务性能有要求，又希望框架能够充分满足内部的可定制化需求，Hertz 会是一个不错的选择。
 ## 框架特点
 - 高易用性
diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv.go
index 40cfdc8..fd355ec 100644
--- a/internal/bytesconv/bytesconv.go
+++ b/internal/bytesconv/bytesconv.go
@@ -42,12 +42,12 @@
 package bytesconv
 
 import (
+	"net/http"
 	"reflect"
 	"sync"
 	"time"
 	"unsafe"
 
-	"github.com/cloudwego/hertz/internal/bytestr"
 	"github.com/cloudwego/hertz/pkg/network"
 )
 
@@ -198,12 +198,10 @@ func AppendUint(dst []byte, n int) []byte {
 	return dst
 }
 
-// AppendHTTPDate appends HTTP-compliant (RFC1123) representation of date
+// AppendHTTPDate appends HTTP-compliant representation of date
 // to dst and returns the extended dst.
 func AppendHTTPDate(dst []byte, date time.Time) []byte {
-	dst = date.In(time.UTC).AppendFormat(dst, time.RFC1123)
-	copy(dst[len(dst)-3:], bytestr.StrGMT)
-	return dst
+	return date.UTC().AppendFormat(dst, http.TimeFormat)
 }
 
 func AppendQuotedPath(dst, src []byte) []byte {
diff --git a/pkg/app/context.go b/pkg/app/context.go
index 27b16c7..b564a52 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -970,6 +970,11 @@ func (ctx *RequestContext) GetRawData() []byte {
 	return ctx.Request.Body()
 }
 
+// Body returns body data
+func (ctx *RequestContext) Body() ([]byte, error) {
+	return ctx.Request.BodyE()
+}
+
 type ClientIP func(ctx *RequestContext) string
 
 var defaultClientIP = func(ctx *RequestContext) string {
diff --git a/pkg/network/connection.go b/pkg/network/connection.go
index c2dd8e3..9a5e358 100644
--- a/pkg/network/connection.go
+++ b/pkg/network/connection.go
@@ -89,3 +89,7 @@ type ConnTLSer interface {
 	Handshake() error
 	ConnectionState() tls.ConnectionState
 }
+
+type HandleSpecificError interface {
+	HandleSpecificError(err error, rip string) (needIgnore bool)
+}
diff --git a/pkg/network/dialer/default.go b/pkg/network/dialer/default.go
new file mode 100644
index 0000000..35c48a0
--- /dev/null
+++ b/pkg/network/dialer/default.go
@@ -0,0 +1,26 @@
+//go:build !windows
+// +build !windows
+
+/*
+ * Copyright 2022 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 dialer
+
+import "github.com/cloudwego/hertz/pkg/network/netpoll"
+
+func init() {
+	defaultDialer = netpoll.NewDialer()
+}
diff --git a/pkg/network/dialer/default_windows.go b/pkg/network/dialer/default_windows.go
new file mode 100644
index 0000000..e2ff5b1
--- /dev/null
+++ b/pkg/network/dialer/default_windows.go
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 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 dialer
+
+import (
+	"github.com/cloudwego/hertz/pkg/network/standard"
+)
+
+func init() {
+	defaultDialer = standard.NewDialer()
+}
diff --git a/pkg/network/dialer/dialer.go b/pkg/network/dialer/dialer.go
index 007dded..9678bd0 100644
--- a/pkg/network/dialer/dialer.go
+++ b/pkg/network/dialer/dialer.go
@@ -22,15 +22,10 @@ import (
 	"time"
 
 	"github.com/cloudwego/hertz/pkg/network"
-	"github.com/cloudwego/hertz/pkg/network/netpoll"
 )
 
 var defaultDialer network.Dialer
 
-func init() {
-	defaultDialer = netpoll.NewDialer()
-}
-
 func SetDialer(dialer network.Dialer) {
 	defaultDialer = dialer
 }
diff --git a/pkg/network/netpoll/connection.go b/pkg/network/netpoll/connection.go
index 12a559f..a407abd 100644
--- a/pkg/network/netpoll/connection.go
+++ b/pkg/network/netpoll/connection.go
@@ -1,3 +1,6 @@
+//go:build !windows
+// +build !windows
+
 /*
  * Copyright 2022 CloudWeGo Authors
  *
@@ -20,6 +23,7 @@ import (
 	"errors"
 	"io"
 
+	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/network"
 	"github.com/cloudwego/netpoll"
 )
@@ -70,6 +74,14 @@ func (c *Conn) Flush() error {
 	return c.Conn.Flush()
 }
 
+func (c *Conn) HandleSpecificError(err error, rip string) (needIgnore bool) {
+	if errors.Is(err, netpoll.ErrConnClosed) {
+		hlog.Warnf("HERTZ: Netpoll error=%s, remoteAddr=%s", err.Error(), rip)
+		return true
+	}
+	return false
+}
+
 func normalizeErr(err error) error {
 	if errors.Is(err, netpoll.ErrEOF) {
 		return io.EOF
diff --git a/pkg/network/netpoll/dial.go b/pkg/network/netpoll/dial.go
index 0df38dd..9c6d7b3 100644
--- a/pkg/network/netpoll/dial.go
+++ b/pkg/network/netpoll/dial.go
@@ -1,3 +1,6 @@
+//go:build !windows
+// +build !windows
+
 /*
  * Copyright 2022 CloudWeGo Authors
  *
diff --git a/pkg/network/netpoll/transport.go b/pkg/network/netpoll/transport.go
index 6377999..803e34e 100644
--- a/pkg/network/netpoll/transport.go
+++ b/pkg/network/netpoll/transport.go
@@ -1,3 +1,6 @@
+//go:build !windows
+// +build !windows
+
 /*
  * Copyright 2022 CloudWeGo Authors
  *
diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go
index 48fb019..fe0cae8 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -1321,6 +1321,17 @@ func (h *RequestHeader) VisitAll(f func(key, value []byte)) {
 	}
 }
 
+// VisitAllCustomHeader calls f for each header in header.h which contains all headers
+// except cookie, host, content-length, content-type, user-agent and connection.
+//
+// f must not retain references to key and/or value after returning.
+// Copy key and/or value contents before returning if you need retaining them.
+//
+// To get the headers in order they were received use VisitAllInOrder.
+func (h *RequestHeader) VisitAllCustomHeader(f func(key, value []byte)) {
+	visitArgs(h.h, f)
+}
+
 func ParseContentLength(b []byte) (int, error) {
 	v, n, err := bytesconv.ParseUintBuf(b)
 	if err != nil {
diff --git a/pkg/protocol/request.go b/pkg/protocol/request.go
index f582f2c..51b2b02 100644
--- a/pkg/protocol/request.go
+++ b/pkg/protocol/request.go
@@ -130,6 +130,9 @@ func (w *requestBodyWriter) Write(p []byte) (int, error) {
 }
 
 func (req *Request) Options() *config.RequestOptions {
+	if req.options == nil {
+		req.options = config.NewRequestOptions(nil)
+	}
 	return req.options
 }
 
@@ -784,11 +787,7 @@ func (req *Request) QueryString() []byte {
 // SetOptions is used to set request options.
 // These options can be used to do something in middlewares such as service discovery.
 func (req *Request) SetOptions(opts ...config.RequestOption) {
-	if req.options == nil {
-		req.options = config.NewRequestOptions(opts)
-	} else {
-		req.options.Apply(opts)
-	}
+	req.Options().Apply(opts)
 }
 
 // ConnectionClose returns true if 'Connection: close' header is set.
diff --git a/pkg/route/default.go b/pkg/route/default.go
new file mode 100644
index 0000000..f0e4153
--- /dev/null
+++ b/pkg/route/default.go
@@ -0,0 +1,28 @@
+//go:build !windows
+// +build !windows
+
+/*
+ * Copyright 2022 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 route
+
+import (
+	"github.com/cloudwego/hertz/pkg/network/netpoll"
+)
+
+func init() {
+	defaultTransporter = netpoll.NewTransporter
+}
diff --git a/pkg/route/default_windows.go b/pkg/route/default_windows.go
new file mode 100644
index 0000000..71deb19
--- /dev/null
+++ b/pkg/route/default_windows.go
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 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 route
+
+import (
+	"github.com/cloudwego/hertz/pkg/network/standard"
+)
+
+func init() {
+	defaultTransporter = standard.NewTransporter
+}
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index b8a6269..fcce934 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -67,17 +67,16 @@ import (
 	"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo"
 	"github.com/cloudwego/hertz/pkg/common/utils"
 	"github.com/cloudwego/hertz/pkg/network"
-	netpoll2 "github.com/cloudwego/hertz/pkg/network/netpoll"
+	"github.com/cloudwego/hertz/pkg/network/standard"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
 	"github.com/cloudwego/hertz/pkg/protocol/http1"
 	"github.com/cloudwego/hertz/pkg/protocol/http1/factory"
 	"github.com/cloudwego/hertz/pkg/protocol/suite"
-	"github.com/cloudwego/netpoll"
 )
 
 var (
-	defaultTransporter = netpoll2.NewTransporter
+	defaultTransporter = standard.NewTransporter
 
 	errInitFailed       = errs.NewPrivate("engine has been init already")
 	errAlreadyRunning   = errs.NewPrivate("engine is already running")
@@ -375,18 +374,26 @@ func errProcess(conn io.Closer, err error) {
 		return
 	}
 
-	// Handle netpoll error
-	if errors.Is(err, netpoll.ErrConnClosed) {
-		hlog.Warnf("HERTZ: Netpoll error=%s", err.Error())
-		return
-	}
-	if errors.Is(err, netpoll.ErrReadTimeout) {
-		hlog.Errorf("HERTZ: Netpoll error=%s", err.Error())
-		return
-	}
+	// Get remote address
+	rip := getRemoteAddrFromCloser(conn)
 
+	// Handle Specific error
+	if hsp, ok := conn.(network.HandleSpecificError); ok {
+		if hsp.HandleSpecificError(err, rip) {
+			return
+		}
+	}
 	// other errors
-	hlog.Errorf("HERTZ: Error=%s", err.Error())
+	hlog.Errorf("HERTZ: Error=%s, remoteAddr=%s", err.Error(), rip)
+}
+
+func getRemoteAddrFromCloser(conn io.Closer) string {
+	if c, ok := conn.(network.Conn); ok {
+		if addr := c.RemoteAddr(); addr != nil {
+			return addr.String()
+		}
+	}
+	return ""
 }
 
 func (engine *Engine) Close() error {


Test patch:
diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go
new file mode 100644
index 0000000..57caea6
--- /dev/null
+++ b/internal/bytesconv/bytesconv_test.go
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 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 (
+	"testing"
+	"time"
+
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
+)
+
+func TestAppendDate(t *testing.T) {
+	t.Parallel()
+	// GMT+8
+	shanghaiTimeZone := time.FixedZone("Asia/Shanghai", 8*60*60)
+
+	for _, c := range []struct {
+		name    string
+		date    time.Time
+		dateStr string
+	}{
+		{
+			name:    "UTC",
+			date:    time.Date(2022, 6, 15, 11, 12, 13, 123, time.UTC),
+			dateStr: "Wed, 15 Jun 2022 11:12:13 GMT",
+		},
+		{
+			name:    "Asia/Shanghai",
+			date:    time.Date(2022, 6, 15, 3, 12, 45, 999, shanghaiTimeZone),
+			dateStr: "Tue, 14 Jun 2022 19:12:45 GMT",
+		},
+	} {
+		t.Run(c.name, func(t *testing.T) {
+			s := AppendHTTPDate(nil, c.date)
+			assert.DeepEqual(t, c.dateStr, string(s))
+		})
+	}
+}
diff --git a/internal/bytestr/bytes.go b/internal/bytestr/bytes.go
index eca1a22..2cfc0c1 100644
--- a/internal/bytestr/bytes.go
+++ b/internal/bytestr/bytes.go
@@ -41,7 +41,6 @@ var (
 	StrStar             = []byte("*")
 	StrColonSlashSlash  = []byte("://")
 	StrColonSpace       = []byte(": ")
-	StrGMT              = []byte("GMT")
 	StrAt               = []byte("@")
 	StrSD               = []byte("sd")
 
diff --git a/pkg/common/testdata/proto/test.pb.go b/pkg/common/testdata/proto/test.pb.go
index 5f947d8..382b673 100644
--- a/pkg/common/testdata/proto/test.pb.go
+++ b/pkg/common/testdata/proto/test.pb.go
@@ -280,14 +280,17 @@ func file_test_proto_rawDescGZIP() []byte {
 	return file_test_proto_rawDescData
 }
 
-var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
-var file_test_proto_goTypes = []interface{}{
-	(FOO)(0),                   // 0: proto.FOO
-	(*Test)(nil),               // 1: proto.Test
-	(*TestStruct)(nil),         // 2: proto.TestStruct
-	(*Test_OptionalGroup)(nil), // 3: proto.Test.OptionalGroup
-}
+var (
+	file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+	file_test_proto_msgTypes  = make([]protoimpl.MessageInfo, 3)
+	file_test_proto_goTypes   = []interface{}{
+		(FOO)(0),                   // 0: proto.FOO
+		(*Test)(nil),               // 1: proto.Test
+		(*TestStruct)(nil),         // 2: proto.TestStruct
+		(*Test_OptionalGroup)(nil), // 3: proto.Test.OptionalGroup
+	}
+)
+
 var file_test_proto_depIdxs = []int32{
 	3, // 0: proto.Test.optionalgroup:type_name -> proto.Test.OptionalGroup
 	1, // [1:1] is the sub-list for method output_type