cubefs/blobstore/common/rpc/client_test.go
slasher d076024883 fix(rpc): timeout with response in rpc client
@formatter:off

Signed-off-by: slasher <shenjie1@oppo.com>
2025-09-05 12:15:07 +08:00

396 lines
11 KiB
Go

// Copyright 2022 The CubeFS 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 rpc
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/cubefs/cubefs/blobstore/common/crc32block"
auth_proto "github.com/cubefs/cubefs/blobstore/common/rpc/auth/proto"
)
var (
testServer *httptest.Server
first = true
)
type handler struct{}
func (s *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
uri := r.URL.RequestURI()
switch uri {
case "/retry":
b, err := io.ReadAll(r.Body)
if err != nil || int64(len(b)) != r.ContentLength || first {
ReplyErr(w, 500, "test retry")
first = false
}
case "/crc":
doAfterCrc(w, r)
case "/crcbody":
w.Header().Set(HeaderAckCrcEncoded, "1")
w.WriteHeader(500)
case "/json":
callWithJSON(w, r)
case "/timeout":
timeout(w, r)
case "/notfound":
ReplyWith(w, 404, "", []byte("404 page not found"))
default:
{
marshal, err := json.Marshal(ret{Name: "Test_GetWith"})
if err == nil {
w.Write(marshal)
return
}
}
}
}
type testTimeoutReader struct{ closeTimes int }
func (t *testTimeoutReader) Read(p []byte) (n int, err error) {
time.Sleep(time.Millisecond * 20)
return 0, nil
}
func (t *testTimeoutReader) Close() error {
t.closeTimes++
return nil
}
func doAfterCrc(w http.ResponseWriter, req *http.Request) {
dec := crc32block.NewBodyDecoder(req.Body)
defer dec.Close()
dataByte := make([]byte, 1024)
read, err := dec.Read(dataByte)
w.Header().Set(HeaderAckCrcEncoded, "1")
if err != nil {
return
}
w.Write(dataByte[:read])
}
func callWithJSON(w http.ResponseWriter, req *http.Request) {
r := &ret{}
err := json.NewDecoder(req.Body).Decode(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
r.Name = r.Name + "+Test"
marshal, err := json.Marshal(r)
if err != nil {
w.WriteHeader(http.StatusGatewayTimeout)
return
}
w.Write(marshal)
}
func timeout(w http.ResponseWriter, req *http.Request) {
r := &ret{}
err := json.NewDecoder(req.Body).Decode(r)
if err != nil {
return
}
for i := 0; i < 10*1024*1024*1024; i++ {
w.Write([]byte("time out"))
}
}
var simpleCfg = &Config{
ClientTimeoutMs: 10000,
Tc: TransportConfig{
DialTimeoutMs: 1000,
ResponseHeaderTimeoutMs: 3000,
MaxConnsPerHost: 100,
MaxIdleConns: 1000,
MaxIdleConnsPerHost: 10,
IdleConnTimeoutMs: 60000,
DisableCompression: true,
Auth: auth_proto.Config{
EnableAuth: true,
Secret: "test",
},
},
}
var simpleClient = NewClient(simpleCfg)
func init() {
testServer = httptest.NewServer(&handler{})
}
func TestClient_NewClient(t *testing.T) {
NewClient(nil)
{
cfg := Config{}
NewClient(&cfg)
require.Equal(t, 10, cfg.Tc.MaxConnsPerHost)
require.Equal(t, int64(100), cfg.Tc.DialTimeoutMs)
require.False(t, cfg.Tc.Auth.EnableAuth)
}
{
cfg := Config{}
cfg.Tc.Auth.EnableAuth = true
cfg.Tc.Auth.Secret = "true"
NewClient(&cfg)
require.Equal(t, 10, cfg.Tc.MaxConnsPerHost)
require.Equal(t, int64(100), cfg.Tc.DialTimeoutMs)
require.True(t, cfg.Tc.Auth.EnableAuth)
require.Equal(t, "true", cfg.Tc.Auth.Secret)
}
{
cfg := Config{}
cfg.Tc.MaxConnsPerHost = 4
NewClient(&cfg)
require.Equal(t, 4, cfg.Tc.MaxConnsPerHost)
require.Equal(t, 0, cfg.Tc.MaxIdleConnsPerHost)
require.Equal(t, int64(200), cfg.Tc.DialTimeoutMs)
require.False(t, cfg.Tc.Auth.EnableAuth)
}
}
func TestClient_GetWith(t *testing.T) {
ctx := context.Background()
result := &ret{}
err := simpleClient.GetWith(ctx, testServer.URL, result)
require.NoError(t, err)
require.NotNil(t, result)
}
func TestClient_PostWithCrc(t *testing.T) {
ctx := context.Background()
result := &ret{}
err := simpleClient.PostWith(ctx, testServer.URL+"/crc", result,
&ret{Name: "TestClient_PostWithCrc"}, WithCrcEncode())
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "TestClient_PostWithCrc", result.Name)
}
func TestClient_PostWithNoCrc(t *testing.T) {
ctx := context.Background()
result := &ret{}
err := simpleClient.PostWith(ctx, testServer.URL+"/json", result, &ret{Name: "TestClient_PostWithNoCrc"})
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "TestClient_PostWithNoCrc+Test", result.Name)
}
func TestClient_PostWithNoneBody(t *testing.T) {
ctx := context.Background()
err := simpleClient.PostWith(ctx, testServer.URL+"/json", nil, NoneBody)
require.Equal(t, DetectStatusCode(err), http.StatusBadRequest)
}
func TestClient_Delete(t *testing.T) {
ctx := context.Background()
resp, err := simpleClient.Delete(ctx, testServer.URL)
require.NoError(t, err)
resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestClient_PutWithNoCrc(t *testing.T) {
ctx := context.Background()
result := &ret{}
err := simpleClient.PutWith(ctx, testServer.URL+"/json", result, &ret{Name: "TestClient_PutWithNoCrc"})
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "TestClient_PutWithNoCrc+Test", result.Name)
}
func TestClient_PutWithCrc(t *testing.T) {
ctx := context.Background()
result := &ret{}
err := simpleClient.PostWith(ctx, testServer.URL+"/crc", result,
&ret{Name: "TestClient_PutWithCrc"}, WithCrcEncode())
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "TestClient_PutWithCrc", result.Name)
}
func TestClient_Post(t *testing.T) {
ctx := context.Background()
result := &ret{}
resp, err := simpleClient.Post(ctx, testServer.URL+"/json", &ret{Name: "TestClient_Post"})
require.NoError(t, err)
err = parseData(resp, result)
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "TestClient_Post+Test", result.Name)
}
func TestClient_PostWithReadResponseTimeout(t *testing.T) {
simpleCfg.BodyBaseTimeoutMs = 50
simpleCfg.BodyBandwidthMBPs = 1
simpleCli := NewClient(simpleCfg)
ctx := context.Background()
resp, err := simpleCli.Post(ctx, testServer.URL+"/timeout", &ret{Name: "TestClient_Post"})
require.NoError(t, err)
defer resp.Body.Close()
cache := make([]byte, 128)
_, err = resp.Body.Read(cache)
for err == nil {
_, err = resp.Body.Read(cache)
}
require.Equal(t, ErrBodyReadTimeout, err)
}
func TestTimeoutReadCloser_Read(t *testing.T) {
// test for read data for input buffer timeout
body := &testTimeoutReader{}
timer := time.NewTimer(time.Millisecond)
responseBody := timeoutReadCloser{body: body, timer: timer}
res := make([]byte, 30)
_, err := responseBody.Read(res)
require.Equal(t, ErrBodyReadTimeout, err)
_, err = responseBody.Read(res)
require.Equal(t, ErrBodyReadTimeout, err)
require.NoError(t, responseBody.Close())
require.NoError(t, responseBody.Close())
require.NoError(t, responseBody.Close())
require.Equal(t, 1, body.closeTimes)
require.False(t, responseBody.timer.Stop())
}
func TestClient_Put(t *testing.T) {
ctx := context.Background()
result := &ret{}
resp, err := simpleClient.Put(ctx, testServer.URL+"/json", &ret{Name: "TestClient_Put"})
require.NoError(t, err)
err = parseData(resp, result)
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "TestClient_Put+Test", result.Name)
}
func TestClient_Close(t *testing.T) {
simpleClient.Close()
}
func TestClient_Head(t *testing.T) {
ctx := context.Background()
resp, err := simpleClient.Head(ctx, testServer.URL)
require.NoError(t, err)
resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestClient_DoWithNoCrc(t *testing.T) {
ctx := context.Background()
result := &ret{}
request, err := http.NewRequest(http.MethodPost, testServer.URL, nil)
require.NoError(t, err)
err = simpleClient.DoWith(ctx, request, result)
require.NoError(t, err)
require.NotNil(t, result)
}
func TestClient_DoWithCrc(t *testing.T) {
ctx := context.Background()
result := &ret{}
request, err := http.NewRequest(http.MethodPost, testServer.URL+"/crc", nil)
require.NoError(t, err)
err = simpleClient.DoWith(ctx, request, result, WithCrcEncode())
require.NoError(t, err)
require.NotNil(t, result)
}
func TestClient_Form(t *testing.T) {
m := make(map[string][]string)
m["test"] = []string{"test_lb_Form"}
ctx := context.Background()
resp, err := simpleClient.Form(ctx, http.MethodPost, testServer.URL, m)
require.NoError(t, err)
resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestClient_ParseDataWithContentLengthZero(t *testing.T) {
m := make(map[string][]string)
m["test"] = []string{"yest_lb_Form"}
ctx := context.Background()
result := &ret{}
resp, err := simpleClient.Form(ctx, http.MethodPost, testServer.URL, m)
require.NoError(t, err)
resp.StatusCode = 400
resp.ContentLength = 0
err = parseData(resp, result)
require.Error(t, err)
}
func TestClient_ParseData(t *testing.T) {
m := make(map[string][]string)
m["test"] = []string{"yest_lb_Form"}
ctx := context.Background()
result := &ret{}
resp, err := simpleClient.Form(ctx, http.MethodPost, testServer.URL, m)
require.NoError(t, err)
resp.StatusCode = 400
resp.ContentLength = 12
err = parseData(resp, result)
require.Error(t, err)
}
func TestClient_DoContextCancel(t *testing.T) {
ctx := context.Background()
result := &ret{}
cancel, cancelFunc := context.WithCancel(ctx)
request, err := http.NewRequest(http.MethodPost, testServer.URL, nil)
require.NoError(t, err)
cancelFunc()
err = simpleClient.DoWith(cancel, request, result)
require.Error(t, err)
}
func TestClient_ResponseClose(t *testing.T) {
ctx := context.Background()
result := &ret{}
resp, err := simpleClient.Post(ctx, testServer.URL+"/json",
&ret{Name: "TestClient_ResponseClose"})
require.NoError(t, err)
resp.Body.Close()
err = parseData(resp, result)
require.Error(t, err)
}
func TestClient_PostWithOnServerNotAck(t *testing.T) {
request, err := http.NewRequest(http.MethodPost, testServer.URL, nil)
require.NoError(t, err)
request.Header.Set(HeaderCrcEncoded, "1")
response, err := simpleClient.Do(context.Background(), request)
require.NoError(t, err)
err = serverCrcEncodeCheck(context.Background(), request, response)
response.Body.Close()
require.NotNil(t, err)
}
func TestClient_PageNotFound(t *testing.T) {
ctx := context.Background()
result := &ret{}
err := simpleClient.GetWith(ctx, testServer.URL+"/notfound", result)
require.Error(t, err)
require.Equal(t, "", result.Name)
}