github.com/cloudwego/hertz@v0.9.3/pkg/app/server/hertz_unix_test.go (about)

     1  // Copyright 2023 CloudWeGo Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
    17  // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
    18  
    19  package server
    20  
    21  import (
    22  	"context"
    23  	"net"
    24  	"net/http"
    25  	"os"
    26  	"os/exec"
    27  	"strconv"
    28  	"sync/atomic"
    29  	"syscall"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/cloudwego/hertz/pkg/app"
    34  	c "github.com/cloudwego/hertz/pkg/app/client"
    35  	"github.com/cloudwego/hertz/pkg/common/test/assert"
    36  	"github.com/cloudwego/hertz/pkg/common/utils"
    37  	"github.com/cloudwego/hertz/pkg/network"
    38  	"github.com/cloudwego/hertz/pkg/network/standard"
    39  	"github.com/cloudwego/hertz/pkg/protocol/consts"
    40  	"golang.org/x/sys/unix"
    41  )
    42  
    43  func TestReusePorts(t *testing.T) {
    44  	cfg := &net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error {
    45  		return c.Control(func(fd uintptr) {
    46  			syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1)
    47  			syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
    48  		})
    49  	}}
    50  	ha := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg), WithTransport(standard.NewTransporter))
    51  	hb := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg), WithTransport(standard.NewTransporter))
    52  	hc := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg))
    53  	hd := New(WithHostPorts("localhost:10093"), WithListenConfig(cfg))
    54  	ha.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
    55  		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
    56  	})
    57  	hc.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
    58  		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
    59  	})
    60  	hd.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
    61  		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
    62  	})
    63  	hb.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
    64  		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
    65  	})
    66  	go ha.Run()
    67  	go hb.Run()
    68  	go hc.Run()
    69  	go hd.Run()
    70  	time.Sleep(time.Second)
    71  
    72  	client, _ := c.NewClient()
    73  	for i := 0; i < 1000; i++ {
    74  		statusCode, body, err := client.Get(context.Background(), nil, "http://localhost:10093/ping")
    75  		assert.Nil(t, err)
    76  		assert.DeepEqual(t, consts.StatusOK, statusCode)
    77  		assert.DeepEqual(t, "{\"ping\":\"pong\"}", string(body))
    78  	}
    79  }
    80  
    81  func TestHertz_Spin(t *testing.T) {
    82  	engine := New(WithHostPorts("127.0.0.1:6668"))
    83  	engine.GET("/test", func(c context.Context, ctx *app.RequestContext) {
    84  		time.Sleep(time.Second * 2)
    85  		path := ctx.Request.URI().PathOriginal()
    86  		ctx.SetBodyString(string(path))
    87  	})
    88  	engine.GET("/test2", func(c context.Context, ctx *app.RequestContext) {})
    89  
    90  	testint := uint32(0)
    91  	engine.Engine.OnShutdown = append(engine.OnShutdown, func(ctx context.Context) {
    92  		atomic.StoreUint32(&testint, 1)
    93  	})
    94  
    95  	go engine.Spin()
    96  	time.Sleep(time.Millisecond)
    97  
    98  	hc := http.Client{Timeout: time.Second}
    99  	var err error
   100  	var resp *http.Response
   101  	ch := make(chan struct{})
   102  	ch2 := make(chan struct{})
   103  	go func() {
   104  		ticker := time.NewTicker(time.Millisecond * 100)
   105  		defer ticker.Stop()
   106  		for range ticker.C {
   107  			_, err := hc.Get("http://127.0.0.1:6668/test2")
   108  			t.Logf("[%v]begin listening\n", time.Now())
   109  			if err != nil {
   110  				t.Logf("[%v]listening closed: %v", time.Now(), err)
   111  				ch2 <- struct{}{}
   112  				break
   113  			}
   114  		}
   115  	}()
   116  	go func() {
   117  		t.Logf("[%v]begin request\n", time.Now())
   118  		resp, err = http.Get("http://127.0.0.1:6668/test")
   119  		t.Logf("[%v]end request\n", time.Now())
   120  		ch <- struct{}{}
   121  	}()
   122  
   123  	time.Sleep(time.Second * 1)
   124  	pid := strconv.Itoa(os.Getpid())
   125  	cmd := exec.Command("kill", "-SIGHUP", pid)
   126  	t.Logf("[%v]begin SIGHUP\n", time.Now())
   127  	if err := cmd.Run(); err != nil {
   128  		t.Fatal(err)
   129  	}
   130  	t.Logf("[%v]end SIGHUP\n", time.Now())
   131  	<-ch
   132  	assert.Nil(t, err)
   133  	assert.NotNil(t, resp)
   134  	assert.DeepEqual(t, uint32(1), atomic.LoadUint32(&testint))
   135  
   136  	<-ch2
   137  }
   138  
   139  func TestWithSenseClientDisconnection(t *testing.T) {
   140  	var closeFlag int32
   141  	h := New(WithHostPorts("127.0.0.1:6631"), WithSenseClientDisconnection(true))
   142  	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
   143  		assert.DeepEqual(t, "aa", string(ctx.Host()))
   144  		ch := make(chan struct{})
   145  		select {
   146  		case <-c.Done():
   147  			atomic.StoreInt32(&closeFlag, 1)
   148  			assert.DeepEqual(t, context.Canceled, c.Err())
   149  		case <-ch:
   150  		}
   151  	})
   152  	go h.Spin()
   153  	time.Sleep(time.Second)
   154  	con, err := net.Dial("tcp", "127.0.0.1:6631")
   155  	assert.Nil(t, err)
   156  	_, err = con.Write([]byte("GET /ping HTTP/1.1\r\nHost: aa\r\n\r\n"))
   157  	assert.Nil(t, err)
   158  	time.Sleep(time.Second)
   159  	assert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(0))
   160  	assert.Nil(t, con.Close())
   161  	time.Sleep(time.Second)
   162  	assert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(1))
   163  }
   164  
   165  func TestWithSenseClientDisconnectionAndWithOnConnect(t *testing.T) {
   166  	var closeFlag int32
   167  	h := New(WithHostPorts("127.0.0.1:6632"), WithSenseClientDisconnection(true), WithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {
   168  		return ctx
   169  	}))
   170  	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
   171  		assert.DeepEqual(t, "aa", string(ctx.Host()))
   172  		ch := make(chan struct{})
   173  		select {
   174  		case <-c.Done():
   175  			atomic.StoreInt32(&closeFlag, 1)
   176  			assert.DeepEqual(t, context.Canceled, c.Err())
   177  		case <-ch:
   178  		}
   179  	})
   180  	go h.Spin()
   181  	time.Sleep(time.Second)
   182  	con, err := net.Dial("tcp", "127.0.0.1:6632")
   183  	assert.Nil(t, err)
   184  	_, err = con.Write([]byte("GET /ping HTTP/1.1\r\nHost: aa\r\n\r\n"))
   185  	assert.Nil(t, err)
   186  	time.Sleep(time.Second)
   187  	assert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(0))
   188  	assert.Nil(t, con.Close())
   189  	time.Sleep(time.Second)
   190  	assert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(1))
   191  }