github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/framework/http/middleware_test.go (about) 1 package ddhttp_test 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "github.com/apolloconfig/agollo/v4" 8 "github.com/apolloconfig/agollo/v4/agcache/memory" 9 apolloConfig "github.com/apolloconfig/agollo/v4/env/config" 10 "github.com/go-resty/resty/v2" 11 "github.com/golang/mock/gomock" 12 "github.com/opentracing-contrib/go-stdlib/nethttp" 13 "github.com/opentracing/opentracing-go" 14 "github.com/pkg/errors" 15 "github.com/slok/goresilience" 16 . "github.com/smartystreets/goconvey/convey" 17 "github.com/unionj-cloud/go-doudou/framework/configmgr" 18 "github.com/unionj-cloud/go-doudou/framework/configmgr/mock" 19 ddhttp "github.com/unionj-cloud/go-doudou/framework/http" 20 httpMock "github.com/unionj-cloud/go-doudou/framework/http/mock" 21 ddmodel "github.com/unionj-cloud/go-doudou/framework/http/model" 22 "github.com/unionj-cloud/go-doudou/framework/internal/config" 23 "github.com/unionj-cloud/go-doudou/framework/registry" 24 "github.com/unionj-cloud/go-doudou/toolkit/maputils" 25 "github.com/wubin1989/nacos-sdk-go/clients/cache" 26 "github.com/wubin1989/nacos-sdk-go/clients/config_client" 27 "github.com/wubin1989/nacos-sdk-go/vo" 28 "net/http" 29 "os" 30 "testing" 31 "time" 32 ) 33 34 type IMocksvcHandler interface { 35 GetUser(w http.ResponseWriter, r *http.Request) 36 SaveUser(w http.ResponseWriter, r *http.Request) 37 SignUp(w http.ResponseWriter, r *http.Request) 38 GetPanic(w http.ResponseWriter, r *http.Request) 39 } 40 41 type MocksvcHandler struct{} 42 43 func (m *MocksvcHandler) GetUser(w http.ResponseWriter, r *http.Request) { 44 w.Header().Set("Content-Type", "text/plain") 45 w.Write([]byte("go-doudou")) 46 } 47 48 func (m *MocksvcHandler) SaveUser(w http.ResponseWriter, r *http.Request) { 49 data := struct { 50 Data string `json:"data"` 51 }{ 52 Data: "OK", 53 } 54 resp, _ := json.Marshal(data) 55 w.Write(resp) 56 } 57 58 func (m *MocksvcHandler) SignUp(w http.ResponseWriter, r *http.Request) { 59 data := struct { 60 Data string `json:"data"` 61 }{ 62 Data: "OK", 63 } 64 resp, _ := json.Marshal(data) 65 w.Write(resp) 66 } 67 68 func (m *MocksvcHandler) GetPanic(w http.ResponseWriter, r *http.Request) { 69 panic(context.Canceled) 70 } 71 72 func Routes(handler IMocksvcHandler) []ddmodel.Route { 73 return []ddmodel.Route{ 74 { 75 Name: "GetUser", 76 Method: "GET", 77 Pattern: "/user", 78 HandlerFunc: handler.GetUser, 79 }, 80 { 81 Name: "SaveUser", 82 Method: "POST", 83 Pattern: "/save/user", 84 HandlerFunc: handler.SaveUser, 85 }, 86 { 87 Name: "SignUp", 88 Method: "POST", 89 Pattern: "/sign/up", 90 HandlerFunc: handler.SignUp, 91 }, 92 { 93 Name: "GetPanic", 94 Method: "GET", 95 Pattern: "/panic", 96 HandlerFunc: handler.GetPanic, 97 }, 98 } 99 } 100 101 func NewMocksvcHandler() IMocksvcHandler { 102 return &MocksvcHandler{} 103 } 104 105 type UserVo struct { 106 Username string 107 Password string 108 } 109 110 type IMockClient interface { 111 GetUser(ctx context.Context, _headers map[string]string) (_resp *resty.Response, data string, err error) 112 SaveUser(ctx context.Context, _headers map[string]string, payload UserVo) (_resp *resty.Response, data string, err error) 113 SignUp(ctx context.Context, _headers map[string]string, username, password string) (_resp *resty.Response, data string, err error) 114 GetPanic(ctx context.Context, _headers map[string]string) (_resp *resty.Response, data string, err error) 115 } 116 117 type MockClient struct { 118 provider registry.IServiceProvider 119 client *resty.Client 120 rootPath string 121 } 122 123 func (receiver *MockClient) SetRootPath(rootPath string) { 124 receiver.rootPath = rootPath 125 } 126 127 func (receiver *MockClient) SetProvider(provider registry.IServiceProvider) { 128 receiver.provider = provider 129 } 130 131 func (receiver *MockClient) SetClient(client *resty.Client) { 132 receiver.client = client 133 } 134 135 func (receiver *MockClient) GetUser(ctx context.Context, _headers map[string]string) (_resp *resty.Response, data string, err error) { 136 var _err error 137 _req := receiver.client.R() 138 _req.SetContext(ctx) 139 _path := "/user" 140 _resp, _err = _req.Get(_path) 141 if _err != nil { 142 err = errors.Wrap(_err, "error") 143 return 144 } 145 if _resp.IsError() { 146 err = errors.New(_resp.String()) 147 return 148 } 149 return _resp, string(_resp.Body()), nil 150 } 151 152 func (receiver *MockClient) GetPanic(ctx context.Context, _headers map[string]string) (_resp *resty.Response, data string, err error) { 153 var _err error 154 _req := receiver.client.R() 155 _req.SetContext(ctx) 156 _path := "/panic" 157 _resp, _err = _req.Get(_path) 158 if _err != nil { 159 err = errors.Wrap(_err, "error") 160 return 161 } 162 if _resp.IsError() { 163 err = errors.New(_resp.String()) 164 return 165 } 166 return _resp, string(_resp.Body()), nil 167 } 168 169 func (receiver *MockClient) SignUp(ctx context.Context, _headers map[string]string, username, password string) (_resp *resty.Response, data string, err error) { 170 var _err error 171 _req := receiver.client.R() 172 _req.SetContext(ctx) 173 formData := make(map[string]string) 174 formData["username"] = fmt.Sprintf("%v", username) 175 formData["password"] = fmt.Sprintf("%v", password) 176 _path := "/sign/up" 177 _req.SetMultipartFormData(formData) 178 _resp, _err = _req.Post(_path) 179 if _err != nil { 180 err = errors.Wrap(_err, "error") 181 return 182 } 183 if _resp.IsError() { 184 err = errors.New(_resp.String()) 185 return 186 } 187 var _result struct { 188 Data string `json:"data"` 189 } 190 if _err = json.Unmarshal(_resp.Body(), &_result); _err != nil { 191 err = errors.Wrap(_err, "error") 192 return 193 } 194 return _resp, _result.Data, nil 195 } 196 197 func (receiver *MockClient) SaveUser(ctx context.Context, _headers map[string]string, payload UserVo) (_resp *resty.Response, data string, err error) { 198 var _err error 199 _req := receiver.client.R() 200 _req.SetContext(ctx) 201 _req.SetBody(payload) 202 _path := "/save/user" 203 _resp, _err = _req.Post(_path) 204 if _err != nil { 205 err = errors.Wrap(_err, "error") 206 return 207 } 208 if _resp.IsError() { 209 err = errors.New(_resp.String()) 210 return 211 } 212 var _result struct { 213 Data string `json:"data"` 214 } 215 if _err = json.Unmarshal(_resp.Body(), &_result); _err != nil { 216 err = errors.Wrap(_err, "error") 217 return 218 } 219 return _resp, _result.Data, nil 220 } 221 222 func NewMockClient(opts ...ddhttp.DdClientOption) *MockClient { 223 defaultProvider := ddhttp.NewServiceProvider("DDHTTP") 224 defaultClient := ddhttp.NewClient() 225 226 svcClient := &MockClient{ 227 provider: defaultProvider, 228 client: defaultClient, 229 } 230 231 for _, opt := range opts { 232 opt(svcClient) 233 } 234 235 svcClient.client.OnBeforeRequest(func(_ *resty.Client, request *resty.Request) error { 236 request.URL = svcClient.provider.SelectServer() + svcClient.rootPath + request.URL 237 return nil 238 }) 239 240 svcClient.client.SetPreRequestHook(func(_ *resty.Client, request *http.Request) error { 241 traceReq, _ := nethttp.TraceRequest(opentracing.GlobalTracer(), request, 242 nethttp.OperationName(fmt.Sprintf("HTTP %s: %s", request.Method, request.URL.Path))) 243 *request = *traceReq 244 return nil 245 }) 246 247 svcClient.client.OnAfterResponse(func(_ *resty.Client, response *resty.Response) error { 248 nethttp.TracerFromRequest(response.Request.RawRequest).Finish() 249 return nil 250 }) 251 252 return svcClient 253 } 254 255 func Test_metrics(t *testing.T) { 256 Convey("Should be equal to go-doudou", t, func() { 257 go func() { 258 srv := ddhttp.NewDefaultHttpSrv() 259 srv.AddRoute(Routes(NewMocksvcHandler())...) 260 srv.Run() 261 }() 262 time.Sleep(10 * time.Millisecond) 263 os.Setenv("DDHTTP", "http://localhost:8088/v1") 264 client := NewMockClient() 265 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 266 defer cancel() 267 _, ret, err := client.GetUser(ctx, nil) 268 So(err, ShouldBeNil) 269 So(ret, ShouldEqual, "go-doudou") 270 }) 271 } 272 273 func Test_NacosConfigType(t *testing.T) { 274 Convey("Should be equal to go-doudou with nacos config", t, func() { 275 ctrl := gomock.NewController(t) 276 defer ctrl.Finish() 277 dataId := ".env" 278 configClient := mock.NewMockIConfigClient(ctrl) 279 configClient. 280 EXPECT(). 281 GetConfig(vo.ConfigParam{ 282 DataId: dataId, 283 Group: config.DefaultGddNacosConfigGroup, 284 }). 285 AnyTimes(). 286 Return("GDD_SERVICE_NAME=configmgr\n\nGDD_READ_TIMEOUT=60s\nGDD_WRITE_TIMEOUT=60s\nGDD_IDLE_TIMEOUT=120s", nil) 287 288 configClient. 289 EXPECT(). 290 ListenConfig(gomock.Any()). 291 AnyTimes(). 292 Return(nil) 293 294 configmgr.NewConfigClient = func(param vo.NacosClientParam) (iClient config_client.IConfigClient, err error) { 295 return configClient, nil 296 } 297 298 configmgr.NacosClient = configmgr.NewNacosConfigMgr([]string{dataId}, 299 config.DefaultGddNacosConfigGroup, configmgr.DotenvConfigFormat, config.DefaultGddNacosNamespaceId, configClient, cache.NewConcurrentMap()) 300 301 _ = config.GddConfigRemoteType.Write(config.NacosConfigType) 302 config.GddNacosConfigDataid.Write(dataId) 303 config.GddPort.Write("6060") 304 ddhttp.InitialiseRemoteConfigListener() 305 go func() { 306 srv := ddhttp.NewDefaultHttpSrv() 307 srv.AddRoute(Routes(NewMocksvcHandler())...) 308 srv.Run() 309 }() 310 time.Sleep(10 * time.Millisecond) 311 os.Setenv("DDHTTP", "http://localhost:6060/v1") 312 client := NewMockClient() 313 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 314 defer cancel() 315 _, ret, err := client.GetUser(ctx, nil) 316 So(err, ShouldBeNil) 317 So(ret, ShouldEqual, "go-doudou") 318 }) 319 } 320 321 func Test_UnknownRemoteConfigType(t *testing.T) { 322 Convey("Should be equal to go-doudou with unknown remote config type", t, func() { 323 _ = config.GddConfigRemoteType.Write("Unknown") 324 config.GddPort.Write("6061") 325 ddhttp.InitialiseRemoteConfigListener() 326 go func() { 327 srv := ddhttp.NewDefaultHttpSrv() 328 srv.AddRoute(Routes(NewMocksvcHandler())...) 329 srv.Run() 330 }() 331 time.Sleep(10 * time.Millisecond) 332 os.Setenv("DDHTTP", "http://localhost:6061/v1") 333 client := NewMockClient() 334 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 335 defer cancel() 336 _, ret, err := client.GetUser(ctx, nil) 337 So(err, ShouldBeNil) 338 So(ret, ShouldEqual, "go-doudou") 339 }) 340 } 341 342 func Test_ApolloConfigType(t *testing.T) { 343 Convey("Should be equal to go-doudou with apollo config", t, func() { 344 ctrl := gomock.NewController(t) 345 defer ctrl.Finish() 346 configClient := mock.NewMockClient(ctrl) 347 factory := &memory.DefaultCacheFactory{} 348 cache := factory.Create() 349 cache.Set("gdd.retry.count", "3", 0) 350 cache.Set("gdd.weight", "5", 0) 351 configClient. 352 EXPECT(). 353 GetConfigCache(config.DefaultGddApolloNamespace). 354 AnyTimes(). 355 Return(cache) 356 357 configClient. 358 EXPECT(). 359 AddChangeListener(gomock.Any()). 360 AnyTimes(). 361 Return() 362 363 configmgr.StartWithConfig = func(loadAppConfig func() (*apolloConfig.AppConfig, error)) (agollo.Client, error) { 364 _, _ = loadAppConfig() 365 return configClient, nil 366 } 367 368 configmgr.ApolloClient = configClient 369 370 _ = config.GddConfigRemoteType.Write(config.ApolloConfigType) 371 config.GddPort.Write("6062") 372 ddhttp.InitialiseRemoteConfigListener() 373 go func() { 374 srv := ddhttp.NewDefaultHttpSrv() 375 srv.AddRoute(Routes(NewMocksvcHandler())...) 376 srv.Run() 377 }() 378 time.Sleep(10 * time.Millisecond) 379 os.Setenv("DDHTTP", "http://localhost:6062/v1") 380 client := NewMockClient() 381 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 382 defer cancel() 383 _, ret, err := client.GetUser(ctx, nil) 384 So(err, ShouldBeNil) 385 So(ret, ShouldEqual, "go-doudou") 386 }) 387 } 388 389 func TestCallbackOnChange(t *testing.T) { 390 Convey("Environment variable GDD_MANAGE_USER should be changed", t, func() { 391 listener := ddhttp.NewHttpConfigListener() 392 ddhttp.CallbackOnChange(listener)(&configmgr.NacosChangeEvent{ 393 Namespace: "", 394 Group: "", 395 DataId: "", 396 Changes: map[string]maputils.Change{ 397 "gdd.manage.user": { 398 OldValue: "admin", 399 NewValue: "go-doudou", 400 ChangeType: maputils.MODIFIED, 401 }, 402 }, 403 }) 404 So(config.GddManageUser.Load(), ShouldEqual, "") 405 ddhttp.CallbackOnChange(listener)(&configmgr.NacosChangeEvent{ 406 Namespace: "", 407 Group: "", 408 DataId: "", 409 Changes: map[string]maputils.Change{ 410 "gdd.manage.user": { 411 OldValue: "admin", 412 NewValue: "go-doudou", 413 ChangeType: maputils.MODIFIED, 414 }, 415 }, 416 }) 417 So(config.GddManageUser.Load(), ShouldEqual, "go-doudou") 418 }) 419 } 420 421 func Test_log_get_text(t *testing.T) { 422 Convey("Should be equal to go-doudou", t, func() { 423 config.GddLogReqEnable.Write("true") 424 config.GddPort.Write("6063") 425 go func() { 426 srv := ddhttp.NewDefaultHttpSrv() 427 srv.AddRoute(Routes(NewMocksvcHandler())...) 428 srv.Run() 429 }() 430 time.Sleep(10 * time.Millisecond) 431 os.Setenv("DDHTTP", "http://localhost:6063/v1") 432 client := NewMockClient() 433 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 434 defer cancel() 435 _, ret, err := client.GetUser(ctx, nil) 436 So(err, ShouldBeNil) 437 So(ret, ShouldEqual, "go-doudou") 438 }) 439 } 440 441 func Test_log_post_json(t *testing.T) { 442 Convey("Should be equal to OK", t, func() { 443 config.GddLogReqEnable.Write("true") 444 config.GddPort.Write("6064") 445 go func() { 446 srv := ddhttp.NewDefaultHttpSrv() 447 srv.AddRoute(Routes(NewMocksvcHandler())...) 448 srv.Run() 449 }() 450 time.Sleep(10 * time.Millisecond) 451 os.Setenv("DDHTTP", "http://localhost:6064/v1") 452 client := NewMockClient() 453 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 454 defer cancel() 455 _, ret, err := client.SaveUser(ctx, nil, UserVo{ 456 Username: "go-doudou", 457 Password: "go-doudou", 458 }) 459 So(err, ShouldBeNil) 460 So(ret, ShouldEqual, "OK") 461 }) 462 } 463 464 func Test_log_post_formdata(t *testing.T) { 465 Convey("Should be equal to OK", t, func() { 466 config.GddLogReqEnable.Write("true") 467 config.GddPort.Write("6065") 468 go func() { 469 srv := ddhttp.NewDefaultHttpSrv() 470 srv.AddRoute(Routes(NewMocksvcHandler())...) 471 srv.Run() 472 }() 473 time.Sleep(10 * time.Millisecond) 474 os.Setenv("DDHTTP", "http://localhost:6065/v1") 475 client := NewMockClient() 476 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 477 defer cancel() 478 _, ret, err := client.SignUp(ctx, nil, "go-doudou", "go-doudou") 479 So(err, ShouldBeNil) 480 So(ret, ShouldEqual, "OK") 481 }) 482 } 483 484 func Test_basicauth_401(t *testing.T) { 485 Convey("Should return 401", t, func() { 486 config.GddPort.Write("6066") 487 config.GddManagePass.Write("admin") 488 go func() { 489 srv := ddhttp.NewDefaultHttpSrv() 490 srv.AddRoute(Routes(NewMocksvcHandler())...) 491 srv.Run() 492 }() 493 time.Sleep(10 * time.Millisecond) 494 resp, err := http.Get("http://localhost:6066/go-doudou/config") 495 So(err, ShouldBeNil) 496 So(resp.StatusCode, ShouldEqual, 401) 497 }) 498 } 499 500 func Test_basicauth_200(t *testing.T) { 501 Convey("Should return 200", t, func() { 502 config.GddPort.Write("6066") 503 config.GddManageUser.Write("admin") 504 config.GddManagePass.Write("admin") 505 go func() { 506 srv := ddhttp.NewDefaultHttpSrv() 507 srv.AddRoute(Routes(NewMocksvcHandler())...) 508 srv.Run() 509 }() 510 time.Sleep(10 * time.Millisecond) 511 resp, err := http.Get("http://admin:admin@localhost:6066/go-doudou/config") 512 So(err, ShouldBeNil) 513 So(resp.StatusCode, ShouldEqual, 200) 514 }) 515 } 516 517 func Test_recovery(t *testing.T) { 518 Convey("Should recovery from panic", t, func() { 519 config.GddPort.Write("6067") 520 go func() { 521 srv := ddhttp.NewDefaultHttpSrv() 522 srv.AddRoute(Routes(NewMocksvcHandler())...) 523 srv.Run() 524 }() 525 time.Sleep(10 * time.Millisecond) 526 os.Setenv("DDHTTP", "http://localhost:6067/v1") 527 client := NewMockClient() 528 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 529 defer cancel() 530 _, _, err := client.GetPanic(ctx, nil) 531 So(err, ShouldNotBeNil) 532 }) 533 } 534 535 func Test_bulkhead(t *testing.T) { 536 Convey("Should work with bulkhead", t, func() { 537 config.GddPort.Write("6068") 538 go func() { 539 srv := ddhttp.NewDefaultHttpSrv() 540 srv.AddRoute(Routes(NewMocksvcHandler())...) 541 srv.AddMiddleware(ddhttp.BulkHead(4, 500*time.Millisecond)) 542 srv.Run() 543 }() 544 time.Sleep(10 * time.Millisecond) 545 os.Setenv("DDHTTP", "http://localhost:6068/v1") 546 client := NewMockClient() 547 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 548 defer cancel() 549 _, ret, err := client.SignUp(ctx, nil, "go-doudou", "go-doudou") 550 So(err, ShouldBeNil) 551 So(ret, ShouldEqual, "OK") 552 }) 553 } 554 555 func Test_bulkhead_fail(t *testing.T) { 556 Convey("Should fail with bulkhead", t, func() { 557 ctrl := gomock.NewController(t) 558 defer ctrl.Finish() 559 runner := httpMock.NewMockRunner(ctrl) 560 runner. 561 EXPECT(). 562 Run(gomock.Any(), gomock.Any()). 563 AnyTimes(). 564 Return(errors.New("mock runner test error")) 565 566 ddhttp.RunnerChain = func(middlewares ...goresilience.Middleware) goresilience.Runner { 567 return runner 568 } 569 config.GddPort.Write("6069") 570 go func() { 571 srv := ddhttp.NewDefaultHttpSrv() 572 srv.AddRoute(Routes(NewMocksvcHandler())...) 573 srv.AddMiddleware(ddhttp.BulkHead(4, 500*time.Millisecond)) 574 srv.Run() 575 }() 576 time.Sleep(10 * time.Millisecond) 577 os.Setenv("DDHTTP", "http://localhost:6069/v1") 578 client := NewMockClient() 579 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 580 defer cancel() 581 _, _, err := client.SignUp(ctx, nil, "go-doudou", "go-doudou") 582 So(err.Error(), ShouldEqual, "too many requests") 583 }) 584 }