github.com/cloudwego/kitex@v0.9.0/pkg/generic/http_test/generic_test.go (about)

     1  /*
     2   * Copyright 2021 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package test
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/base64"
    23  	"encoding/json"
    24  	"fmt"
    25  	"math"
    26  	"net"
    27  	"net/http"
    28  	"reflect"
    29  	"strconv"
    30  	"strings"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/bytedance/sonic"
    35  	"github.com/cloudwego/dynamicgo/conv"
    36  	"github.com/tidwall/gjson"
    37  
    38  	"github.com/cloudwego/kitex/client/callopt"
    39  	"github.com/cloudwego/kitex/client/genericclient"
    40  	kt "github.com/cloudwego/kitex/internal/mocks/thrift"
    41  	"github.com/cloudwego/kitex/internal/test"
    42  	"github.com/cloudwego/kitex/pkg/generic"
    43  	"github.com/cloudwego/kitex/pkg/generic/descriptor"
    44  	"github.com/cloudwego/kitex/server"
    45  	"github.com/cloudwego/kitex/transport"
    46  )
    47  
    48  var customJson = sonic.Config{
    49  	EscapeHTML: true,
    50  	UseNumber:  true,
    51  }.Froze()
    52  
    53  func TestRun(t *testing.T) {
    54  	t.Run("TestThriftNormalBinaryEcho", testThriftNormalBinaryEcho)
    55  	t.Run("TestThriftException", testThriftException)
    56  	t.Run("TestRegression", testRegression)
    57  	t.Run("TestThriftBase64BinaryEcho", testThriftBase64BinaryEcho)
    58  	t.Run("TestUseRawBodyAndBodyCompatibility", testUseRawBodyAndBodyCompatibility)
    59  }
    60  
    61  func initThriftClientByIDL(t *testing.T, tp transport.Protocol, addr, idl string, opts []generic.Option, base64Binary, enableDynamicGo bool) genericclient.Client {
    62  	var p generic.DescriptorProvider
    63  	var err error
    64  	if enableDynamicGo {
    65  		p, err = generic.NewThriftFileProviderWithDynamicGo(idl)
    66  	} else {
    67  		p, err = generic.NewThriftFileProvider(idl)
    68  	}
    69  	test.Assert(t, err == nil)
    70  	g, err := generic.HTTPThriftGeneric(p, opts...)
    71  	test.Assert(t, err == nil)
    72  	err = generic.SetBinaryWithBase64(g, base64Binary)
    73  	test.Assert(t, err == nil)
    74  	cli := newGenericClient(tp, "destServiceName", g, addr)
    75  	test.Assert(t, err == nil)
    76  	return cli
    77  }
    78  
    79  func initThriftServer(t *testing.T, address string, handler generic.Service, idlPath string) server.Server {
    80  	addr, _ := net.ResolveTCPAddr("tcp", address)
    81  	p, err := generic.NewThriftFileProvider(idlPath)
    82  	test.Assert(t, err == nil)
    83  	g, err := generic.MapThriftGeneric(p)
    84  	test.Assert(t, err == nil)
    85  	svr := newGenericServer(g, addr, handler)
    86  	test.Assert(t, err == nil)
    87  	return svr
    88  }
    89  
    90  func initMockServer(t *testing.T, handler kt.Mock, address string) server.Server {
    91  	addr, _ := net.ResolveTCPAddr("tcp", address)
    92  	svr := newMockServer(handler, addr)
    93  	return svr
    94  }
    95  
    96  func testThriftNormalBinaryEcho(t *testing.T) {
    97  	addr := test.GetLocalAddress()
    98  	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift")
    99  
   100  	url := "http://example.com/BinaryEcho"
   101  
   102  	// []byte value for binary field
   103  	body := map[string]interface{}{
   104  		"msg":        []byte(mockMyMsg),
   105  		"got_base64": true,
   106  		"num":        "",
   107  	}
   108  	data, err := json.Marshal(body)
   109  	if err != nil {
   110  		panic(err)
   111  	}
   112  	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   113  	if err != nil {
   114  		panic(err)
   115  	}
   116  	customReq, err := generic.FromHTTPRequest(req)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  
   121  	// normal way
   122  	var opts []generic.Option
   123  	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, false)
   124  	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   125  	test.Assert(t, err == nil, err)
   126  	gr, ok := resp.(*generic.HTTPResponse)
   127  	test.Assert(t, ok)
   128  	test.Assert(t, gr.Body["msg"] == base64.StdEncoding.EncodeToString([]byte(mockMyMsg)))
   129  	test.Assert(t, gr.Body["num"] == "0")
   130  
   131  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   132  	// read: dynamicgo
   133  	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
   134  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, true)
   135  	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   136  	test.Assert(t, err == nil, err)
   137  	gr, ok = resp.(*generic.HTTPResponse)
   138  	test.Assert(t, ok)
   139  	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), base64.StdEncoding.EncodeToString([]byte(mockMyMsg))), gjson.Get(string(gr.RawBody), "msg").String())
   140  	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "num").String(), "0"), gjson.Get(string(gr.RawBody), "num").String())
   141  
   142  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   143  	// read: dynamicgo
   144  	cli = initThriftClientByIDL(t, transport.PurePayload, addr, "./idl/binary_echo.thrift", opts, false, true)
   145  	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   146  	test.Assert(t, err == nil, err)
   147  	gr, ok = resp.(*generic.HTTPResponse)
   148  	test.Assert(t, ok)
   149  	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), base64.StdEncoding.EncodeToString([]byte(mockMyMsg))), gjson.Get(string(gr.RawBody), "msg").String())
   150  	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "num").String(), "0"), gjson.Get(string(gr.RawBody), "num").String())
   151  
   152  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   153  	// read: fallback
   154  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", nil, false, true)
   155  	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   156  	test.Assert(t, err == nil, err)
   157  	gr, ok = resp.(*generic.HTTPResponse)
   158  	test.Assert(t, ok)
   159  	test.Assert(t, gr.Body["msg"] == base64.StdEncoding.EncodeToString([]byte(mockMyMsg)))
   160  	test.Assert(t, gr.Body["num"] == "0")
   161  
   162  	body = map[string]interface{}{
   163  		"msg":        string(mockMyMsg),
   164  		"got_base64": false,
   165  		"num":        0,
   166  	}
   167  	data, err = json.Marshal(body)
   168  	if err != nil {
   169  		panic(err)
   170  	}
   171  	req, err = http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   172  	if err != nil {
   173  		panic(err)
   174  	}
   175  	customReq, err = generic.FromHTTPRequest(req)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   181  	// read: dynamicgo
   182  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, true)
   183  	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   184  	test.Assert(t, err == nil, err)
   185  	gr, ok = resp.(*generic.HTTPResponse)
   186  	test.Assert(t, ok)
   187  	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), mockMyMsg), gjson.Get(string(gr.RawBody), "msg").String())
   188  	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "num").String(), "0"), gjson.Get(string(gr.RawBody), "num").String())
   189  
   190  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   191  	// read: fallback
   192  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", nil, false, true)
   193  	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   194  	test.Assert(t, err == nil, err)
   195  	gr, ok = resp.(*generic.HTTPResponse)
   196  	test.Assert(t, ok)
   197  	test.Assert(t, gr.Body["msg"] == mockMyMsg)
   198  	test.Assert(t, gr.Body["num"] == "0")
   199  
   200  	body = map[string]interface{}{
   201  		"msg":        []byte(mockMyMsg),
   202  		"got_base64": true,
   203  		"num":        "123",
   204  	}
   205  	data, err = json.Marshal(body)
   206  	if err != nil {
   207  		panic(err)
   208  	}
   209  	req, err = http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   210  	if err != nil {
   211  		panic(err)
   212  	}
   213  	customReq, err = generic.FromHTTPRequest(req)
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   219  	// read: dynamicgo
   220  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, true)
   221  	_, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   222  	test.Assert(t, err.Error() == "remote or network error[remote]: biz error: call failed, incorrect num", err.Error())
   223  
   224  	svr.Stop()
   225  }
   226  
   227  func BenchmarkCompareDynamicgoAndOriginal_Small(b *testing.B) {
   228  	// small data
   229  	sobj := getSimpleValue()
   230  	data, err := json.Marshal(sobj)
   231  	if err != nil {
   232  		panic(err)
   233  	}
   234  	fmt.Println("small data size: ", len(string(data)))
   235  	url := "http://example.com/simple"
   236  
   237  	t := testing.T{}
   238  	var opts []generic.Option
   239  	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
   240  
   241  	b.Run("thrift_small_dynamicgo", func(b *testing.B) {
   242  		addr := test.GetLocalAddress()
   243  		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
   244  		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", opts, false, true)
   245  
   246  		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
   247  		if err != nil {
   248  			panic(err)
   249  		}
   250  		customReq, err := generic.FromHTTPRequest(req)
   251  		if err != nil {
   252  			t.Fatal(err)
   253  		}
   254  		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   255  		test.Assert(&t, err == nil, err)
   256  		gr, ok := resp.(*generic.HTTPResponse)
   257  		test.Assert(&t, ok)
   258  		test.Assert(&t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "I64Field").String(), strconv.Itoa(math.MaxInt64)), gjson.Get(string(gr.RawBody), "I64Field").String())
   259  
   260  		b.ResetTimer()
   261  		for i := 0; i < b.N; i++ {
   262  			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   263  		}
   264  		svr.Stop()
   265  	})
   266  
   267  	b.Run("thrift_small_original", func(b *testing.B) {
   268  		addr := test.GetLocalAddress()
   269  		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
   270  		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", nil, false, false)
   271  
   272  		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
   273  		if err != nil {
   274  			panic(err)
   275  		}
   276  		customReq, err := generic.FromHTTPRequest(req)
   277  		if err != nil {
   278  			t.Fatal(err)
   279  		}
   280  		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   281  		test.Assert(&t, err == nil, err)
   282  		gr, ok := resp.(*generic.HTTPResponse)
   283  		test.Assert(&t, ok)
   284  		test.Assert(&t, reflect.DeepEqual(gr.Body["I64Field"].(string), strconv.Itoa(math.MaxInt64)), gr.Body["I64Field"].(string))
   285  
   286  		b.ResetTimer()
   287  		for i := 0; i < b.N; i++ {
   288  			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   289  		}
   290  		svr.Stop()
   291  	})
   292  }
   293  
   294  func BenchmarkCompareDynamicgoAndOriginal_Medium(b *testing.B) {
   295  	// medium data
   296  	nobj := getNestingValue()
   297  	data, err := json.Marshal(nobj)
   298  	if err != nil {
   299  		panic(err)
   300  	}
   301  	fmt.Println("medium data size: ", len(string(data)))
   302  	url := "http://example.com/nesting/100"
   303  
   304  	t := testing.T{}
   305  	var opts []generic.Option
   306  	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
   307  
   308  	b.Run("thrift_medium_dynamicgo", func(b *testing.B) {
   309  		addr := test.GetLocalAddress()
   310  		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
   311  		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", opts, false, true)
   312  
   313  		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
   314  		if err != nil {
   315  			panic(err)
   316  		}
   317  		customReq, err := generic.FromHTTPRequest(req)
   318  		if err != nil {
   319  			t.Fatal(err)
   320  		}
   321  		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   322  		test.Assert(&t, err == nil, err)
   323  		gr, ok := resp.(*generic.HTTPResponse)
   324  		test.Assert(&t, ok)
   325  		test.Assert(&t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "I32").String(), strconv.Itoa(math.MaxInt32)), gjson.Get(string(gr.RawBody), "I32").String())
   326  
   327  		b.ResetTimer()
   328  		for i := 0; i < b.N; i++ {
   329  			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   330  		}
   331  		svr.Stop()
   332  	})
   333  
   334  	b.Run("thrift_medium_original", func(b *testing.B) {
   335  		addr := test.GetLocalAddress()
   336  		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
   337  		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", nil, false, false)
   338  
   339  		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
   340  		if err != nil {
   341  			panic(err)
   342  		}
   343  		customReq, err := generic.FromHTTPRequest(req)
   344  		if err != nil {
   345  			t.Fatal(err)
   346  		}
   347  		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   348  		test.Assert(&t, err == nil, err)
   349  		gr, ok := resp.(*generic.HTTPResponse)
   350  		test.Assert(&t, ok)
   351  		test.Assert(&t, gr.Body["I32"].(int32) == math.MaxInt32, gr.Body["I32"].(int32))
   352  
   353  		b.ResetTimer()
   354  		for i := 0; i < b.N; i++ {
   355  			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   356  		}
   357  		svr.Stop()
   358  	})
   359  }
   360  
   361  func testThriftException(t *testing.T) {
   362  	addr := test.GetLocalAddress()
   363  	svr := initMockServer(t, new(mockImpl), addr)
   364  
   365  	body := map[string]interface{}{
   366  		"Msg":     "hello",
   367  		"strMap":  map[string]interface{}{"mk1": "mv1", "mk2": "mv2"},
   368  		"strList": []string{"lv1", "lv2"},
   369  	}
   370  	data, err := json.Marshal(body)
   371  	if err != nil {
   372  		panic(err)
   373  	}
   374  	url := "http://example.com/ExceptionTest"
   375  	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   376  	if err != nil {
   377  		panic(err)
   378  	}
   379  	customReq, err := generic.FromHTTPRequest(req)
   380  	if err != nil {
   381  		t.Fatal(err)
   382  	}
   383  
   384  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   385  	// read: dynamicgo
   386  	var opts []generic.Option
   387  	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
   388  	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/mock.thrift", opts, false, true)
   389  	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   390  	test.Assert(t, err == nil, err)
   391  	fmt.Println(string(resp.(*descriptor.HTTPResponse).RawBody))
   392  	test.DeepEqual(t, gjson.Get(string(resp.(*descriptor.HTTPResponse).RawBody), "code").Int(), int64(400))
   393  	test.DeepEqual(t, gjson.Get(string(resp.(*descriptor.HTTPResponse).RawBody), "msg").String(), "this is an exception")
   394  
   395  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   396  	// read: fallback
   397  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/mock.thrift", nil, false, true)
   398  	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   399  	test.Assert(t, err == nil, err)
   400  	fmt.Println(string(resp.(*descriptor.HTTPResponse).RawBody))
   401  	test.DeepEqual(t, resp.(*descriptor.HTTPResponse).Body["code"].(int32), int32(400))
   402  	test.DeepEqual(t, resp.(*descriptor.HTTPResponse).Body["msg"], "this is an exception")
   403  
   404  	svr.Stop()
   405  }
   406  
   407  func testRegression(t *testing.T) {
   408  	addr := test.GetLocalAddress()
   409  	svr := initThriftServer(t, addr, new(GenericServiceAnnotationImpl), "./idl/http_annotation.thrift")
   410  
   411  	body := map[string]interface{}{
   412  		"text": "text",
   413  		"req_items_map": map[string]interface{}{
   414  			"1": map[string]interface{}{
   415  				"MyID": "1",
   416  				"text": "text",
   417  			},
   418  		},
   419  		"some": map[string]interface{}{
   420  			"MyID": "1",
   421  			"text": "text",
   422  		},
   423  	}
   424  	data, err := json.Marshal(body)
   425  	if err != nil {
   426  		panic(err)
   427  	}
   428  	url := "http://example.com/life/client/1/1?v_int64=1&req_items=item1,item2,item3&cids=1,2,3&vids=1,2,3"
   429  	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   430  	if err != nil {
   431  		panic(err)
   432  	}
   433  	req.Header.Set("token", "1")
   434  	cookie := &http.Cookie{
   435  		Name:  "cookie",
   436  		Value: "cookie_val",
   437  	}
   438  	req.AddCookie(cookie)
   439  	customReq, err := generic.FromHTTPRequest(req)
   440  	if err != nil {
   441  		t.Fatal(err)
   442  	}
   443  
   444  	// client without dynamicgo
   445  	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/http_annotation.thrift", nil, false, false)
   446  	respI, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   447  	test.Assert(t, err == nil, err)
   448  	resp, ok := respI.(*generic.HTTPResponse)
   449  	test.Assert(t, ok)
   450  
   451  	// client with dynamicgo
   452  	var opts []generic.Option
   453  	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
   454  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/http_annotation.thrift", opts, false, true)
   455  	respI, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   456  	test.Assert(t, err == nil, err)
   457  	dresp, ok := respI.(*generic.HTTPResponse)
   458  	test.Assert(t, ok)
   459  
   460  	// check body
   461  	var dMapBody map[string]interface{}
   462  	err = customJson.Unmarshal(dresp.RawBody, &dMapBody)
   463  	test.Assert(t, err == nil)
   464  	bytes, err := customJson.Marshal(resp.Body)
   465  	test.Assert(t, err == nil)
   466  	err = customJson.Unmarshal(bytes, &resp.Body)
   467  	test.Assert(t, err == nil, err)
   468  	test.Assert(t, isEqual(dMapBody, resp.Body))
   469  
   470  	test.DeepEqual(t, resp.StatusCode, dresp.StatusCode)
   471  	test.DeepEqual(t, resp.ContentType, dresp.ContentType)
   472  
   473  	checkHeader(t, resp)
   474  	checkHeader(t, dresp)
   475  
   476  	svr.Stop()
   477  }
   478  
   479  func testThriftBase64BinaryEcho(t *testing.T) {
   480  	addr := test.GetLocalAddress()
   481  	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift")
   482  
   483  	var opts []generic.Option
   484  	convOpts := conv.Options{EnableValueMapping: true, NoBase64Binary: false}
   485  	opts = append(opts, generic.WithCustomDynamicGoConvOpts(&convOpts), generic.UseRawBodyForHTTPResp(true))
   486  
   487  	url := "http://example.com/BinaryEcho"
   488  
   489  	// []byte value for binary field
   490  	body := map[string]interface{}{
   491  		"msg":        []byte(mockMyMsg),
   492  		"got_base64": false,
   493  		"num":        "0",
   494  	}
   495  	data, err := json.Marshal(body)
   496  	if err != nil {
   497  		panic(err)
   498  	}
   499  	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   500  	if err != nil {
   501  		panic(err)
   502  	}
   503  	customReq, err := generic.FromHTTPRequest(req)
   504  	if err != nil {
   505  		t.Fatal(err)
   506  	}
   507  
   508  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   509  	// read: dynamicgo
   510  	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, true, true)
   511  	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   512  	test.Assert(t, err == nil, err)
   513  	gr, ok := resp.(*generic.HTTPResponse)
   514  	test.Assert(t, ok)
   515  	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), base64.StdEncoding.EncodeToString([]byte(mockMyMsg))), gjson.Get(string(gr.RawBody), "msg").String())
   516  
   517  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   518  	// read: fallback
   519  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", nil, true, true)
   520  	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   521  	test.Assert(t, err == nil, err)
   522  	gr, ok = resp.(*generic.HTTPResponse)
   523  	test.Assert(t, ok)
   524  	test.Assert(t, gr.Body["msg"] == base64.StdEncoding.EncodeToString(body["msg"].([]byte)))
   525  
   526  	// string value for binary field which should fail
   527  	body = map[string]interface{}{
   528  		"msg": string(mockMyMsg),
   529  	}
   530  	data, err = json.Marshal(body)
   531  	if err != nil {
   532  		panic(err)
   533  	}
   534  	req, err = http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   535  	if err != nil {
   536  		panic(err)
   537  	}
   538  	customReq, err = generic.FromHTTPRequest(req)
   539  	if err != nil {
   540  		t.Fatal(err)
   541  	}
   542  
   543  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   544  	// read: dynamicgo
   545  	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, true, true)
   546  	_, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   547  	test.Assert(t, strings.Contains(err.Error(), "illegal base64 data"))
   548  
   549  	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
   550  	// read: fallback
   551  	cli = initThriftClientByIDL(t, transport.PurePayload, addr, "./idl/binary_echo.thrift", nil, true, true)
   552  	_, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   553  	test.Assert(t, strings.Contains(err.Error(), "illegal base64 data"))
   554  
   555  	svr.Stop()
   556  }
   557  
   558  func testUseRawBodyAndBodyCompatibility(t *testing.T) {
   559  	addr := test.GetLocalAddress()
   560  	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift")
   561  
   562  	url := "http://example.com/BinaryEcho"
   563  
   564  	// []byte value for binary field
   565  	body := map[string]interface{}{
   566  		"msg":        []byte(mockMyMsg),
   567  		"got_base64": true,
   568  		"num":        "",
   569  	}
   570  	data, err := json.Marshal(body)
   571  	if err != nil {
   572  		panic(err)
   573  	}
   574  	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
   575  	if err != nil {
   576  		panic(err)
   577  	}
   578  	customReq, err := generic.FromHTTPRequest(req)
   579  	if err != nil {
   580  		t.Fatal(err)
   581  	}
   582  
   583  	var opts []generic.Option
   584  	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
   585  	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, false)
   586  	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
   587  	test.Assert(t, err == nil)
   588  	gr, ok := resp.(*generic.HTTPResponse)
   589  	test.Assert(t, ok)
   590  
   591  	var mapBody map[string]interface{}
   592  	err = customJson.Unmarshal(gr.RawBody, &mapBody)
   593  	test.Assert(t, err == nil)
   594  	test.DeepEqual(t, gr.Body, mapBody)
   595  
   596  	svr.Stop()
   597  }
   598  
   599  func isEqual(a, b interface{}) bool {
   600  	if reflect.TypeOf(a) != reflect.TypeOf(b) {
   601  		return false
   602  	}
   603  	switch a := a.(type) {
   604  	case []interface{}:
   605  		b, ok := b.([]interface{})
   606  		if !ok {
   607  			return false
   608  		}
   609  		if len(a) != len(b) {
   610  			return false
   611  		}
   612  		for i := range a {
   613  			if !isEqual(a[i], b[i]) {
   614  				return false
   615  			}
   616  		}
   617  		return true
   618  	case map[string]interface{}:
   619  		b, ok := b.(map[string]interface{})
   620  		if !ok {
   621  			return false
   622  		}
   623  		if len(a) != len(b) {
   624  			return false
   625  		}
   626  		for k, v1 := range a {
   627  			v2, ok := b[k]
   628  			if !ok || !isEqual(v1, v2) {
   629  				return false
   630  			}
   631  		}
   632  		return true
   633  	default:
   634  		return reflect.DeepEqual(a, b)
   635  	}
   636  }
   637  
   638  func checkHeader(t *testing.T, resp *generic.HTTPResponse) {
   639  	test.Assert(t, resp.Header.Get("b") == "true")
   640  	test.Assert(t, resp.Header.Get("eight") == "8")
   641  	test.Assert(t, resp.Header.Get("sixteen") == "16")
   642  	test.Assert(t, resp.Header.Get("thirtytwo") == "32")
   643  	test.Assert(t, resp.Header.Get("sixtyfour") == "64")
   644  	test.Assert(t, resp.Header.Get("d") == "123.45")
   645  	test.Assert(t, resp.Header.Get("T") == "1")
   646  	test.Assert(t, resp.Header.Get("item_count") == "1,2,3")
   647  	test.Assert(t, resp.Header.Get("header_map") == "map[map1:1 map2:2]")
   648  	test.Assert(t, resp.Header.Get("header_struct") == "map[item_id:1 text:1]")
   649  	test.Assert(t, resp.Header.Get("string_set") == "a,b,c")
   650  }