Repository: go-kratos/kratos
Pull request #1916 (2022-04-07 11:05:48)

optimization: optimize a clear interaction for content-type
Content-type is not be promise from RFC2616 7.2.1.
it may be need a clear describe info:)

workman-Lu: optimize Content-type reason of error

Patch:
diff --git a/cmd/kratos/version.go b/cmd/kratos/version.go
index ae1c2a28..571688f4 100644
--- a/cmd/kratos/version.go
+++ b/cmd/kratos/version.go
@@ -1,4 +1,4 @@
 package main
 
 // release is the current kratos tool version.
-const release = "v2.3.0"
+const release = "v2.3.1"
diff --git a/cmd/protoc-gen-go-errors/version.go b/cmd/protoc-gen-go-errors/version.go
index 48251706..7717f4e6 100644
--- a/cmd/protoc-gen-go-errors/version.go
+++ b/cmd/protoc-gen-go-errors/version.go
@@ -1,4 +1,4 @@
 package main
 
 // release is the current protoc-gen-go-errors version.
-const release = "v2.3.0"
+const release = "v2.3.1"
diff --git a/cmd/protoc-gen-go-http/version.go b/cmd/protoc-gen-go-http/version.go
index bbe52dd9..a619f8e2 100644
--- a/cmd/protoc-gen-go-http/version.go
+++ b/cmd/protoc-gen-go-http/version.go
@@ -1,4 +1,4 @@
 package main
 
 // release is the current protoc-gen-go-http version.
-const release = "v2.3.0"
+const release = "v2.3.1"
diff --git a/contrib/config/apollo/go.mod b/contrib/config/apollo/go.mod
index 0886b4dd..2c403798 100644
--- a/contrib/config/apollo/go.mod
+++ b/contrib/config/apollo/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/apolloconfig/agollo/v4 v4.2.0
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 )
 
 require (
diff --git a/contrib/config/consul/go.mod b/contrib/config/consul/go.mod
index bd7dd34d..48fe75ed 100644
--- a/contrib/config/consul/go.mod
+++ b/contrib/config/consul/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/config/consul/v2
 go 1.15
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/hashicorp/consul/api v1.12.0
 )
 
diff --git a/contrib/config/etcd/go.mod b/contrib/config/etcd/go.mod
index 69b23228..92299bf2 100644
--- a/contrib/config/etcd/go.mod
+++ b/contrib/config/etcd/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/config/etcd/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	go.etcd.io/etcd/client/v3 v3.5.4
 	google.golang.org/grpc v1.46.2
 )
diff --git a/contrib/config/kubernetes/go.mod b/contrib/config/kubernetes/go.mod
index 13bec73d..db846f9c 100644
--- a/contrib/config/kubernetes/go.mod
+++ b/contrib/config/kubernetes/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/config/kubernetes/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	k8s.io/api v0.24.1
 	k8s.io/apimachinery v0.24.1
 	k8s.io/client-go v0.24.1
diff --git a/contrib/config/nacos/go.mod b/contrib/config/nacos/go.mod
index d465ce72..c77f6657 100644
--- a/contrib/config/nacos/go.mod
+++ b/contrib/config/nacos/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/config/nacos/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/nacos-group/nacos-sdk-go v1.0.9
 	gopkg.in/yaml.v3 v3.0.1
 )
diff --git a/contrib/encoding/msgpack/go.mod b/contrib/encoding/msgpack/go.mod
index a4b7e8b4..4adcc0b8 100644
--- a/contrib/encoding/msgpack/go.mod
+++ b/contrib/encoding/msgpack/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/encoding/msgpack/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/vmihailenco/msgpack/v5 v5.3.5
 )
 
diff --git a/contrib/log/aliyun/go.mod b/contrib/log/aliyun/go.mod
index bdecf49c..5acaf3c0 100644
--- a/contrib/log/aliyun/go.mod
+++ b/contrib/log/aliyun/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/aliyun/aliyun-log-go-sdk v0.1.36
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	google.golang.org/protobuf v1.28.0
 )
 
diff --git a/contrib/log/fluent/go.mod b/contrib/log/fluent/go.mod
index 41e38614..5f820297 100644
--- a/contrib/log/fluent/go.mod
+++ b/contrib/log/fluent/go.mod
@@ -5,7 +5,7 @@ go 1.16
 require (
 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
 	github.com/fluent/fluent-logger-golang v1.9.0
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/tinylib/msgp v1.1.6 // indirect
 )
diff --git a/contrib/log/zap/go.mod b/contrib/log/zap/go.mod
index 0d81c83f..43b1181c 100644
--- a/contrib/log/zap/go.mod
+++ b/contrib/log/zap/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/log/zap/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	go.uber.org/zap v1.21.0
 )
 
diff --git a/contrib/metrics/datadog/go.mod b/contrib/metrics/datadog/go.mod
index 42995100..8ce02768 100644
--- a/contrib/metrics/datadog/go.mod
+++ b/contrib/metrics/datadog/go.mod
@@ -5,7 +5,7 @@ go 1.16
 require (
 	github.com/DataDog/datadog-go v4.8.3+incompatible
 	github.com/Microsoft/go-winio v0.5.2 // indirect
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/metrics/prometheus/go.mod b/contrib/metrics/prometheus/go.mod
index 081af491..8c6f24aa 100644
--- a/contrib/metrics/prometheus/go.mod
+++ b/contrib/metrics/prometheus/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/metrics/prometheus/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/prometheus/client_golang v1.12.2
 )
 
diff --git a/contrib/opensergo/go.mod b/contrib/opensergo/go.mod
index 5f1a1292..8849cb45 100644
--- a/contrib/opensergo/go.mod
+++ b/contrib/opensergo/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/opensergo/v2
 go 1.17
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/opensergo/opensergo-go v0.0.0-20220331070310-e5b01fee4d1c
 	golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
 	google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd
diff --git a/contrib/registry/consul/go.mod b/contrib/registry/consul/go.mod
index 4278a792..f746247d 100644
--- a/contrib/registry/consul/go.mod
+++ b/contrib/registry/consul/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/consul/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/hashicorp/consul/api v1.12.0
 )
 
diff --git a/contrib/registry/discovery/go.mod b/contrib/registry/discovery/go.mod
index ef2f80d7..9b74f265 100644
--- a/contrib/registry/discovery/go.mod
+++ b/contrib/registry/discovery/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/discovery/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/go-resty/resty/v2 v2.7.0
 	github.com/pkg/errors v0.9.1
 )
diff --git a/contrib/registry/etcd/go.mod b/contrib/registry/etcd/go.mod
index 8b71180b..04d06613 100644
--- a/contrib/registry/etcd/go.mod
+++ b/contrib/registry/etcd/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/etcd/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	go.etcd.io/etcd/client/v3 v3.5.4
 	google.golang.org/grpc v1.46.2
 )
diff --git a/contrib/registry/kubernetes/go.mod b/contrib/registry/kubernetes/go.mod
index 81acdcf5..af084136 100644
--- a/contrib/registry/kubernetes/go.mod
+++ b/contrib/registry/kubernetes/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/kubernetes/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/json-iterator/go v1.1.12
 	k8s.io/api v0.24.1
 	k8s.io/apimachinery v0.24.1
diff --git a/contrib/registry/nacos/go.mod b/contrib/registry/nacos/go.mod
index 8c902e0b..82d23b12 100644
--- a/contrib/registry/nacos/go.mod
+++ b/contrib/registry/nacos/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/nacos/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/nacos-group/nacos-sdk-go v1.0.9
 )
 
diff --git a/contrib/registry/polaris/go.mod b/contrib/registry/polaris/go.mod
index 83ff4574..59c461c1 100644
--- a/contrib/registry/polaris/go.mod
+++ b/contrib/registry/polaris/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/polaris/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/polarismesh/polaris-go v1.1.0
 )
 
diff --git a/contrib/registry/zookeeper/go.mod b/contrib/registry/zookeeper/go.mod
index 9a22ff86..0db3bbc3 100644
--- a/contrib/registry/zookeeper/go.mod
+++ b/contrib/registry/zookeeper/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/zookeeper/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.3.0
+	github.com/go-kratos/kratos/v2 v2.3.1
 	github.com/go-zookeeper/zk v1.0.2
 	golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
 )
diff --git a/internal/endpoint/endpoint.go b/internal/endpoint/endpoint.go
index a125d924..e4c9ef96 100644
--- a/internal/endpoint/endpoint.go
+++ b/internal/endpoint/endpoint.go
@@ -6,29 +6,42 @@ import (
 )
 
 // NewEndpoint new an Endpoint URL.
-func NewEndpoint(scheme, host string, isSecure bool) *url.URL {
-	var query string
-	if isSecure {
-		query = "isSecure=true"
-	}
-	return &url.URL{Scheme: scheme, Host: host, RawQuery: query}
+func NewEndpoint(scheme, host string) *url.URL {
+	return &url.URL{Scheme: scheme, Host: host}
 }
 
 // ParseEndpoint parses an Endpoint URL.
-func ParseEndpoint(endpoints []string, scheme string, isSecure bool) (string, error) {
+func ParseEndpoint(endpoints []string, scheme string) (string, error) {
 	for _, e := range endpoints {
 		u, err := url.Parse(e)
 		if err != nil {
 			return "", err
 		}
-		if u.Scheme == scheme && IsSecure(u) == isSecure {
+
+		// TODO: Compatibility processing
+		// Function is to convert grpc:/127.0.0.1/?isSecure=true into grpcs:/127.0.0.1/
+		// It will be deleted in about a month
+		u = legacyURLToNew(u)
+		if u.Scheme == scheme {
 			return u.Host, nil
 		}
 	}
 	return "", nil
 }
 
+func legacyURLToNew(u *url.URL) *url.URL {
+	if u.Scheme == "https" || u.Scheme == "grpcs" {
+		return u
+	}
+	if IsSecure(u) {
+		return &url.URL{Scheme: u.Scheme + "s", Host: u.Host}
+	}
+	return u
+}
+
 // IsSecure parses isSecure for Endpoint URL.
+// Note: It will be deleted after some time,
+// unified use grpcs://127.0.0.1:8080 instead of grpc://127.0.0.1:8080?isSecure=true
 func IsSecure(u *url.URL) bool {
 	ok, err := strconv.ParseBool(u.Query().Get("isSecure"))
 	if err != nil {
@@ -36,3 +49,12 @@ func IsSecure(u *url.URL) bool {
 	}
 	return ok
 }
+
+// Scheme is the scheme of endpoint url.
+// examples: scheme="http",isSecure=true get "https"
+func Scheme(scheme string, isSecure bool) string {
+	if isSecure {
+		return scheme + "s"
+	}
+	return scheme
+}
diff --git a/transport/grpc/resolver/discovery/resolver.go b/transport/grpc/resolver/discovery/resolver.go
index 770fa801..6a7f6e7e 100644
--- a/transport/grpc/resolver/discovery/resolver.go
+++ b/transport/grpc/resolver/discovery/resolver.go
@@ -49,7 +49,7 @@ func (r *discoveryResolver) update(ins []*registry.ServiceInstance) {
 	addrs := make([]resolver.Address, 0)
 	endpoints := make(map[string]struct{})
 	for _, in := range ins {
-		endpoint, err := endpoint.ParseEndpoint(in.Endpoints, "grpc", !r.insecure)
+		endpoint, err := endpoint.ParseEndpoint(in.Endpoints, endpoint.Scheme("grpc", !r.insecure))
 		if err != nil {
 			log.Errorf("[resolver] Failed to parse discovery endpoint: %v", err)
 			continue
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index 8ec76884..31c03f75 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -207,6 +207,6 @@ func (s *Server) listenAndEndpoint() error {
 		_ = s.lis.Close()
 		return err
 	}
-	s.endpoint = endpoint.NewEndpoint("grpc", addr, s.tlsConf != nil)
+	s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("grpc", s.tlsConf != nil), addr)
 	return nil
 }
diff --git a/transport/http/codec.go b/transport/http/codec.go
index 79db5747..20b1a8ec 100644
--- a/transport/http/codec.go
+++ b/transport/http/codec.go
@@ -1,6 +1,7 @@
 package http
 
 import (
+	"fmt"
 	"io"
 	"net/http"
 
@@ -12,12 +13,21 @@ import (
 // SupportPackageIsVersion1 These constants should not be referenced from any other code.
 const SupportPackageIsVersion1 = true
 
+// Redirector replies to the request with a redirect to url
+// which may be a path relative to the request path.
+type Redirector interface {
+	Redirect() (string, int)
+}
+
 // Request type net/http.
 type Request = http.Request
 
 // ResponseWriter type net/http.
 type ResponseWriter = http.ResponseWriter
 
+// Flusher type net/http
+type Flusher = http.Flusher
+
 // DecodeRequestFunc is decode request func.
 type DecodeRequestFunc func(*http.Request, interface{}) error
 
@@ -31,7 +41,7 @@ type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)
 func DefaultRequestDecoder(r *http.Request, v interface{}) error {
 	codec, ok := CodecForRequest(r, "Content-Type")
 	if !ok {
-		return errors.BadRequest("CODEC", r.Header.Get("Content-Type"))
+		return errors.BadRequest("CODEC", fmt.Sprintf("unregister Content-Type: %s", r.Header.Get("Content-Type")))
 	}
 	data, err := io.ReadAll(r.Body)
 	if err != nil {
@@ -41,7 +51,7 @@ func DefaultRequestDecoder(r *http.Request, v interface{}) error {
 		return nil
 	}
 	if err = codec.Unmarshal(data, v); err != nil {
-		return errors.BadRequest("CODEC", err.Error())
+		return errors.BadRequest("CODEC", fmt.Sprintf("body unmarshal %s", err.Error()))
 	}
 	return nil
 }
@@ -49,8 +59,12 @@ func DefaultRequestDecoder(r *http.Request, v interface{}) error {
 // DefaultResponseEncoder encodes the object to the HTTP response.
 func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
 	if v == nil {
-		_, err := w.Write(nil)
-		return err
+		return nil
+	}
+	if rd, ok := v.(Redirector); ok {
+		url, code := rd.Redirect()
+		http.Redirect(w, r, url, code)
+		return nil
 	}
 	codec, _ := CodecForRequest(r, "Accept")
 	data, err := codec.Marshal(v)
diff --git a/transport/http/redirect.go b/transport/http/redirect.go
new file mode 100644
index 00000000..cb98ac09
--- /dev/null
+++ b/transport/http/redirect.go
@@ -0,0 +1,18 @@
+package http
+
+type redirect struct {
+	URL  string
+	Code int
+}
+
+func (r *redirect) Redirect() (string, int) {
+	return r.URL, r.Code
+}
+
+// NewRedirect new a redirect with url, which may be a path relative to the request path.
+// The provided code should be in the 3xx range and is usually StatusMovedPermanently, StatusFound or StatusSeeOther.
+// If the Content-Type header has not been set, Redirect sets it to "text/html; charset=utf-8" and writes a small HTML body.
+// Setting the Content-Type header to any value, including nil, disables that behavior.
+func NewRedirect(url string, code int) Redirector {
+	return &redirect{URL: url, Code: code}
+}
diff --git a/transport/http/resolver.go b/transport/http/resolver.go
index 1a4ba81c..f742c36a 100644
--- a/transport/http/resolver.go
+++ b/transport/http/resolver.go
@@ -112,7 +112,7 @@ func newResolver(ctx context.Context, discovery registry.Discovery, target *Targ
 func (r *resolver) update(services []*registry.ServiceInstance) bool {
 	nodes := make([]selector.Node, 0)
 	for _, ins := range services {
-		ept, err := endpoint.ParseEndpoint(ins.Endpoints, "http", !r.insecure)
+		ept, err := endpoint.ParseEndpoint(ins.Endpoints, endpoint.Scheme("http", !r.insecure))
 		if err != nil {
 			log.Errorf("Failed to parse (%v) discovery endpoint: %v error %v", r.target, ins.Endpoints, err)
 			continue
diff --git a/transport/http/server.go b/transport/http/server.go
index 58f3060d..c45a7269 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -224,7 +224,8 @@ func (s *Server) filter() mux.MiddlewareFunc {
 
 // Endpoint return a real address to registry endpoint.
 // examples:
-//   http://127.0.0.1:8000?isSecure=false
+//   https://127.0.0.1:8000
+//   Legacy: http://127.0.0.1:8000?isSecure=false
 func (s *Server) Endpoint() (*url.URL, error) {
 	if s.err != nil {
 		return nil, s.err
@@ -272,6 +273,6 @@ func (s *Server) listenAndEndpoint() error {
 		_ = s.lis.Close()
 		return err
 	}
-	s.endpoint = endpoint.NewEndpoint("http", addr, s.tlsConf != nil)
+	s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("http", s.tlsConf != nil), addr)
 	return nil
 }
diff --git a/version.go b/version.go
index 7f15bf01..3a3d022e 100644
--- a/version.go
+++ b/version.go
@@ -1,4 +1,4 @@
 package kratos
 
 // Release is the current kratos version.
-const Release = "v2.3.0"
+const Release = "v2.3.1"


Test patch:
diff --git a/internal/endpoint/endpoint_test.go b/internal/endpoint/endpoint_test.go
index db089288..356d8fbc 100644
--- a/internal/endpoint/endpoint_test.go
+++ b/internal/endpoint/endpoint_test.go
@@ -18,17 +18,17 @@ func TestEndPoint(t *testing.T) {
 		// TODO: Add test cases.
 		{
 			name: "grpc://127.0.0.1?isSecure=false",
-			args: args{NewEndpoint("grpc", "127.0.0.1", false)},
+			args: args{&url.URL{Scheme: "grpc", Host: "127.0.0.1", RawQuery: "isSecure=false"}},
 			want: false,
 		},
 		{
 			name: "grpc://127.0.0.1?isSecure=true",
-			args: args{NewEndpoint("http", "127.0.0.1", true)},
+			args: args{&url.URL{Scheme: "grpc", Host: "127.0.0.1", RawQuery: "isSecure=true"}},
 			want: true,
 		},
 		{
 			name: "grpc://127.0.0.1",
-			args: args{NewEndpoint("grpc", "localhost", false)},
+			args: args{&url.URL{Scheme: "grpc", Host: "127.0.0.1"}},
 			want: false,
 		},
 	}
@@ -43,9 +43,8 @@ func TestEndPoint(t *testing.T) {
 
 func TestNewEndpoint(t *testing.T) {
 	type args struct {
-		scheme   string
-		host     string
-		isSecure bool
+		scheme string
+		host   string
 	}
 	tests := []struct {
 		name string
@@ -54,18 +53,23 @@ func TestNewEndpoint(t *testing.T) {
 	}{
 		{
 			name: "https://github.com/go-kratos/kratos/",
-			args: args{"https", "github.com/go-kratos/kratos/", false},
+			args: args{"https", "github.com/go-kratos/kratos/"},
 			want: &url.URL{Scheme: "https", Host: "github.com/go-kratos/kratos/"},
 		},
 		{
 			name: "https://go-kratos.dev/",
-			args: args{"https", "go-kratos.dev/", true},
-			want: &url.URL{Scheme: "https", Host: "go-kratos.dev/", RawQuery: "isSecure=true"},
+			args: args{"https", "go-kratos.dev/"},
+			want: &url.URL{Scheme: "https", Host: "go-kratos.dev/"},
+		},
+		{
+			name: "https://www.google.com/",
+			args: args{"https", "www.google.com/"},
+			want: &url.URL{Scheme: "https", Host: "www.google.com/"},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := NewEndpoint(tt.args.scheme, tt.args.host, tt.args.isSecure); !reflect.DeepEqual(got, tt.want) {
+			if got := NewEndpoint(tt.args.scheme, tt.args.host); !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("NewEndpoint() = %v, want %v", got, tt.want)
 			}
 		})
@@ -76,7 +80,6 @@ func TestParseEndpoint(t *testing.T) {
 	type args struct {
 		endpoints []string
 		scheme    string
-		isSecure  bool
 	}
 	tests := []struct {
 		name    string
@@ -86,20 +89,46 @@ func TestParseEndpoint(t *testing.T) {
 	}{
 		{
 			name:    "kratos",
-			args:    args{endpoints: []string{"https://github.com/go-kratos/kratos?isSecure=true"}, scheme: "https", isSecure: true},
+			args:    args{endpoints: []string{"https://github.com/go-kratos/kratos"}, scheme: "https"},
 			want:    "github.com",
 			wantErr: false,
 		},
 		{
 			name:    "test",
-			args:    args{endpoints: []string{"https://go-kratos.dev/"}, scheme: "http", isSecure: true},
+			args:    args{endpoints: []string{"http://go-kratos.dev/"}, scheme: "https"},
+			want:    "",
+			wantErr: false,
+		},
+		{
+			name:    "localhost:8080",
+			args:    args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpcs"},
+			want:    "localhost:8080",
+			wantErr: false,
+		},
+		{
+			name:    "localhost:8081",
+			args:    args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpc"},
 			want:    "",
 			wantErr: false,
 		},
+
+		// Legacy
+		{
+			name:    "google",
+			args:    args{endpoints: []string{"grpc://www.google.com/?isSecure=true"}, scheme: "grpcs"},
+			want:    "www.google.com",
+			wantErr: false,
+		},
+		{
+			name:    "baidu",
+			args:    args{endpoints: []string{"http://www.baidu.com/?isSecure=true"}, scheme: "https"},
+			want:    "www.baidu.com",
+			wantErr: false,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := ParseEndpoint(tt.args.endpoints, tt.args.scheme, tt.args.isSecure)
+			got, err := ParseEndpoint(tt.args.endpoints, tt.args.scheme)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("ParseEndpoint() error = %v, wantErr %v", err, tt.wantErr)
 				return
diff --git a/transport/http/redirect_test.go b/transport/http/redirect_test.go
new file mode 100644
index 00000000..b98bf4f0
--- /dev/null
+++ b/transport/http/redirect_test.go
@@ -0,0 +1,23 @@
+package http
+
+import (
+	"net/http/httptest"
+	"testing"
+)
+
+func TestRedirect(t *testing.T) {
+	var (
+		redirectURL  = "/redirect"
+		redirectCode = 302
+	)
+	r := httptest.NewRequest("POST", "/test", nil)
+	w := httptest.NewRecorder()
+	_ = DefaultResponseEncoder(w, r, NewRedirect(redirectURL, redirectCode))
+
+	if w.Code != redirectCode {
+		t.Fatalf("want %d but got %d", redirectCode, w.Code)
+	}
+	if v := w.Header().Get("Location"); v != redirectURL {
+		t.Fatalf("want %s but got %s", redirectURL, v)
+	}
+}