From 75ab005e05850981648a244311b6c6c0f3e90e28 Mon Sep 17 00:00:00 2001 From: Wenju Gao Date: Tue, 31 Jan 2023 09:59:08 +0800 Subject: [PATCH 1/7] test: fix netpoll ut in windows (#536) Co-authored-by: kinggo Co-authored-by: yinxuran.lucky --- .github/workflows/tests.yml | 22 ++-- .golangci.yaml | 35 +++++++ cmd/hz/protobuf/api/api.pb.go | 3 +- licenses/LICENSE-fsnotify | 25 +++++ pkg/app/client/client_test.go | 37 +++---- pkg/app/client/client_unix_test.go | 43 ++++++++ pkg/app/client/client_windows_test.go | 38 +++++++ pkg/app/client/option.go | 3 +- pkg/app/server/hertz_test.go | 113 ++------------------ pkg/app/server/hertz_unix_test.go | 136 +++++++++++++++++++++++++ pkg/app/server/render/html.go | 13 +-- pkg/app/server/render/html_test.go | 16 +-- pkg/network/netpoll/connection.go | 35 +++---- pkg/network/netpoll/connection_test.go | 32 +++--- pkg/network/netpoll/dial.go | 3 - pkg/network/netpoll/transport.go | 31 +++--- pkg/network/utils_test.go | 5 + pkg/protocol/uri_test.go | 62 +++++++++++ pkg/route/engine_test.go | 24 ++--- 19 files changed, 444 insertions(+), 232 deletions(-) create mode 100644 .golangci.yaml create mode 100644 licenses/LICENSE-fsnotify create mode 100644 pkg/app/client/client_unix_test.go create mode 100644 pkg/app/client/client_windows_test.go create mode 100644 pkg/app/server/hertz_unix_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e42b00f7..1adafe427 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,18 +22,18 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - name: Lint - run: | - go vet -stdmethods=false $(go list ./...) - go install mvdan.cc/gofumpt@v0.2.0 - test -z "$(gofumpt -l -extra .)" + - name: Golangci Lint + # https://golangci-lint.run/ + uses: golangci/golangci-lint-action@v3 + with: + version: latest - name: Unit Test run: go test -race -covermode=atomic -coverprofile=coverage.txt ./... - name: Codecov run: bash <(curl -s https://codecov.io/bash) - lint-and-ut-windows: + ut-windows: strategy: matrix: version: [ 1.18, 1.19 ] @@ -52,13 +52,5 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - name: Lint - run: | - go vet -stdmethods=false $(go list ./...) - go install mvdan.cc/gofumpt@v0.2.0 - test -z "$(gofumpt -l -extra .)" - name: Unit Test - run: go test -race -covermode=atomic -coverprofile=coverage.txt ./... -# -# - name: Codecov -# run: bash <(curl -s https://codecov.io/bash) + run: go test -race -covermode=atomic ./... diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 000000000..23c8d3629 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,35 @@ +# Options for analysis running. +run: + # include `vendor` `third_party` `testdata` `examples` `Godeps` `builtin` + skip-dirs-use-default: true +# output configuration options + + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 30m + +output: + # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions + format: colored-line-number +# All available settings of specific linters. +# Refer to https://golangci-lint.run/usage/linters +linters-settings: + govet: + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + disable: + - stdmethods +linters: + enable: + - gofumpt + - goimports + - gofmt + - govet + disable: + - errcheck + - typecheck + - deadcode + - varcheck + - staticcheck +issues: + exclude-use-default: true \ No newline at end of file diff --git a/cmd/hz/protobuf/api/api.pb.go b/cmd/hz/protobuf/api/api.pb.go index 219a0133e..6a38b1b2c 100644 --- a/cmd/hz/protobuf/api/api.pb.go +++ b/cmd/hz/protobuf/api/api.pb.go @@ -7,10 +7,11 @@ package api import ( + reflect "reflect" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" descriptorpb "google.golang.org/protobuf/types/descriptorpb" - reflect "reflect" ) const ( diff --git a/licenses/LICENSE-fsnotify b/licenses/LICENSE-fsnotify new file mode 100644 index 000000000..c656a4322 --- /dev/null +++ b/licenses/LICENSE-fsnotify @@ -0,0 +1,25 @@ +Copyright © 2012 The Go Authors. All rights reserved. +Copyright © fsnotify Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +* Neither the name of Google Inc. nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go index 2798f12e6..23b1eea51 100644 --- a/pkg/app/client/client_test.go +++ b/pkg/app/client/client_test.go @@ -48,7 +48,6 @@ import ( "errors" "fmt" "io" - "math/rand" "net" "net/http" "net/http/httptest" @@ -70,7 +69,6 @@ import ( "github.com/cloudwego/hertz/pkg/common/test/assert" "github.com/cloudwego/hertz/pkg/network" "github.com/cloudwego/hertz/pkg/network/dialer" - "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" @@ -1897,24 +1895,13 @@ func (m *mockDialer) DialConnection(network, address string, timeout time.Durati return m.Dialer.DialConnection(m.network, m.address, m.timeout, tlsConfig) } -func newMockDialerWithCustomFunc(network, address string, timeout time.Duration, f func(network, address string, timeout time.Duration, tlsConfig *tls.Config)) network.Dialer { - dialer := standard.NewDialer() - if rand.Intn(2) == 0 { - dialer = netpoll.NewDialer() - } - return &mockDialer{ - Dialer: dialer, - customDialerFunc: f, - network: network, - address: address, - timeout: timeout, - } -} - func TestClientRetry(t *testing.T) { t.Parallel() client, err := NewClient( - WithDialTimeout(2*time.Second), + // Default dial function performs different in different os. So unit the performance of dial function. + WithDialFunc(func(addr string) (network.Conn, error) { + return nil, fmt.Errorf("dial tcp %s: i/o timeout", addr) + }), WithRetryConfig( retry.WithMaxAttemptTimes(3), retry.WithInitDelay(100*time.Millisecond), @@ -1943,7 +1930,9 @@ func TestClientRetry(t *testing.T) { } client2, err := NewClient( - WithDialTimeout(2*time.Second), + WithDialFunc(func(addr string) (network.Conn, error) { + return nil, fmt.Errorf("dial tcp %s: i/o timeout", addr) + }), WithRetryConfig( retry.WithMaxAttemptTimes(2), retry.WithInitDelay(500*time.Millisecond), @@ -1972,7 +1961,9 @@ func TestClientRetry(t *testing.T) { } client3, err := NewClient( - WithDialTimeout(2*time.Second), + WithDialFunc(func(addr string) (network.Conn, error) { + return nil, fmt.Errorf("dial tcp %s: i/o timeout", addr) + }), WithRetryConfig( retry.WithMaxAttemptTimes(2), retry.WithInitDelay(100*time.Millisecond), @@ -2002,7 +1993,9 @@ func TestClientRetry(t *testing.T) { } client4, err := NewClient( - WithDialTimeout(2*time.Second), + WithDialFunc(func(addr string) (network.Conn, error) { + return nil, fmt.Errorf("dial tcp %s: i/o timeout", addr) + }), WithRetryConfig( retry.WithMaxAttemptTimes(2), retry.WithInitDelay(1*time.Second), @@ -2045,12 +2038,12 @@ func TestClientDialerName(t *testing.T) { t.Errorf("expected 'netpoll', but get %s", dName) } - client, _ = NewClient(WithDialer(netpoll.NewDialer())) + client, _ = NewClient(WithDialer(&mockDialer{})) dName, err = client.GetDialerName() if err != nil { t.Fatalf("unexpected error: %v", err) } - if dName != "netpoll" { + if dName != "client" { t.Errorf("expected 'standard', but get %s", dName) } diff --git a/pkg/app/client/client_unix_test.go b/pkg/app/client/client_unix_test.go new file mode 100644 index 000000000..94869e8f2 --- /dev/null +++ b/pkg/app/client/client_unix_test.go @@ -0,0 +1,43 @@ +// Copyright 2023 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. +// + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package client + +import ( + "crypto/tls" + "math/rand" + "time" + + "github.com/cloudwego/hertz/pkg/network" + "github.com/cloudwego/hertz/pkg/network/netpoll" + "github.com/cloudwego/hertz/pkg/network/standard" +) + +func newMockDialerWithCustomFunc(network, address string, timeout time.Duration, f func(network, address string, timeout time.Duration, tlsConfig *tls.Config)) network.Dialer { + dialer := standard.NewDialer() + if rand.Intn(2) == 0 { + dialer = netpoll.NewDialer() + } + return &mockDialer{ + Dialer: dialer, + customDialerFunc: f, + network: network, + address: address, + timeout: timeout, + } +} diff --git a/pkg/app/client/client_windows_test.go b/pkg/app/client/client_windows_test.go new file mode 100644 index 000000000..63e60fd43 --- /dev/null +++ b/pkg/app/client/client_windows_test.go @@ -0,0 +1,38 @@ +// Copyright 2023 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. +// + +//go:build windows +// +build windows + +package client + +import ( + "crypto/tls" + "time" + + "github.com/cloudwego/hertz/pkg/network" + "github.com/cloudwego/hertz/pkg/network/standard" +) + +func newMockDialerWithCustomFunc(network, address string, timeout time.Duration, f func(network, address string, timeout time.Duration, tlsConfig *tls.Config)) network.Dialer { + dialer := standard.NewDialer() + return &mockDialer{ + Dialer: dialer, + customDialerFunc: f, + network: network, + address: address, + timeout: timeout, + } +} diff --git a/pkg/app/client/option.go b/pkg/app/client/option.go index 34f4196b4..3e03c4431 100644 --- a/pkg/app/client/option.go +++ b/pkg/app/client/option.go @@ -180,8 +180,7 @@ type customDialer struct { dialFunc network.DialFunc } -func (m *customDialer) DialConnection(network, address string, timeout time.Duration, - tlsConfig *tls.Config) (conn network.Conn, err error) { +func (m *customDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) { if m.dialFunc != nil { return m.dialFunc(address) } diff --git a/pkg/app/server/hertz_test.go b/pkg/app/server/hertz_test.go index de6a5933b..b651550b5 100644 --- a/pkg/app/server/hertz_test.go +++ b/pkg/app/server/hertz_test.go @@ -25,13 +25,9 @@ import ( "io" "net" "net/http" - "os" - "os/exec" - "strconv" "strings" "sync" "sync/atomic" - "syscall" "testing" "time" @@ -49,7 +45,6 @@ import ( "github.com/cloudwego/hertz/pkg/protocol/consts" "github.com/cloudwego/hertz/pkg/protocol/http1/req" "github.com/cloudwego/hertz/pkg/protocol/http1/resp" - "golang.org/x/sys/unix" ) func TestHertz_Run(t *testing.T) { @@ -147,64 +142,6 @@ func TestHertz_GracefulShutdown(t *testing.T) { cancel() } -func TestHertz_Spin(t *testing.T) { - engine := New(WithHostPorts("127.0.0.1:6668")) - engine.GET("/test", func(c context.Context, ctx *app.RequestContext) { - time.Sleep(time.Second * 2) - path := ctx.Request.URI().PathOriginal() - ctx.SetBodyString(string(path)) - }) - engine.GET("/test2", func(c context.Context, ctx *app.RequestContext) {}) - - testint := uint32(0) - engine.Engine.OnShutdown = append(engine.OnShutdown, func(ctx context.Context) { - atomic.StoreUint32(&testint, 1) - }) - - go engine.Spin() - time.Sleep(time.Millisecond) - - hc := http.Client{Timeout: time.Second} - var err error - var resp *http.Response - ch := make(chan struct{}) - ch2 := make(chan struct{}) - go func() { - ticker := time.NewTicker(time.Millisecond * 100) - defer ticker.Stop() - for range ticker.C { - _, err := hc.Get("http://127.0.0.1:6668/test2") - t.Logf("[%v]begin listening\n", time.Now()) - if err != nil { - t.Logf("[%v]listening closed: %v", time.Now(), err) - ch2 <- struct{}{} - break - } - } - }() - go func() { - t.Logf("[%v]begin request\n", time.Now()) - resp, err = http.Get("http://127.0.0.1:6668/test") - t.Logf("[%v]end request\n", time.Now()) - ch <- struct{}{} - }() - - time.Sleep(time.Second * 1) - pid := strconv.Itoa(os.Getpid()) - cmd := exec.Command("kill", "-SIGHUP", pid) - t.Logf("[%v]begin SIGHUP\n", time.Now()) - if err := cmd.Run(); err != nil { - t.Fatal(err) - } - t.Logf("[%v]end SIGHUP\n", time.Now()) - <-ch - assert.Nil(t, err) - assert.NotNil(t, resp) - assert.DeepEqual(t, uint32(1), atomic.LoadUint32(&testint)) - - <-ch2 -} - func TestLoadHTMLGlob(t *testing.T) { engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:8890")) engine.Delims("{[{", "}]}") @@ -415,8 +352,6 @@ func TestEnoughBodySize(t *testing.T) { } func TestRequestCtxHijack(t *testing.T) { - t.Parallel() - hijackStartCh := make(chan struct{}) hijackStopCh := make(chan struct{}) engine := New() @@ -595,44 +530,6 @@ func TestDuplicateReleaseBodyStream(t *testing.T) { wg.Wait() } -func TestReusePorts(t *testing.T) { - cfg := &net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error { - return c.Control(func(fd uintptr) { - syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1) - syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1) - }) - }} - ha := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg), WithTransport(standard.NewTransporter)) - hb := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg), WithTransport(standard.NewTransporter)) - hc := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg)) - hd := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg)) - ha.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) - }) - hc.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) - }) - hd.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) - }) - hb.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) - }) - go ha.Run() - go hb.Run() - go hc.Run() - go hd.Run() - time.Sleep(time.Second) - - client, _ := c.NewClient() - for i := 0; i < 1000; i++ { - statusCode, body, err := client.Get(context.Background(), nil, "http://localhost:10093/ping") - assert.Nil(t, err) - assert.DeepEqual(t, consts.StatusOK, statusCode) - assert.DeepEqual(t, "{\"ping\":\"pong\"}", string(body)) - } -} - func TestServiceRegisterFailed(t *testing.T) { mockRegErr := errors.New("mock register error") var rCount int32 @@ -783,6 +680,10 @@ func TestReuseCtx(t *testing.T) { } } +type CloseWithoutResetBuffer interface { + CloseNoResetBuffer() error +} + func TestOnprepare(t *testing.T) { h := New( WithHostPorts("localhost:9229"), @@ -790,7 +691,11 @@ func TestOnprepare(t *testing.T) { b, err := conn.Peek(3) assert.Nil(t, err) assert.DeepEqual(t, string(b), "GET") - conn.Close() + if c, ok := conn.(CloseWithoutResetBuffer); ok { + c.CloseNoResetBuffer() + } else { + conn.Close() + } return ctx })) h.GET("/ping", func(ctx context.Context, c *app.RequestContext) { diff --git a/pkg/app/server/hertz_unix_test.go b/pkg/app/server/hertz_unix_test.go new file mode 100644 index 000000000..b37ddfbdf --- /dev/null +++ b/pkg/app/server/hertz_unix_test.go @@ -0,0 +1,136 @@ +// Copyright 2023 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. +// + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package server + +import ( + "context" + "net" + "net/http" + "os" + "os/exec" + "strconv" + "sync/atomic" + "syscall" + "testing" + "time" + + "github.com/cloudwego/hertz/pkg/app" + c "github.com/cloudwego/hertz/pkg/app/client" + "github.com/cloudwego/hertz/pkg/common/test/assert" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/network/standard" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "golang.org/x/sys/unix" +) + +func TestReusePorts(t *testing.T) { + cfg := &net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1) + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }) + }} + ha := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg), WithTransport(standard.NewTransporter)) + hb := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg), WithTransport(standard.NewTransporter)) + hc := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg)) + hd := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg)) + ha.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + hc.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + hd.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + hb.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + go ha.Run() + go hb.Run() + go hc.Run() + go hd.Run() + time.Sleep(time.Second) + + client, _ := c.NewClient() + for i := 0; i < 1000; i++ { + statusCode, body, err := client.Get(context.Background(), nil, "http://localhost:10093/ping") + assert.Nil(t, err) + assert.DeepEqual(t, consts.StatusOK, statusCode) + assert.DeepEqual(t, "{\"ping\":\"pong\"}", string(body)) + } +} + +func TestHertz_Spin(t *testing.T) { + engine := New(WithHostPorts("127.0.0.1:6668")) + engine.GET("/test", func(c context.Context, ctx *app.RequestContext) { + time.Sleep(time.Second * 2) + path := ctx.Request.URI().PathOriginal() + ctx.SetBodyString(string(path)) + }) + engine.GET("/test2", func(c context.Context, ctx *app.RequestContext) {}) + + testint := uint32(0) + engine.Engine.OnShutdown = append(engine.OnShutdown, func(ctx context.Context) { + atomic.StoreUint32(&testint, 1) + }) + + go engine.Spin() + time.Sleep(time.Millisecond) + + hc := http.Client{Timeout: time.Second} + var err error + var resp *http.Response + ch := make(chan struct{}) + ch2 := make(chan struct{}) + go func() { + ticker := time.NewTicker(time.Millisecond * 100) + defer ticker.Stop() + for range ticker.C { + _, err := hc.Get("http://127.0.0.1:6668/test2") + t.Logf("[%v]begin listening\n", time.Now()) + if err != nil { + t.Logf("[%v]listening closed: %v", time.Now(), err) + ch2 <- struct{}{} + break + } + } + }() + go func() { + t.Logf("[%v]begin request\n", time.Now()) + resp, err = http.Get("http://127.0.0.1:6668/test") + t.Logf("[%v]end request\n", time.Now()) + ch <- struct{}{} + }() + + time.Sleep(time.Second * 1) + pid := strconv.Itoa(os.Getpid()) + cmd := exec.Command("kill", "-SIGHUP", pid) + t.Logf("[%v]begin SIGHUP\n", time.Now()) + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + t.Logf("[%v]end SIGHUP\n", time.Now()) + <-ch + assert.Nil(t, err) + assert.NotNil(t, resp) + assert.DeepEqual(t, uint32(1), atomic.LoadUint32(&testint)) + + <-ch2 +} diff --git a/pkg/app/server/render/html.go b/pkg/app/server/render/html.go index f31e33a85..2a9fe8afa 100644 --- a/pkg/app/server/render/html.go +++ b/pkg/app/server/render/html.go @@ -113,7 +113,6 @@ type HTMLDebug struct { sync.Once Template *template.Template RefreshInterval time.Duration - updateTimeStamp time.Time Files []string FuncMap template.FuncMap @@ -161,14 +160,10 @@ func (h *HTMLDebug) startChecker() { if h.RefreshInterval > 0 { go func() { hlog.SystemLogger().Debugf("[HTMLDebug] HTML template reloader started with interval %v", h.RefreshInterval) - for { - n := time.Now() - if n.UTC().Sub(h.updateTimeStamp.UTC()) > h.RefreshInterval { - hlog.SystemLogger().Debugf("[HTMLDebug] triggering HTML template reloader") - h.reloadCh <- struct{}{} - hlog.SystemLogger().Debugf("[HTMLDebug] HTML template has been reloaded, next reload in %v", h.RefreshInterval) - h.updateTimeStamp = time.Now() - } + for range time.Tick(h.RefreshInterval) { + hlog.SystemLogger().Debugf("[HTMLDebug] triggering HTML template reloader") + h.reloadCh <- struct{}{} + hlog.SystemLogger().Debugf("[HTMLDebug] HTML template has been reloaded, next reload in %v", h.RefreshInterval) } }() return diff --git a/pkg/app/server/render/html_test.go b/pkg/app/server/render/html_test.go index 86fa98103..aca0966a8 100644 --- a/pkg/app/server/render/html_test.go +++ b/pkg/app/server/render/html_test.go @@ -23,7 +23,7 @@ import ( ) func TestHTMLDebug_StartChecker_timer(t *testing.T) { - render := &HTMLDebug{RefreshInterval: 2 * time.Second} + render := &HTMLDebug{RefreshInterval: time.Second} select { case <-render.reloadCh: t.Fatalf("should not be triggered") @@ -31,17 +31,9 @@ func TestHTMLDebug_StartChecker_timer(t *testing.T) { } render.startChecker() select { - case <-time.After(50 * time.Millisecond): - t.Fatalf("should be triggered immediately") + case <-time.After(render.RefreshInterval + 100*time.Millisecond): + t.Fatalf("should be triggered in 1 second") case <-render.reloadCh: - - } - select { - // some ci servers have poor computing power, so we add some extra time here. - case <-time.After(2*time.Second + 50*time.Millisecond): - t.Fatalf("should be triggered before 2 seconds") - case <-render.reloadCh: - t.Logf("paas") } } @@ -59,7 +51,7 @@ func TestHTMLDebug_StartChecker_fs_watcher(t *testing.T) { } render.startChecker() f.Write([]byte("hello")) - + f.Sync() select { case <-time.After(50 * time.Millisecond): t.Fatalf("should be triggered immediately") diff --git a/pkg/network/netpoll/connection.go b/pkg/network/netpoll/connection.go index 683f9c36f..7c26ef914 100644 --- a/pkg/network/netpoll/connection.go +++ b/pkg/network/netpoll/connection.go @@ -1,21 +1,17 @@ -//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. - */ +// 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 netpoll @@ -29,7 +25,6 @@ import ( "github.com/cloudwego/hertz/pkg/common/hlog" "github.com/cloudwego/hertz/pkg/network" "github.com/cloudwego/netpoll" - "golang.org/x/sys/unix" ) type Conn struct { @@ -37,7 +32,7 @@ type Conn struct { } func (c *Conn) ToHertzError(err error) error { - if errors.Is(err, netpoll.ErrConnClosed) || errors.Is(err, unix.EPIPE) { + if errors.Is(err, netpoll.ErrConnClosed) || errors.Is(err, syscall.EPIPE) { return errs.ErrConnectionClosed } return err diff --git a/pkg/network/netpoll/connection_test.go b/pkg/network/netpoll/connection_test.go index 893b328d4..26610567a 100644 --- a/pkg/network/netpoll/connection_test.go +++ b/pkg/network/netpoll/connection_test.go @@ -1,18 +1,20 @@ -/* - * 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. - */ +// 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. +// + +//go:build !windows +// +build !windows package netpoll diff --git a/pkg/network/netpoll/dial.go b/pkg/network/netpoll/dial.go index 70adbc108..86a371eb1 100644 --- a/pkg/network/netpoll/dial.go +++ b/pkg/network/netpoll/dial.go @@ -13,9 +13,6 @@ // limitations under the License. // -//go:build !windows -// +build !windows - package netpoll import ( diff --git a/pkg/network/netpoll/transport.go b/pkg/network/netpoll/transport.go index c2130607b..12e54a914 100644 --- a/pkg/network/netpoll/transport.go +++ b/pkg/network/netpoll/transport.go @@ -1,22 +1,21 @@ +// 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. +// + //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 netpoll import ( diff --git a/pkg/network/utils_test.go b/pkg/network/utils_test.go index 73ef33b1e..0d713998a 100644 --- a/pkg/network/utils_test.go +++ b/pkg/network/utils_test.go @@ -18,10 +18,15 @@ package network import ( "os" + "runtime" "testing" ) func TestUnlinkUdsFile(t *testing.T) { + if runtime.GOOS == "windows" { + t.SkipNow() + } + tmp := "tmpFile" var err error diff --git a/pkg/protocol/uri_test.go b/pkg/protocol/uri_test.go index 958873483..e245faf26 100644 --- a/pkg/protocol/uri_test.go +++ b/pkg/protocol/uri_test.go @@ -44,6 +44,7 @@ package protocol import ( "path/filepath" "reflect" + "runtime" "testing" "github.com/cloudwego/hertz/pkg/common/test/assert" @@ -374,6 +375,67 @@ func TestParsePathWindows(t *testing.T) { testParsePathWindows(t, "/..%5c..%5cfoo", "/foo") } +func TestURIPathNormalize(t *testing.T) { + if runtime.GOOS == "windows" { + t.SkipNow() + } + + t.Parallel() + + var u URI + + // double slash + testURIPathNormalize(t, &u, "/aa//bb", "/aa/bb") + + // triple slash + testURIPathNormalize(t, &u, "/x///y/", "/x/y/") + + // multi slashes + testURIPathNormalize(t, &u, "/abc//de///fg////", "/abc/de/fg/") + + // encoded slashes + testURIPathNormalize(t, &u, "/xxxx%2fyyy%2f%2F%2F", "/xxxx/yyy/") + + // dotdot + testURIPathNormalize(t, &u, "/aaa/..", "/") + + // dotdot with trailing slash + testURIPathNormalize(t, &u, "/xxx/yyy/../", "/xxx/") + + // multi dotdots + testURIPathNormalize(t, &u, "/aaa/bbb/ccc/../../ddd", "/aaa/ddd") + + // dotdots separated by other data + testURIPathNormalize(t, &u, "/a/b/../c/d/../e/..", "/a/c/") + + // too many dotdots + testURIPathNormalize(t, &u, "/aaa/../../../../xxx", "/xxx") + testURIPathNormalize(t, &u, "/../../../../../..", "/") + testURIPathNormalize(t, &u, "/../../../../../../", "/") + + // encoded dotdots + testURIPathNormalize(t, &u, "/aaa%2Fbbb%2F%2E.%2Fxxx", "/aaa/xxx") + + // double slash with dotdots + testURIPathNormalize(t, &u, "/aaa////..//b", "/b") + + // fake dotdot + testURIPathNormalize(t, &u, "/aaa/..bbb/ccc/..", "/aaa/..bbb/") + + // single dot + testURIPathNormalize(t, &u, "/a/./b/././c/./d.html", "/a/b/c/d.html") + testURIPathNormalize(t, &u, "./foo/", "/foo/") + testURIPathNormalize(t, &u, "./../.././../../aaa/bbb/../../../././../", "/") + testURIPathNormalize(t, &u, "./a/./.././../b/./foo.html", "/b/foo.html") +} + +func testURIPathNormalize(t *testing.T, u *URI, requestURI, expectedPath string) { + u.Parse(nil, []byte(requestURI)) //nolint:errcheck + if string(u.Path()) != expectedPath { + t.Fatalf("Unexpected path %q. Expected %q. requestURI=%q", u.Path(), expectedPath, requestURI) + } +} + func testParsePathWindows(t *testing.T, path, expectedPath string) { var u URI u.Parse(nil, []byte(path)) diff --git a/pkg/route/engine_test.go b/pkg/route/engine_test.go index f9f152834..d73bc8c11 100644 --- a/pkg/route/engine_test.go +++ b/pkg/route/engine_test.go @@ -59,7 +59,6 @@ import ( "github.com/cloudwego/hertz/pkg/common/test/assert" "github.com/cloudwego/hertz/pkg/common/test/mock" "github.com/cloudwego/hertz/pkg/network" - "github.com/cloudwego/hertz/pkg/network/netpoll" "github.com/cloudwego/hertz/pkg/network/standard" "github.com/cloudwego/hertz/pkg/protocol/consts" ) @@ -75,16 +74,16 @@ func TestNew_Engine(t *testing.T) { } func TestNew_Engine_WithTransporter(t *testing.T) { - defaultTransporter = netpoll.NewTransporter + defaultTransporter = newMockTransporter opt := config.NewOptions([]config.Option{}) router := NewEngine(opt) - assert.DeepEqual(t, "netpoll", router.GetTransporterName()) + assert.DeepEqual(t, "route", router.GetTransporterName()) - defaultTransporter = netpoll.NewTransporter + defaultTransporter = newMockTransporter opt.TransporterNewer = standard.NewTransporter router = NewEngine(opt) assert.DeepEqual(t, "standard", router.GetTransporterName()) - assert.DeepEqual(t, "netpoll", GetTransporterName()) + assert.DeepEqual(t, "route", GetTransporterName()) } func TestGetTransporterName(t *testing.T) { @@ -286,8 +285,8 @@ func TestIdleTimeout03(t *testing.T) { t.Errorf("err should be ErrShortConnection, but got %s", err) } return - case <-time.Tick(120 * time.Millisecond): - t.Errorf("timeout! should have been finished in 120ms...") + case <-time.Tick(200 * time.Millisecond): + t.Errorf("timeout! should have been finished in 200ms...") } } @@ -419,18 +418,17 @@ func TestRenderHtml(t *testing.T) { } func TestTransporterName(t *testing.T) { - SetTransporter(netpoll.NewTransporter) - assert.DeepEqual(t, "netpoll", GetTransporterName()) - SetTransporter(standard.NewTransporter) assert.DeepEqual(t, "standard", GetTransporterName()) - SetTransporter(func(options *config.Options) network.Transporter { - return &mockTransporter{} - }) + SetTransporter(newMockTransporter) assert.DeepEqual(t, "route", GetTransporterName()) } +func newMockTransporter(options *config.Options) network.Transporter { + return &mockTransporter{} +} + type mockTransporter struct{} func (m *mockTransporter) ListenAndServe(onData network.OnData) (err error) { From 99111566547f8f1e10aea19d567af39bdab923e5 Mon Sep 17 00:00:00 2001 From: kinggo Date: Tue, 31 Jan 2023 17:11:38 +0800 Subject: [PATCH 2/7] feat: add PeekAll function (#569) --- pkg/protocol/args.go | 10 ++++ pkg/protocol/header.go | 114 ++++++++++++++++++++++++++++++++++++ pkg/protocol/header_test.go | 69 ++++++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/pkg/protocol/args.go b/pkg/protocol/args.go index 5731db570..ebf9ae56c 100644 --- a/pkg/protocol/args.go +++ b/pkg/protocol/args.go @@ -224,6 +224,16 @@ func peekArgBytes(h []argsKV, k []byte) []byte { return nil } +func peekAllArgBytesToDst(dst [][]byte, h []argsKV, k []byte) [][]byte { + for i, n := 0, len(h); i < n; i++ { + kv := &h[i] + if bytes.Equal(kv.key, k) { + dst = append(dst, kv.value) + } + } + return dst +} + func delAllArgsBytes(args []argsKV, key []byte) []argsKV { return delAllArgs(args, bytesconv.B2s(key)) } diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go index b447ccef1..dfc5c8d81 100644 --- a/pkg/protocol/header.go +++ b/pkg/protocol/header.go @@ -83,6 +83,7 @@ type RequestHeader struct { host []byte contentType []byte userAgent []byte + mulHeader [][]byte h []argsKV bufKV argsKV @@ -121,6 +122,7 @@ type ResponseHeader struct { contentType []byte server []byte + mulHeader [][]byte h []argsKV bufKV argsKV @@ -486,6 +488,7 @@ func (h *ResponseHeader) ResetSkipNormalize() { h.h = h.h[:0] h.cookies = h.cookies[:0] + h.mulHeader = h.mulHeader[:0] } // ContentLength returns Content-Length header value. @@ -687,6 +690,94 @@ func (h *ResponseHeader) peek(key []byte) []byte { } } +// PeekAll returns all header value for the given key. +// +// The returned value is valid until the request is released, +// either though ReleaseResponse or your request handler returning. +// Any future calls to the Peek* will modify the returned value. +// Do not store references to returned value. Use ResponseHeader.GetAll(key) instead. +func (h *ResponseHeader) PeekAll(key string) [][]byte { + k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing) + return h.peekAll(k) +} + +func (h *ResponseHeader) peekAll(key []byte) [][]byte { + h.mulHeader = h.mulHeader[:0] + switch string(key) { + case consts.HeaderContentType: + if contentType := h.ContentType(); len(contentType) > 0 { + h.mulHeader = append(h.mulHeader, contentType) + } + case consts.HeaderContentEncoding: + if contentEncoding := h.ContentEncoding(); len(contentEncoding) > 0 { + h.mulHeader = append(h.mulHeader, contentEncoding) + } + case consts.HeaderServer: + if server := h.Server(); len(server) > 0 { + h.mulHeader = append(h.mulHeader, server) + } + case consts.HeaderConnection: + if h.ConnectionClose() { + h.mulHeader = append(h.mulHeader, bytestr.StrClose) + } else { + h.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key) + } + case consts.HeaderContentLength: + h.mulHeader = append(h.mulHeader, h.contentLengthBytes) + case consts.HeaderSetCookie: + h.mulHeader = append(h.mulHeader, appendResponseCookieBytes(nil, h.cookies)) + default: + h.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key) + } + return h.mulHeader +} + +// PeekAll returns all header value for the given key. +// +// The returned value is valid until the request is released, +// either though ReleaseRequest or your request handler returning. +// Any future calls to the Peek* will modify the returned value. +// Do not store references to returned value. Use RequestHeader.GetAll(key) instead. +func (h *RequestHeader) PeekAll(key string) [][]byte { + k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing) + return h.peekAll(k) +} + +func (h *RequestHeader) peekAll(key []byte) [][]byte { + h.mulHeader = h.mulHeader[:0] + switch string(key) { + case consts.HeaderHost: + if host := h.Host(); len(host) > 0 { + h.mulHeader = append(h.mulHeader, host) + } + case consts.HeaderContentType: + if contentType := h.ContentType(); len(contentType) > 0 { + h.mulHeader = append(h.mulHeader, contentType) + } + case consts.HeaderUserAgent: + if ua := h.UserAgent(); len(ua) > 0 { + h.mulHeader = append(h.mulHeader, ua) + } + case consts.HeaderConnection: + if h.ConnectionClose() { + h.mulHeader = append(h.mulHeader, bytestr.StrClose) + } else { + h.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key) + } + case consts.HeaderContentLength: + h.mulHeader = append(h.mulHeader, h.contentLengthBytes) + case consts.HeaderCookie: + if h.cookiesCollected { + h.mulHeader = append(h.mulHeader, appendRequestCookieBytes(nil, h.cookies)) + } else { + h.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key) + } + default: + h.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key) + } + return h.mulHeader +} + // SetContentTypeBytes sets Content-Type header value. func (h *ResponseHeader) SetContentTypeBytes(contentType []byte) { h.contentType = append(h.contentType[:0], contentType...) @@ -1299,6 +1390,7 @@ func (h *RequestHeader) ResetSkipNormalize() { h.cookiesCollected = false h.rawHeaders = h.rawHeaders[:0] + h.mulHeader = h.mulHeader[:0] } func peekRawHeader(buf, key []byte) []byte { @@ -1482,6 +1574,28 @@ func (h *ResponseHeader) Get(key string) string { return string(h.Peek(key)) } +// GetAll returns all header value for the given key +// it is concurrent safety and long lifetime. +func (h *RequestHeader) GetAll(key string) []string { + res := make([]string, 0) + headers := h.PeekAll(key) + for _, header := range headers { + res = append(res, string(header)) + } + return res +} + +// GetAll returns all header value for the given key and is concurrent safety. +// it is concurrent safety and long lifetime. +func (h *ResponseHeader) GetAll(key string) []string { + res := make([]string, 0) + headers := h.PeekAll(key) + for _, header := range headers { + res = append(res, string(header)) + } + return res +} + func appendHeaderLine(dst, key, value []byte) []byte { dst = append(dst, key...) dst = append(dst, bytestr.StrColonSpace...) diff --git a/pkg/protocol/header_test.go b/pkg/protocol/header_test.go index 0a7baf2fd..fc58a29c0 100644 --- a/pkg/protocol/header_test.go +++ b/pkg/protocol/header_test.go @@ -572,3 +572,72 @@ func TestRequestHeaderSetNoDefaultContentType(t *testing.T) { b = h.AppendBytes(nil) assert.DeepEqual(t, b, []byte("POST / HTTP/1.1\r\n\r\n")) } + +func TestRequestHeader_PeekAll(t *testing.T) { + t.Parallel() + h := &RequestHeader{} + h.Add(consts.HeaderConnection, "keep-alive") + h.Add("Content-Type", "aaa") + h.Add(consts.HeaderHost, "aaabbb") + h.Add("User-Agent", "asdfas") + h.Add("Content-Length", "1123") + h.Add("Cookie", "foobar=baz") + h.Add("aaa", "aaa") + h.Add("aaa", "bbb") + + expectRequestHeaderAll(t, h, consts.HeaderConnection, [][]byte{[]byte("keep-alive")}) + expectRequestHeaderAll(t, h, "Content-Type", [][]byte{[]byte("aaa")}) + expectRequestHeaderAll(t, h, consts.HeaderHost, [][]byte{[]byte("aaabbb")}) + expectRequestHeaderAll(t, h, "User-Agent", [][]byte{[]byte("asdfas")}) + expectRequestHeaderAll(t, h, "Content-Length", [][]byte{[]byte("1123")}) + expectRequestHeaderAll(t, h, "Cookie", [][]byte{[]byte("foobar=baz")}) + expectRequestHeaderAll(t, h, "aaa", [][]byte{[]byte("aaa"), []byte("bbb")}) + + h.DelBytes([]byte("Content-Type")) + h.DelBytes([]byte((consts.HeaderHost))) + h.DelBytes([]byte("aaa")) + expectRequestHeaderAll(t, h, "Content-Type", [][]byte{}) + expectRequestHeaderAll(t, h, consts.HeaderHost, [][]byte{}) + expectRequestHeaderAll(t, h, "aaa", [][]byte{}) +} + +func expectRequestHeaderAll(t *testing.T, h *RequestHeader, key string, expectedValue [][]byte) { + if len(h.PeekAll(key)) != len(expectedValue) { + t.Fatalf("Unexpected size for key %q: %d. Expected %d", key, len(h.PeekAll(key)), len(expectedValue)) + } + assert.DeepEqual(t, h.PeekAll(key), expectedValue) +} + +func TestResponseHeader_PeekAll(t *testing.T) { + t.Parallel() + + h := &ResponseHeader{} + h.Add(consts.HeaderContentType, "aaa/bbb") + h.Add(consts.HeaderContentEncoding, "gzip") + h.Add(consts.HeaderConnection, "close") + h.Add(consts.HeaderContentLength, "1234") + h.Add(consts.HeaderServer, "aaaa") + h.Add(consts.HeaderSetCookie, "cccc") + h.Add("aaa", "aaa") + h.Add("aaa", "bbb") + + expectResponseHeaderAll(t, h, consts.HeaderContentType, [][]byte{[]byte("aaa/bbb")}) + expectResponseHeaderAll(t, h, consts.HeaderContentEncoding, [][]byte{[]byte("gzip")}) + expectResponseHeaderAll(t, h, consts.HeaderConnection, [][]byte{[]byte("close")}) + expectResponseHeaderAll(t, h, consts.HeaderContentLength, [][]byte{[]byte("1234")}) + expectResponseHeaderAll(t, h, consts.HeaderServer, [][]byte{[]byte("aaaa")}) + expectResponseHeaderAll(t, h, consts.HeaderSetCookie, [][]byte{[]byte("cccc")}) + expectResponseHeaderAll(t, h, "aaa", [][]byte{[]byte("aaa"), []byte("bbb")}) + + h.Del(consts.HeaderContentType) + h.Del(consts.HeaderContentEncoding) + expectResponseHeaderAll(t, h, consts.HeaderContentType, [][]byte{bytestr.DefaultContentType}) + expectResponseHeaderAll(t, h, consts.HeaderContentEncoding, [][]byte{}) +} + +func expectResponseHeaderAll(t *testing.T, h *ResponseHeader, key string, expectedValue [][]byte) { + if len(h.PeekAll(key)) != len(expectedValue) { + t.Fatalf("Unexpected size for key %q: %d. Expected %d", key, len(h.PeekAll(key)), len(expectedValue)) + } + assert.DeepEqual(t, h.PeekAll(key), expectedValue) +} From c26de3dad38e5ab3bca9e80db2aadc04ef190a06 Mon Sep 17 00:00:00 2001 From: GuangyuFan <97507466+FGYFFFF@users.noreply.github.com> Date: Thu, 2 Feb 2023 11:15:07 +0800 Subject: [PATCH 3/7] feat(hz): add mod flag for update command (#574) --- cmd/hz/app/app.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/hz/app/app.go b/cmd/hz/app/app.go index 8625de401..1f639a990 100644 --- a/cmd/hz/app/app.go +++ b/cmd/hz/app/app.go @@ -136,7 +136,7 @@ func Init() *cli.App { verboseFlag := cli.BoolFlag{Name: "verbose,vv", Usage: "turn on verbose mode", Destination: &globalArgs.Verbose} idlFlag := cli.StringSliceFlag{Name: "idl", Usage: "Specify the IDL file path. (.thrift or .proto)"} - moduleFlag := cli.StringFlag{Name: "module", Aliases: []string{"mod"}, Usage: "Specify the Go module name to generate go.mod.", Destination: &globalArgs.Gomod} + moduleFlag := cli.StringFlag{Name: "module", Aliases: []string{"mod"}, Usage: "Specify the Go module name.", Destination: &globalArgs.Gomod} serviceNameFlag := cli.StringFlag{Name: "service", Usage: "Specify the service name.", Destination: &globalArgs.ServiceName} outDirFlag := cli.StringFlag{Name: "out_dir", Usage: "Specify the project path.", Destination: &globalArgs.OutDir} handlerDirFlag := cli.StringFlag{Name: "handler_dir", Usage: "Specify the handler path.", Destination: &globalArgs.HandlerDir} @@ -212,6 +212,7 @@ func Init() *cli.App { Usage: "Update an existing Hertz project", Flags: []cli.Flag{ &idlFlag, + &moduleFlag, &outDirFlag, &handlerDirFlag, &modelDirFlag, From 545a0ea175d01f1c350f8ac08d33cee8829768ec Mon Sep 17 00:00:00 2001 From: Xuran <37136584+Duslia@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:22:15 +0800 Subject: [PATCH 4/7] fix: uri normalize in windows (#568) --- pkg/app/server/hertz_test.go | 4 +++- pkg/common/testdata/template/index.tmpl | 6 +----- pkg/protocol/uri_windows.go | 3 ++- pkg/protocol/uri_windows_test.go | 28 +++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 pkg/protocol/uri_windows_test.go diff --git a/pkg/app/server/hertz_test.go b/pkg/app/server/hertz_test.go index b651550b5..9df769a5b 100644 --- a/pkg/app/server/hertz_test.go +++ b/pkg/app/server/hertz_test.go @@ -157,7 +157,9 @@ func TestLoadHTMLGlob(t *testing.T) { assert.DeepEqual(t, consts.StatusOK, resp.StatusCode) b := make([]byte, 100) n, _ := resp.Body.Read(b) - assert.DeepEqual(t, "\n

\n Main website\n

\n", string(b[0:n])) + const expected = `

Main website

` + + assert.DeepEqual(t, expected, string(b[0:n])) } func TestLoadHTMLFiles(t *testing.T) { diff --git a/pkg/common/testdata/template/index.tmpl b/pkg/common/testdata/template/index.tmpl index 5e5a2612c..5100785e8 100644 --- a/pkg/common/testdata/template/index.tmpl +++ b/pkg/common/testdata/template/index.tmpl @@ -1,5 +1 @@ - -

- {[{ .title }]} -

- \ No newline at end of file +

{[{ .title }]}

\ No newline at end of file diff --git a/pkg/protocol/uri_windows.go b/pkg/protocol/uri_windows.go index 774644b61..abf13e72f 100644 --- a/pkg/protocol/uri_windows.go +++ b/pkg/protocol/uri_windows.go @@ -46,7 +46,8 @@ package protocol func addLeadingSlash(dst, src []byte) []byte { // zero length and "C:/" case - if len(src) == 0 || (len(src) > 2 && src[1] != ':') { + isDisk := len(src) > 2 && src[1] == ':' + if len(src) == 0 || (!isDisk && src[0] != '/') { dst = append(dst, '/') } diff --git a/pkg/protocol/uri_windows_test.go b/pkg/protocol/uri_windows_test.go new file mode 100644 index 000000000..507924b97 --- /dev/null +++ b/pkg/protocol/uri_windows_test.go @@ -0,0 +1,28 @@ +// Copyright 2023 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 protocol + +import "testing" + +func TestURIPathNormalizeIssue86(t *testing.T) { + t.Parallel() + + var u URI + + testURIPathNormalize(t, &u, `a`, `/a`) + testURIPathNormalize(t, &u, "/../../../../../foo", "/foo") + testURIPathNormalize(t, &u, "/..\\..\\..\\..\\..\\", "/") + testURIPathNormalize(t, &u, "/..%5c..%5cfoo", "/foo") +} From d83648d2ce111b2fb6341e1e964e935b9f68daca Mon Sep 17 00:00:00 2001 From: "Asterisk L. Yuan" <92938836+L2ncE@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:50:40 +0800 Subject: [PATCH 5/7] fix: fix bug when using stdjson tag for amd64 architecture (#581) --- pkg/common/json/sonic.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/common/json/sonic.go b/pkg/common/json/sonic.go index af0174e7e..dad1e6308 100644 --- a/pkg/common/json/sonic.go +++ b/pkg/common/json/sonic.go @@ -13,9 +13,10 @@ // limitations under the License. // -//go:build (linux || windows || darwin) && amd64 +//go:build (linux || windows || darwin) && amd64 && !stdjson // +build linux windows darwin // +build amd64 +// +build !stdjson package json From 69f158c7fcd6476d63b1db6c94a22889d1cf37af Mon Sep 17 00:00:00 2001 From: GuangyuFan <97507466+FGYFFFF@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:01:26 +0800 Subject: [PATCH 6/7] chore(hz): release hz v0.5.2 (#582) --- cmd/hz/meta/const.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/hz/meta/const.go b/cmd/hz/meta/const.go index 00c0aa8d5..2e4d82f71 100644 --- a/cmd/hz/meta/const.go +++ b/cmd/hz/meta/const.go @@ -19,7 +19,7 @@ package meta import "runtime" // Version hz version -const Version = "v0.5.1" +const Version = "v0.5.2" // Mode hz run modes type Mode int From 12f575625a899d839c7db653e766ccab1c3da8ff Mon Sep 17 00:00:00 2001 From: alice <90381261+alice-yyds@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:03:55 +0800 Subject: [PATCH 7/7] chore: update version v0.5.2 --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index b57f3fee8..98ad92ce3 100644 --- a/version.go +++ b/version.go @@ -19,5 +19,5 @@ package hertz // Name and Version info of this framework, used for statistics and debug const ( Name = "Hertz" - Version = "v0.5.1" + Version = "v0.5.2" )