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  }