trpc.group/trpc-go/trpc-go@v1.0.3/restful/restful_test.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 package restful_test 15 16 import ( 17 "bytes" 18 "compress/gzip" 19 "context" 20 "encoding/json" 21 "errors" 22 "fmt" 23 "io" 24 "net/http" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/stretchr/testify/require" 31 "github.com/valyala/fasthttp" 32 "google.golang.org/protobuf/proto" 33 "google.golang.org/protobuf/testing/protocmp" 34 "google.golang.org/protobuf/types/known/emptypb" 35 36 thttp "trpc.group/trpc-go/trpc-go/http" 37 "trpc.group/trpc-go/trpc-go/restful" 38 "trpc.group/trpc-go/trpc-go/server" 39 bpb "trpc.group/trpc-go/trpc-go/testdata/restful/bookstore" 40 hpb "trpc.group/trpc-go/trpc-go/testdata/restful/helloworld" 41 "trpc.group/trpc-go/trpc-go/transport" 42 ) 43 44 // helloworld service impl 45 type greeterServerImpl struct{} 46 47 func (s *greeterServerImpl) SayHello(ctx context.Context, req *hpb.HelloRequest) (*hpb.HelloReply, error) { 48 rsp := &hpb.HelloReply{} 49 if req.Name != "xyz" { 50 return nil, errors.New("test error") 51 } 52 rsp.Message = "test" 53 return rsp, nil 54 } 55 56 func TestHelloworldService(t *testing.T) { 57 // service registration 58 s := &server.Server{} 59 service := server.New( 60 server.WithAddress("127.0.0.1:6677"), 61 server.WithServiceName("trpc.test.helloworld.Service"), 62 server.WithNetwork("tcp"), 63 server.WithProtocol("restful"), 64 server.WithRESTOptions( 65 restful.WithHeaderMatcher( 66 func(ctx context.Context, w http.ResponseWriter, r *http.Request, serviceName string, 67 methodName string) (context.Context, error) { 68 return context.Background(), nil 69 }, 70 ), 71 restful.WithResponseHandler( 72 func( 73 ctx context.Context, 74 w http.ResponseWriter, 75 r *http.Request, 76 resp proto.Message, 77 body []byte, 78 ) error { 79 if r.Header.Get("Accept-Encoding") != "gzip" { 80 return errors.New("test error") 81 } 82 writeCloser, err := (&restful.GZIPCompressor{}).Compress(w) 83 if err != nil { 84 return err 85 } 86 defer writeCloser.Close() 87 w.Header().Set("Content-Encoding", "gzip") 88 w.Header().Set("Content-Type", "application/json") 89 writeCloser.Write(body) 90 return nil 91 }, 92 ), 93 restful.WithErrorHandler( 94 func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) { 95 w.WriteHeader(500) 96 w.Header().Add("Content-Type", "application/json") 97 w.Write([]byte(`{"massage":"test error"}`)) 98 }, 99 ), 100 ), 101 ) 102 s.AddService("trpc.test.helloworld.Service", service) 103 hpb.RegisterGreeterService(s, &greeterServerImpl{}) 104 105 // start server 106 go func() { 107 err := s.Serve() 108 require.Nil(t, err) 109 }() 110 111 time.Sleep(100 * time.Millisecond) 112 113 // create restful request 114 data := `{"name": "xyz"}` 115 buf := bytes.Buffer{} 116 gBuf := gzip.NewWriter(&buf) 117 _, err := gBuf.Write([]byte(data)) 118 require.Nil(t, err) 119 gBuf.Close() 120 req, err := http.NewRequest("POST", "http://127.0.0.1:6677/v1/foobar", &buf) 121 require.Nil(t, err) 122 req.Header.Add("Content-Type", "anything") 123 req.Header.Add("Content-Encoding", "gzip") 124 req.Header.Add("Accept-Encoding", "gzip") 125 126 // send restful request 127 cli := http.Client{} 128 resp, err := cli.Do(req) 129 require.Nil(t, err) 130 defer resp.Body.Close() 131 require.Equal(t, resp.StatusCode, http.StatusOK) 132 reader, err := gzip.NewReader(resp.Body) 133 require.Nil(t, err) 134 bodyBytes, err := io.ReadAll(reader) 135 require.Nil(t, err) 136 type responseBody struct { 137 Message string `json:"message"` 138 } 139 respBody := &responseBody{} 140 json.Unmarshal(bodyBytes, respBody) 141 require.Equal(t, respBody.Message, "test") 142 143 // test matching all by query params 144 req2, err := http.NewRequest("GET", "http://127.0.0.1:6677/v2/bar?name=xyz", nil) 145 require.Nil(t, err) 146 resp2, err := http.DefaultClient.Do(req2) 147 require.Nil(t, err) 148 defer resp2.Body.Close() 149 require.Equal(t, resp2.StatusCode, http.StatusOK) 150 151 // test response content-type 152 require.Equal(t, resp2.Header.Get("Content-Type"), "application/json") 153 } 154 155 func TestHeaderMatcher(t *testing.T) { 156 // service registration 157 s := &server.Server{} 158 service := server.New(server.WithAddress("127.0.0.1:6678"), 159 server.WithServiceName("test"), 160 server.WithNetwork("tcp"), 161 server.WithProtocol("restful"), 162 server.WithRESTOptions(restful.WithHeaderMatcher(func(ctx context.Context, w http.ResponseWriter, 163 r *http.Request, serviceName string, methodName string) (context.Context, error) { 164 return nil, errors.New("test error") 165 })), 166 ) 167 s.AddService("test", service) 168 hpb.RegisterGreeterService(s, &greeterServerImpl{}) 169 170 // start server 171 go func() { 172 err := s.Serve() 173 require.Nil(t, err) 174 }() 175 176 time.Sleep(100 * time.Millisecond) 177 178 // test header matcher error 179 req, err := http.NewRequest("POST", "http://127.0.0.1:6678/v1/foobar", 180 bytes.NewBuffer([]byte(`{"name": "xyz"}`))) 181 require.Nil(t, err) 182 resp, err := http.DefaultClient.Do(req) 183 require.Nil(t, err) 184 defer resp.Body.Close() 185 require.Equal(t, http.StatusBadRequest, resp.StatusCode) 186 } 187 188 func TestResponseHandler(t *testing.T) { 189 // service registration 190 s := &server.Server{} 191 service := server.New( 192 server.WithAddress("127.0.0.1:6679"), 193 server.WithServiceName("test.ResponseHandler"), 194 server.WithNetwork("tcp"), 195 server.WithProtocol("restful"), 196 server.WithRESTOptions(restful.WithResponseHandler( 197 func( 198 ctx context.Context, 199 w http.ResponseWriter, 200 r *http.Request, 201 resp proto.Message, 202 body []byte, 203 ) error { 204 return errors.New("test error") 205 }, 206 )), 207 ) 208 s.AddService("test.ResponseHandler", service) 209 hpb.RegisterGreeterService(s, &greeterServerImpl{}) 210 211 // start server 212 go func() { 213 err := s.Serve() 214 require.Nil(t, err) 215 }() 216 217 time.Sleep(100 * time.Millisecond) 218 219 // test response handler error 220 req, err := http.NewRequest("POST", "http://127.0.0.1:6679/v1/foobar", 221 bytes.NewBuffer([]byte(`{"name": "xyz"}`))) 222 require.Nil(t, err) 223 resp, err := http.DefaultClient.Do(req) 224 require.Nil(t, err) 225 defer resp.Body.Close() 226 require.Equal(t, http.StatusInternalServerError, resp.StatusCode) 227 } 228 229 // bookstore service impl 230 type bookstoreServiceImpl struct{} 231 232 var shelves = map[int64]*bpb.Shelf{ 233 0: { 234 Id: 0, 235 Theme: "shelf_0", 236 }, 237 1: { 238 Id: 1, 239 Theme: "shelf_1", 240 }, 241 } 242 243 var shelf2Books = map[int64]map[int64]*bpb.Book{ 244 0: { 245 0: { 246 Id: 0, 247 Author: "author_0", 248 Title: "title_0", 249 }, 250 }, 251 1: { 252 1: { 253 Id: 1, 254 Author: "author_1", 255 Title: "title_1", 256 }, 257 }, 258 } 259 260 // ListShelves lists all shelves. 261 func (s *bookstoreServiceImpl) ListShelves(ctx context.Context, req *emptypb.Empty) (rsp *bpb.ListShelvesResponse, err error) { 262 rsp = &bpb.ListShelvesResponse{} 263 for _, each := range shelves { 264 rsp.Shelves = append(rsp.Shelves, each) 265 } 266 267 return rsp, nil 268 } 269 270 // CreateShelf creates a shelf. 271 func (s *bookstoreServiceImpl) CreateShelf(ctx context.Context, req *bpb.CreateShelfRequest) (rsp *bpb.Shelf, err error) { 272 rsp = &bpb.Shelf{} 273 id := req.GetShelf().GetId() 274 if _, ok := shelves[id]; ok { 275 return nil, &restful.WithStatusCode{ 276 StatusCode: http.StatusConflict, 277 Err: errors.New("shelf already exists"), 278 } 279 } 280 281 shelves[id] = req.GetShelf() 282 rsp.Id = req.GetShelf().Id 283 rsp.Theme = req.GetShelf().Theme 284 shelf2Books[id] = make(map[int64]*bpb.Book) 285 restful.SetStatusCodeOnSucceed(ctx, http.StatusCreated) 286 return rsp, nil 287 } 288 289 // GetShelf returns a shelf. 290 func (s *bookstoreServiceImpl) GetShelf(ctx context.Context, req *bpb.GetShelfRequest) (rsp *bpb.Shelf, err error) { 291 rsp = &bpb.Shelf{} 292 id := req.GetShelf() 293 if shelf, ok := shelves[id]; !ok { 294 return nil, &restful.WithStatusCode{ 295 StatusCode: http.StatusNotFound, 296 Err: errors.New("shelf not found"), 297 } 298 } else { 299 rsp.Id = shelf.Id 300 rsp.Theme = shelf.Theme 301 restful.SetStatusCodeOnSucceed(ctx, http.StatusAccepted) 302 } 303 return rsp, nil 304 } 305 306 // DeleteShelf deletes a shelf. 307 func (s *bookstoreServiceImpl) DeleteShelf(ctx context.Context, req *bpb.DeleteShelfRequest) (rsp *emptypb.Empty, err error) { 308 rsp = &emptypb.Empty{} 309 id := req.GetShelf() 310 if _, ok := shelves[id]; !ok { 311 return nil, &restful.WithStatusCode{ 312 StatusCode: http.StatusNotFound, 313 Err: errors.New("shelf not found"), 314 } 315 } 316 delete(shelves, id) 317 delete(shelf2Books, id) 318 restful.SetStatusCodeOnSucceed(ctx, http.StatusNoContent) 319 return rsp, nil 320 } 321 322 // ListBooks lists all books. 323 func (s *bookstoreServiceImpl) ListBooks(ctx context.Context, req *bpb.ListBooksRequest) (rsp *bpb.ListBooksResponse, err error) { 324 rsp = &bpb.ListBooksResponse{} 325 shelfID := req.GetShelf() 326 books, ok := shelf2Books[shelfID] 327 if !ok { 328 return nil, &restful.WithStatusCode{ 329 StatusCode: http.StatusNotFound, 330 Err: errors.New("shelf not found"), 331 } 332 } 333 334 for _, book := range books { 335 rsp.Books = append(rsp.Books, book) 336 } 337 restful.SetStatusCodeOnSucceed(ctx, http.StatusAccepted) 338 return rsp, nil 339 } 340 341 // CreateBook creates a book. 342 func (s *bookstoreServiceImpl) CreateBook(ctx context.Context, req *bpb.CreateBookRequest) (rsp *bpb.Book, err error) { 343 rsp = &bpb.Book{} 344 shelfID := req.GetShelf() 345 books, ok := shelf2Books[shelfID] 346 if !ok { 347 return nil, &restful.WithStatusCode{ 348 StatusCode: http.StatusNotFound, 349 Err: errors.New("shelf not found"), 350 } 351 } 352 353 bookID := req.GetBook().GetId() 354 if _, ok := books[bookID]; ok { 355 return nil, &restful.WithStatusCode{ 356 StatusCode: http.StatusConflict, 357 Err: errors.New("book already exists"), 358 } 359 } 360 361 shelf2Books[shelfID][bookID] = req.GetBook() 362 rsp.Id = req.GetBook().Id 363 rsp.Title = req.GetBook().Title 364 rsp.Author = req.GetBook().Author 365 rsp.Content = req.GetBook().Content 366 restful.SetStatusCodeOnSucceed(ctx, http.StatusCreated) 367 return rsp, nil 368 } 369 370 // GetBook returns a book. 371 func (s *bookstoreServiceImpl) GetBook(ctx context.Context, req *bpb.GetBookRequest) (rsp *bpb.Book, err error) { 372 rsp = &bpb.Book{} 373 shelfID := req.GetShelf() 374 books, ok := shelf2Books[shelfID] 375 if !ok { 376 return nil, &restful.WithStatusCode{ 377 StatusCode: http.StatusNotFound, 378 Err: errors.New("shelf not found"), 379 } 380 } 381 382 bookID := req.GetBook() 383 if book, ok := books[bookID]; !ok { 384 return nil, &restful.WithStatusCode{ 385 StatusCode: http.StatusNotFound, 386 Err: errors.New("book not found"), 387 } 388 } else { 389 rsp.Id = book.Id 390 rsp.Author = book.Author 391 rsp.Title = book.Title 392 rsp.Content = book.Content 393 restful.SetStatusCodeOnSucceed(ctx, http.StatusAccepted) 394 } 395 return rsp, nil 396 } 397 398 // DeleteBook deletes a book. 399 func (s *bookstoreServiceImpl) DeleteBook(ctx context.Context, req *bpb.DeleteBookRequest) (rsp *emptypb.Empty, err error) { 400 rsp = &emptypb.Empty{} 401 shelfID := req.GetShelf() 402 books, ok := shelf2Books[shelfID] 403 if !ok { 404 return nil, &restful.WithStatusCode{ 405 StatusCode: http.StatusNotFound, 406 Err: errors.New("shelf not found"), 407 } 408 } 409 410 bookID := req.GetBook() 411 if _, ok := books[bookID]; !ok { 412 return nil, &restful.WithStatusCode{ 413 StatusCode: http.StatusNotFound, 414 Err: errors.New("book not found"), 415 } 416 } 417 418 delete(shelf2Books[shelfID], bookID) 419 restful.SetStatusCodeOnSucceed(ctx, http.StatusAccepted) 420 return rsp, nil 421 } 422 423 // UpdateBook updates a book. 424 func (s *bookstoreServiceImpl) UpdateBook(ctx context.Context, req *bpb.UpdateBookRequest) (rsp *bpb.Book, err error) { 425 rsp = &bpb.Book{} 426 shelfID := req.GetShelf() 427 books, ok := shelf2Books[shelfID] 428 if !ok { 429 return nil, &restful.WithStatusCode{ 430 StatusCode: http.StatusNotFound, 431 Err: errors.New("shelf not found"), 432 } 433 } 434 435 bookID := req.GetBook().Id 436 book, ok := books[bookID] 437 if !ok { 438 return nil, &restful.WithStatusCode{ 439 StatusCode: http.StatusNotFound, 440 Err: errors.New("book not found"), 441 } 442 } 443 444 for _, path := range req.GetUpdateMask().Paths { 445 switch path { 446 case "author": 447 book.Author = req.Book.Author 448 case "title": 449 book.Title = req.Book.Title 450 case "content.summary": 451 book.Content = req.Book.Content 452 default: 453 } 454 } 455 456 rsp.Id = book.Id 457 rsp.Author = book.Author 458 rsp.Title = book.Title 459 rsp.Content = book.Content 460 restful.SetStatusCodeOnSucceed(ctx, http.StatusAccepted) 461 return rsp, nil 462 } 463 464 func (s *bookstoreServiceImpl) UpdateBooks(ctx context.Context, req *bpb.UpdateBooksRequest) (rsp *bpb.ListBooksResponse, err error) { 465 books, ok := shelf2Books[req.Shelf] 466 if !ok { 467 return nil, &restful.WithStatusCode{ 468 StatusCode: http.StatusNotFound, 469 Err: errors.New("shelf not found"), 470 } 471 } 472 473 for _, b := range req.Books { 474 _, ok := books[b.Id] 475 if !ok { 476 return nil, &restful.WithStatusCode{ 477 StatusCode: http.StatusNotFound, 478 Err: fmt.Errorf("book %d not found", b.Id), 479 } 480 } 481 books[b.Id] = b 482 } 483 484 restful.SetStatusCodeOnSucceed(ctx, http.StatusAccepted) 485 return &bpb.ListBooksResponse{Books: req.Books}, nil 486 } 487 488 func httpNewRequest(t *testing.T, method, url string, body io.Reader, contentType string) *http.Request { 489 req, err := http.NewRequest(method, url, body) 490 require.Nil(t, err) 491 req.Header.Add("Content-Type", contentType) 492 return req 493 } 494 495 func TestBookstoreService(t *testing.T) { 496 // service registration 497 s := &server.Server{} 498 service := server.New(server.WithAddress("127.0.0.1:6666"), 499 server.WithServiceName("trpc.test.bookstore.Bookstore"), 500 server.WithProtocol("restful")) 501 s.AddService("trpc.test.bookstore.Bookstore", service) 502 bpb.RegisterBookstoreService(s, &bookstoreServiceImpl{}) 503 504 // start server 505 go func() { 506 err := s.Serve() 507 require.Nil(t, err) 508 }() 509 510 time.Sleep(100 * time.Millisecond) 511 512 for _, test := range []struct { 513 httpRequest *http.Request 514 respStatusCode int 515 expectShelves map[int64]*bpb.Shelf 516 expectShelf2Books map[int64]map[int64]*bpb.Book 517 desc string 518 }{ 519 { 520 httpRequest: httpNewRequest(t, "GET", "http://127.0.0.1:6666/shelves", nil, 521 "application/json"), 522 respStatusCode: http.StatusOK, 523 desc: "test listing shelves", 524 }, 525 { 526 httpRequest: httpNewRequest(t, "POST", "http://127.0.0.1:6666/shelf", bytes.NewBuffer([]byte( 527 `{"shelf":{"id":2,"theme":"shelf_2"}}`)), "application/json"), 528 respStatusCode: http.StatusCreated, 529 expectShelves: map[int64]*bpb.Shelf{ 530 0: {Id: 0, Theme: "shelf_0"}, 531 1: {Id: 1, Theme: "shelf_1"}, 532 2: {Id: 2, Theme: "shelf_2"}, 533 }, 534 expectShelf2Books: map[int64]map[int64]*bpb.Book{ 535 0: {0: {Id: 0, Author: "author_0", Title: "title_0"}}, 536 1: {1: {Id: 1, Author: "author_1", Title: "title_1"}}, 537 2: {}, 538 }, 539 desc: "test creating a shelf", 540 }, 541 { 542 httpRequest: httpNewRequest(t, "POST", "http://127.0.0.1:6666/shelf", bytes.NewBuffer([]byte( 543 `{"shelf":{"id":2,"theme":"shelf_02"}}`)), "application/json"), 544 respStatusCode: http.StatusConflict, 545 desc: "test creating dup shelf", 546 }, 547 { 548 httpRequest: httpNewRequest(t, "DELETE", "http://127.0.0.1:6666/shelf/2", 549 nil, "application/json"), 550 respStatusCode: http.StatusNoContent, 551 expectShelves: map[int64]*bpb.Shelf{ 552 0: {Id: 0, Theme: "shelf_0"}, 553 1: {Id: 1, Theme: "shelf_1"}, 554 }, 555 expectShelf2Books: map[int64]map[int64]*bpb.Book{ 556 0: {0: {Id: 0, Author: "author_0", Title: "title_0"}}, 557 1: {1: {Id: 1, Author: "author_1", Title: "title_1"}}, 558 }, 559 desc: "test deleting a shelf", 560 }, 561 { 562 httpRequest: httpNewRequest(t, "DELETE", "http://127.0.0.1:6666/shelf/2", 563 nil, "application/json"), 564 respStatusCode: http.StatusNotFound, 565 desc: "test deleting a shelf non exists", 566 }, 567 { 568 httpRequest: httpNewRequest(t, "POST", "http://127.0.0.1:6666/anything", 569 nil, "application/json"), 570 respStatusCode: http.StatusNotFound, 571 desc: "test invalid url path", 572 }, 573 { 574 httpRequest: httpNewRequest(t, "POST", 575 "http://127.0.0.1:6666/shelf/theme/shelf_2?shelf.theme=x&shelf.id=2", nil, 576 "application/json"), 577 respStatusCode: http.StatusCreated, 578 expectShelves: map[int64]*bpb.Shelf{ 579 0: {Id: 0, Theme: "shelf_0"}, 580 1: {Id: 1, Theme: "shelf_1"}, 581 2: {Id: 2, Theme: "shelf_2"}, 582 }, 583 expectShelf2Books: map[int64]map[int64]*bpb.Book{ 584 0: {0: {Id: 0, Author: "author_0", Title: "title_0"}}, 585 1: {1: {Id: 1, Author: "author_1", Title: "title_1"}}, 586 2: {}, 587 }, 588 desc: "test creating a shelf with query params", 589 }, 590 { 591 httpRequest: httpNewRequest(t, "POST", 592 "http://127.0.0.1:6666/shelf/theme/shelf_2?anything=2", 593 nil, "application/json"), 594 respStatusCode: http.StatusBadRequest, 595 desc: "test creating a shelf with invalid query params", 596 }, 597 { 598 httpRequest: httpNewRequest(t, "POST", "http://127.0.0.1:6666/book/shelf/1", 599 bytes.NewBuffer([]byte("anything")), "application/json"), 600 respStatusCode: http.StatusBadRequest, 601 desc: "test creating a book with invalid body data", 602 }, 603 { 604 httpRequest: httpNewRequest(t, "PATCH", "http://127.0.0.1:6666/book/shelfid/1/bookid/1", 605 bytes.NewBuffer([]byte(`{"author":"anonymous","content":{"summary":"life of a hero"}}`)), 606 "application/json"), 607 respStatusCode: http.StatusAccepted, 608 expectShelves: map[int64]*bpb.Shelf{ 609 0: {Id: 0, Theme: "shelf_0"}, 610 1: {Id: 1, Theme: "shelf_1"}, 611 2: {Id: 2, Theme: "shelf_2"}, 612 }, 613 expectShelf2Books: map[int64]map[int64]*bpb.Book{ 614 0: {0: {Id: 0, Author: "author_0", Title: "title_0"}}, 615 1: {1: {Id: 1, Author: "anonymous", Title: "title_1", 616 Content: &bpb.Content{Summary: "life of a hero"}}}, 617 2: {}, 618 }, 619 desc: "test updating a book", 620 }, 621 { 622 httpRequest: httpNewRequest(t, "POST", "http://127.0.0.1:6666/book/shelf/2", 623 strings.NewReader("id=2&author=author_2&title=title_2&content.summary=whatever"), 624 "application/x-www-form-urlencoded"), 625 respStatusCode: http.StatusCreated, 626 expectShelves: map[int64]*bpb.Shelf{ 627 0: {Id: 0, Theme: "shelf_0"}, 628 1: {Id: 1, Theme: "shelf_1"}, 629 2: {Id: 2, Theme: "shelf_2"}, 630 }, 631 expectShelf2Books: map[int64]map[int64]*bpb.Book{ 632 0: {0: {Id: 0, Author: "author_0", Title: "title_0"}}, 633 1: {1: {Id: 1, Author: "anonymous", Title: "title_1", 634 Content: &bpb.Content{Summary: "life of a hero"}}}, 635 2: {2: {Id: 2, Author: "author_2", Title: "title_2", 636 Content: &bpb.Content{Summary: "whatever"}}}, 637 }, 638 desc: "test posting form to create book", 639 }, 640 { 641 httpRequest: httpNewRequest(t, "POST", "http://127.0.0.1:6666/book/shelf/2", 642 strings.NewReader("id=3&author=author_3&title=title_3&content.summary=whatever"), 643 "application/x-www-form-urlencoded; charset=UTF-8"), 644 respStatusCode: http.StatusCreated, 645 expectShelves: map[int64]*bpb.Shelf{ 646 0: {Id: 0, Theme: "shelf_0"}, 647 1: {Id: 1, Theme: "shelf_1"}, 648 2: {Id: 2, Theme: "shelf_2"}, 649 }, 650 expectShelf2Books: map[int64]map[int64]*bpb.Book{ 651 0: {0: {Id: 0, Author: "author_0", Title: "title_0"}}, 652 1: {1: {Id: 1, Author: "anonymous", Title: "title_1", 653 Content: &bpb.Content{Summary: "life of a hero"}}}, 654 2: { 655 2: {Id: 2, Author: "author_2", Title: "title_2", 656 Content: &bpb.Content{Summary: "whatever"}}, 657 3: {Id: 3, Author: "author_3", Title: "title_3", 658 Content: &bpb.Content{Summary: "whatever"}}, 659 }, 660 }, 661 desc: "test posting form to create book", 662 }, 663 { 664 httpRequest: httpNewRequest(t, "PATCH", "http://127.0.0.1:6666/book/shelfid/2", 665 bytes.NewBuffer([]byte(`[{"id":"2", "author":"author_2"},{"id":"3", "author":"author_3"}]`)), 666 "application/json"), 667 respStatusCode: http.StatusAccepted, 668 expectShelves: map[int64]*bpb.Shelf{ 669 0: {Id: 0, Theme: "shelf_0"}, 670 1: {Id: 1, Theme: "shelf_1"}, 671 2: {Id: 2, Theme: "shelf_2"}, 672 }, 673 expectShelf2Books: map[int64]map[int64]*bpb.Book{ 674 0: {0: {Id: 0, Author: "author_0", Title: "title_0"}}, 675 1: {1: {Id: 1, Author: "anonymous", Title: "title_1", 676 Content: &bpb.Content{Summary: "life of a hero"}}}, 677 2: { 678 2: {Id: 2, Author: "author_2"}, 679 3: {Id: 3, Author: "author_3"}, 680 }, 681 }, 682 desc: "test updating books of a shelf", 683 }, 684 } { 685 req := test.httpRequest 686 cli := http.Client{} 687 resp, err := cli.Do(req) 688 require.Nil(t, err, test.desc) 689 require.Equal(t, test.respStatusCode, resp.StatusCode, test.desc) 690 691 if resp.StatusCode > 200 && resp.StatusCode < 300 { 692 require.Equal(t, "", cmp.Diff(shelves, test.expectShelves, protocmp.Transform()), test.desc) 693 require.Equal(t, "", cmp.Diff(shelf2Books, test.expectShelf2Books, protocmp.Transform()), test.desc) 694 } 695 } 696 } 697 698 func TestBasedOnFastHTTP(t *testing.T) { 699 // replace server transport based on fasthttp 700 transport.RegisterServerTransport("restful_based_on_fasthttp", 701 thttp.NewRESTServerTransport(true)) 702 703 // service registration 704 s := &server.Server{} 705 service := server.New(server.WithAddress("127.0.0.1:45678"), 706 server.WithServiceName("trpc.test.helloworld.FastHTTP"), 707 server.WithProtocol("restful_based_on_fasthttp"), 708 server.WithRESTOptions( 709 restful.WithFastHTTPHeaderMatcher( 710 func(ctx context.Context, requestCtx *fasthttp.RequestCtx, serviceName string, 711 methodName string) (context.Context, error) { 712 return context.Background(), nil 713 }, 714 ), 715 restful.WithFastHTTPRespHandler( 716 func( 717 ctx context.Context, 718 requestCtx *fasthttp.RequestCtx, 719 resp proto.Message, 720 body []byte, 721 ) error { 722 if string(requestCtx.Request.Header.Peek("Accept-Encoding")) != "gzip" { 723 return errors.New("test error") 724 } 725 writeCloser, err := (&restful.GZIPCompressor{}). 726 Compress(requestCtx.Response.BodyWriter()) 727 if err != nil { 728 return err 729 } 730 defer writeCloser.Close() 731 requestCtx.Response.Header.Set("Content-Encoding", "gzip") 732 requestCtx.Response.Header.Set("Content-Type", "application/json") 733 writeCloser.Write(body) 734 return nil 735 }, 736 ), 737 restful.WithFastHTTPErrorHandler( 738 func(ctx context.Context, requestCtx *fasthttp.RequestCtx, err error) { 739 requestCtx.Response.SetStatusCode(http.StatusInternalServerError) 740 requestCtx.Response.Header.Set("Content-Type", "application/json") 741 requestCtx.Write([]byte(`{"massage":"test error"}`)) 742 }, 743 ), 744 ), 745 ) 746 s.AddService("trpc.test.helloworld.FastHTTP", service) 747 hpb.RegisterGreeterService(s, &greeterServerImpl{}) 748 749 // start server 750 go func() { 751 err := s.Serve() 752 require.Nil(t, err) 753 }() 754 755 time.Sleep(100 * time.Millisecond) 756 757 // create restful request 758 data := `{"name": "xyz"}` 759 buf := bytes.Buffer{} 760 gBuf := gzip.NewWriter(&buf) 761 _, err := gBuf.Write([]byte(data)) 762 require.Nil(t, err) 763 gBuf.Close() 764 req, err := http.NewRequest("POST", "http://127.0.0.1:45678/v1/foobar", &buf) 765 require.Nil(t, err) 766 req.Header.Add("Content-Type", "anything") 767 req.Header.Add("Content-Encoding", "gzip") 768 req.Header.Add("Accept-Encoding", "gzip") 769 770 // send restful request 771 cli := http.Client{} 772 resp, err := cli.Do(req) 773 require.Nil(t, err) 774 defer resp.Body.Close() 775 require.Equal(t, resp.StatusCode, http.StatusOK) 776 reader, err := gzip.NewReader(resp.Body) 777 require.Nil(t, err) 778 bodyBytes, err := io.ReadAll(reader) 779 require.Nil(t, err) 780 type responseBody struct { 781 Message string `json:"message"` 782 } 783 respBody := &responseBody{} 784 json.Unmarshal(bodyBytes, respBody) 785 require.Equal(t, respBody.Message, "test") 786 787 // test matching all by query params 788 req2, err := http.NewRequest("GET", "http://127.0.0.1:45678/v2/bar?name=xyz", nil) 789 require.Nil(t, err) 790 resp2, err := http.DefaultClient.Do(req2) 791 require.Nil(t, err) 792 defer resp2.Body.Close() 793 require.Equal(t, resp2.StatusCode, http.StatusOK) 794 795 // test response content-type 796 require.Equal(t, resp2.Header.Get("Content-Type"), "application/json") 797 798 // test server error 799 req3, err := http.NewRequest("GET", "http://127.0.0.1:45678/v2/bar?name=anything", nil) 800 require.Nil(t, err) 801 resp3, err := http.DefaultClient.Do(req3) 802 require.Nil(t, err) 803 defer resp3.Body.Close() 804 require.Equal(t, resp3.StatusCode, http.StatusInternalServerError) 805 806 // test err handler 807 data4 := `{"name": "abc"}` 808 buf4 := bytes.Buffer{} 809 gBuf4 := gzip.NewWriter(&buf4) 810 _, err = gBuf4.Write([]byte(data4)) 811 require.Nil(t, err) 812 gBuf4.Close() 813 req4, err := http.NewRequest("POST", "http://127.0.0.1:45678/v1/foobar", &buf4) 814 require.Nil(t, err) 815 req4.Header.Add("Content-Type", "anything") 816 req4.Header.Add("Content-Encoding", "gzip") 817 req4.Header.Add("Accept-Encoding", "gzip") 818 cli4 := http.Client{} 819 resp4, err := cli4.Do(req4) 820 require.Nil(t, err) 821 defer resp4.Body.Close() 822 require.Equal(t, resp4.StatusCode, http.StatusInternalServerError) 823 bodyBytes4, err := io.ReadAll(resp4.Body) 824 require.Nil(t, err) 825 require.Equal(t, bodyBytes4, []byte(`{"massage":"test error"}`)) 826 } 827 828 func TestDiscardUnknownParams(t *testing.T) { 829 // service registration 830 s := &server.Server{} 831 service := server.New( 832 server.WithAddress("127.0.0.1:6680"), 833 server.WithServiceName("trpc.test.helloworld.GreeterDiscardUnknownParams"), 834 server.WithNetwork("tcp"), 835 server.WithProtocol("restful"), 836 server.WithRESTOptions( 837 restful.WithDiscardUnknownParams(true), 838 ), 839 ) 840 s.AddService("trpc.test.helloworld.GreeterDiscardUnknownParams", service) 841 hpb.RegisterGreeterService(s, &greeterServerImpl{}) 842 843 // start server 844 go func() { 845 err := s.Serve() 846 require.Nil(t, err) 847 }() 848 849 time.Sleep(100 * time.Millisecond) 850 851 // unknown query params 852 req, err := http.NewRequest("GET", "http://127.0.0.1:6680/v2/bar?name=xyz&unknown_arg=anything", nil) 853 require.Nil(t, err) 854 resp, err := http.DefaultClient.Do(req) 855 require.Nil(t, err) 856 defer resp.Body.Close() 857 require.Equal(t, http.StatusOK, resp.StatusCode) 858 require.Nil(t, s.Close(nil)) 859 } 860 861 func TestMultipleServiceBinding(t *testing.T) { 862 s := &server.Server{} 863 serviceName := "trpc.test.helloworld.TestMultipleServiceBinding" 864 service := server.New( 865 server.WithAddress("127.0.0.1:6681"), 866 server.WithServiceName(serviceName), 867 server.WithNetwork("tcp"), 868 server.WithProtocol("restful"), 869 server.WithRESTOptions( 870 restful.WithDiscardUnknownParams(true), 871 ), 872 ) 873 s.AddService(serviceName, service) 874 // Register multiple services from different pb. 875 hpb.RegisterGreeterService(s, &greeterServerImpl{}) 876 bpb.RegisterBookstoreService(s, &bookstoreServiceImpl{}) 877 878 go func() { 879 err := s.Serve() 880 require.Nil(t, err) 881 }() 882 883 time.Sleep(100 * time.Millisecond) 884 885 // Test service 1. 886 req, err := http.NewRequest("GET", "http://127.0.0.1:6681/v2/bar?name=xyz", nil) 887 require.Nil(t, err) 888 resp, err := http.DefaultClient.Do(req) 889 require.Nil(t, err) 890 defer resp.Body.Close() 891 require.Equal(t, http.StatusOK, resp.StatusCode) 892 893 // Test service 2. 894 req2, err := http.NewRequest("GET", "http://127.0.0.1:6681/shelves", nil) 895 require.Nil(t, err) 896 resp2, err := http.DefaultClient.Do(req2) 897 require.Nil(t, err) 898 defer resp2.Body.Close() 899 require.Equal(t, http.StatusOK, resp2.StatusCode) 900 require.Nil(t, s.Close(nil)) 901 }