From b73684d9a6e6768bf97c4e3ba37609fb9eb5b34b Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 31 Jul 2020 12:13:30 +0800 Subject: [PATCH] move router to httpx --- rest/httpx/requests_test.go | 869 ------------------------ rest/httpx/router.go | 9 + rest/internal/router/patrouter.go | 9 +- rest/internal/router/patrouter_test.go | 872 ++++++++++++++++++++++++- rest/internal/router/router.go | 24 - rest/ngin.go | 4 +- rest/server.go | 9 +- 7 files changed, 895 insertions(+), 901 deletions(-) create mode 100644 rest/httpx/router.go delete mode 100644 rest/internal/router/router.go diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go index a6eb43a8..49ecaa7e 100644 --- a/rest/httpx/requests_test.go +++ b/rest/httpx/requests_test.go @@ -1,25 +1,15 @@ package httpx import ( - "bytes" - "fmt" - "io" "net/http" "net/http/httptest" "strconv" "strings" "testing" - "zero/rest/internal/router" - "github.com/stretchr/testify/assert" ) -const ( - applicationJsonWithUtf8 = "application/json; charset=utf-8" - contentLength = "Content-Length" -) - func TestParseForm(t *testing.T) { var v struct { Name string `form:"name"` @@ -119,865 +109,6 @@ func TestParseRequired(t *testing.T) { assert.NotNil(t, err) } -func TestParseSlice(t *testing.T) { - body := `names=%5B%22first%22%2C%22second%22%5D` - reader := strings.NewReader(body) - r, err := http.NewRequest(http.MethodPost, "http://hello.com/", reader) - assert.Nil(t, err) - r.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - rt := router.NewPatRouter() - err = rt.Handle(http.MethodPost, "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - v := struct { - Names []string `form:"names"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - assert.Equal(t, 2, len(v.Names)) - assert.Equal(t, "first", v.Names[0]) - assert.Equal(t, "second", v.Names[1]) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - rt.ServeHTTP(rr, r) -} - -func TestParseJsonPost(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", - bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func( - w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d:%s:%d", v.Name, v.Year, - v.Nickname, v.Zipcode, v.Location, v.Time)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017:whatever:200000:shanghai:20170912", rr.Body.String()) -} - -func TestParseJsonPostWithIntSlice(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", - bytes.NewBufferString(`{"ages": [1, 2], "years": [3, 4]}`)) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func( - w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Ages []int `json:"ages"` - Years []int64 `json:"years"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - assert.ElementsMatch(t, []int{1, 2}, v.Ages) - assert.ElementsMatch(t, []int64{3, 4}, v.Years) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseJsonPostError(t *testing.T) { - payload := `[{"abcd": "cdef"}]` - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", - bytes.NewBufferString(payload)) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseJsonPostInvalidRequest(t *testing.T) { - payload := `{"ages": ["cdef"]}` - r, err := http.NewRequest(http.MethodPost, "http://hello.com/", - bytes.NewBufferString(payload)) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Ages []int `json:"ages"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseJsonPostRequired(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", - bytes.NewBufferString(`{"location": "shanghai"`)) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParsePath(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s in %d", v.Name, v.Year)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin in 2017", rr.Body.String()) -} - -func TestParsePathRequired(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseQuery(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "whatever:200000", rr.Body.String()) -} - -func TestParseQueryRequired(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - v := struct { - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseOptional(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode,optional"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "whatever:0", rr.Body.String()) -} - -func TestParseNestedInRequestEmpty(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", bytes.NewBufferString("{}")) - assert.Nil(t, err) - - type ( - Request struct { - Name string `path:"name"` - Year int `path:"year"` - } - - Audio struct { - Volume int `json:"volume"` - } - - WrappedRequest struct { - Request - Audio Audio `json:"audio,optional"` - } - ) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - var v WrappedRequest - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017", rr.Body.String()) -} - -func TestParsePtrInRequest(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", - bytes.NewBufferString(`{"audio": {"volume": 100}}`)) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - - type ( - Request struct { - Name string `path:"name"` - Year int `path:"year"` - } - - Audio struct { - Volume int `json:"volume"` - } - - WrappedRequest struct { - Request - Audio *Audio `json:"audio,optional"` - } - ) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - var v WrappedRequest - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%d", v.Name, v.Year, v.Audio.Volume)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017:100", rr.Body.String()) -} - -func TestParsePtrInRequestEmpty(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin", bytes.NewBufferString("{}")) - assert.Nil(t, err) - - type ( - Audio struct { - Volume int `json:"volume"` - } - - WrappedRequest struct { - Audio *Audio `json:"audio,optional"` - } - ) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/kevin", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - var v WrappedRequest - err = Parse(r, &v) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseQueryOptional(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode,optional"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "whatever:0", rr.Body.String()) -} - -func TestParse(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d", v.Name, v.Year, v.Nickname, v.Zipcode)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017:whatever:200000", rr.Body.String()) -} - -func TestParseWrappedRequest(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) - assert.Nil(t, err) - - type ( - Request struct { - Name string `path:"name"` - Year int `path:"year"` - } - - WrappedRequest struct { - Request - } - ) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - var v WrappedRequest - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017", rr.Body.String()) -} - -func TestParseWrappedGetRequestWithJsonHeader(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) - assert.Nil(t, err) - r.Header.Set(ContentType, applicationJsonWithUtf8) - - type ( - Request struct { - Name string `path:"name"` - Year int `path:"year"` - } - - WrappedRequest struct { - Request - } - ) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - var v WrappedRequest - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017", rr.Body.String()) -} - -func TestParseWrappedHeadRequestWithJsonHeader(t *testing.T) { - r, err := http.NewRequest(http.MethodHead, "http://hello.com/kevin/2017", nil) - assert.Nil(t, err) - r.Header.Set(ContentType, applicationJsonWithUtf8) - - type ( - Request struct { - Name string `path:"name"` - Year int `path:"year"` - } - - WrappedRequest struct { - Request - } - ) - - router := router.NewPatRouter() - err = router.Handle(http.MethodHead, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - var v WrappedRequest - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017", rr.Body.String()) -} - -func TestParseWrappedRequestPtr(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) - assert.Nil(t, err) - - type ( - Request struct { - Name string `path:"name"` - Year int `path:"year"` - } - - WrappedRequest struct { - *Request - } - ) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - var v WrappedRequest - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017", rr.Body.String()) -} - -func TestParseWithAll(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", - bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d:%s:%d", v.Name, v.Year, - v.Nickname, v.Zipcode, v.Location, v.Time)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017:whatever:200000:shanghai:20170912", rr.Body.String()) -} - -func TestParseWithAllUtf8(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", - bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) - assert.Nil(t, err) - r.Header.Set(ContentType, applicationJsonWithUtf8) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d:%s:%d", v.Name, v.Year, - v.Nickname, v.Zipcode, v.Location, v.Time)) - assert.Nil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) - - assert.Equal(t, "kevin:2017:whatever:200000:shanghai:20170912", rr.Body.String()) -} - -func TestParseWithMissingForm(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever", - bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - assert.Equal(t, "field zipcode is not set", err.Error()) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseWithMissingAllForms(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", - bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseWithMissingJson(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", - bytes.NewBufferString(`{"location": "shanghai"}`)) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotEqual(t, io.EOF, err) - assert.NotNil(t, Parse(r, &v)) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseWithMissingAllJsons(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotEqual(t, io.EOF, err) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseWithMissingPath(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/2017?nickname=whatever&zipcode=200000", - bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - assert.Equal(t, "field name is not set", err.Error()) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseWithMissingAllPaths(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/?nickname=whatever&zipcode=200000", - bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) - assert.Nil(t, err) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseGetWithContentLengthHeader(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) - assert.Nil(t, err) - r.Header.Set(ContentType, ApplicationJson) - r.Header.Set(contentLength, "1024") - - router := router.NewPatRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Location string `json:"location"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseJsonPostWithTypeMismatch(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", - bytes.NewBufferString(`{"time": "20170912"}`)) - assert.Nil(t, err) - r.Header.Set(ContentType, applicationJsonWithUtf8) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode"` - Time int64 `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - -func TestParseJsonPostWithInt2String(t *testing.T) { - r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", - bytes.NewBufferString(`{"time": 20170912}`)) - assert.Nil(t, err) - r.Header.Set(ContentType, applicationJsonWithUtf8) - - router := router.NewPatRouter() - err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Name string `path:"name"` - Year int `path:"year"` - Time string `json:"time"` - }{} - - err = Parse(r, &v) - assert.NotNil(t, err) - })) - assert.Nil(t, err) - - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) -} - func BenchmarkParseRaw(b *testing.B) { r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil) if err != nil { diff --git a/rest/httpx/router.go b/rest/httpx/router.go new file mode 100644 index 00000000..11bbc9a8 --- /dev/null +++ b/rest/httpx/router.go @@ -0,0 +1,9 @@ +package httpx + +import "net/http" + +type Router interface { + http.Handler + Handle(method string, path string, handler http.Handler) error + SetNotFoundHandler(handler http.Handler) +} diff --git a/rest/internal/router/patrouter.go b/rest/internal/router/patrouter.go index 2c7fdd08..4db67273 100644 --- a/rest/internal/router/patrouter.go +++ b/rest/internal/router/patrouter.go @@ -1,11 +1,13 @@ package router import ( + "errors" "net/http" "path" "strings" "zero/core/search" + "zero/rest/httpx" "zero/rest/internal/context" ) @@ -14,12 +16,17 @@ const ( allowMethodSeparator = ", " ) +var ( + ErrInvalidMethod = errors.New("not a valid http method") + ErrInvalidPath = errors.New("path must begin with '/'") +) + type PatRouter struct { trees map[string]*search.Tree notFound http.Handler } -func NewPatRouter() Router { +func NewPatRouter() httpx.Router { return &PatRouter{ trees: make(map[string]*search.Tree), } diff --git a/rest/internal/router/patrouter_test.go b/rest/internal/router/patrouter_test.go index a7ad81f5..3bc716aa 100644 --- a/rest/internal/router/patrouter_test.go +++ b/rest/internal/router/patrouter_test.go @@ -1,12 +1,23 @@ package router import ( + "bytes" + "fmt" + "io" "net/http" + "net/http/httptest" + "strings" "testing" + "zero/rest/httpx" + "zero/rest/internal/context" + "github.com/stretchr/testify/assert" +) - "zero/rest/internal/context" +const ( + applicationJsonWithUtf8 = "application/json; charset=utf-8" + contentLength = "Content-Length" ) type mockedResponseWriter struct { @@ -108,6 +119,865 @@ func TestPatRouter(t *testing.T) { } } +func TestParseSlice(t *testing.T) { + body := `names=%5B%22first%22%2C%22second%22%5D` + reader := strings.NewReader(body) + r, err := http.NewRequest(http.MethodPost, "http://hello.com/", reader) + assert.Nil(t, err) + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rt := NewPatRouter() + err = rt.Handle(http.MethodPost, "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + v := struct { + Names []string `form:"names"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + assert.Equal(t, 2, len(v.Names)) + assert.Equal(t, "first", v.Names[0]) + assert.Equal(t, "second", v.Names[1]) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + rt.ServeHTTP(rr, r) +} + +func TestParseJsonPost(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", + bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func( + w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d:%s:%d", v.Name, v.Year, + v.Nickname, v.Zipcode, v.Location, v.Time)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017:whatever:200000:shanghai:20170912", rr.Body.String()) +} + +func TestParseJsonPostWithIntSlice(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", + bytes.NewBufferString(`{"ages": [1, 2], "years": [3, 4]}`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func( + w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Ages []int `json:"ages"` + Years []int64 `json:"years"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + assert.ElementsMatch(t, []int{1, 2}, v.Ages) + assert.ElementsMatch(t, []int64{3, 4}, v.Years) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseJsonPostError(t *testing.T) { + payload := `[{"abcd": "cdef"}]` + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", + bytes.NewBufferString(payload)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseJsonPostInvalidRequest(t *testing.T) { + payload := `{"ages": ["cdef"]}` + r, err := http.NewRequest(http.MethodPost, "http://hello.com/", + bytes.NewBufferString(payload)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Ages []int `json:"ages"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseJsonPostRequired(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", + bytes.NewBufferString(`{"location": "shanghai"`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParsePath(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s in %d", v.Name, v.Year)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin in 2017", rr.Body.String()) +} + +func TestParsePathRequired(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseQuery(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "whatever:200000", rr.Body.String()) +} + +func TestParseQueryRequired(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + v := struct { + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseOptional(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode,optional"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "whatever:0", rr.Body.String()) +} + +func TestParseNestedInRequestEmpty(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", bytes.NewBufferString("{}")) + assert.Nil(t, err) + + type ( + Request struct { + Name string `path:"name"` + Year int `path:"year"` + } + + Audio struct { + Volume int `json:"volume"` + } + + WrappedRequest struct { + Request + Audio Audio `json:"audio,optional"` + } + ) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var v WrappedRequest + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017", rr.Body.String()) +} + +func TestParsePtrInRequest(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", + bytes.NewBufferString(`{"audio": {"volume": 100}}`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + + type ( + Request struct { + Name string `path:"name"` + Year int `path:"year"` + } + + Audio struct { + Volume int `json:"volume"` + } + + WrappedRequest struct { + Request + Audio *Audio `json:"audio,optional"` + } + ) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var v WrappedRequest + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%d", v.Name, v.Year, v.Audio.Volume)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017:100", rr.Body.String()) +} + +func TestParsePtrInRequestEmpty(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin", bytes.NewBufferString("{}")) + assert.Nil(t, err) + + type ( + Audio struct { + Volume int `json:"volume"` + } + + WrappedRequest struct { + Audio *Audio `json:"audio,optional"` + } + ) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/kevin", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var v WrappedRequest + err = httpx.Parse(r, &v) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseQueryOptional(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode,optional"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "whatever:0", rr.Body.String()) +} + +func TestParse(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d", v.Name, v.Year, v.Nickname, v.Zipcode)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017:whatever:200000", rr.Body.String()) +} + +func TestParseWrappedRequest(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) + assert.Nil(t, err) + + type ( + Request struct { + Name string `path:"name"` + Year int `path:"year"` + } + + WrappedRequest struct { + Request + } + ) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var v WrappedRequest + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017", rr.Body.String()) +} + +func TestParseWrappedGetRequestWithJsonHeader(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, applicationJsonWithUtf8) + + type ( + Request struct { + Name string `path:"name"` + Year int `path:"year"` + } + + WrappedRequest struct { + Request + } + ) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var v WrappedRequest + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017", rr.Body.String()) +} + +func TestParseWrappedHeadRequestWithJsonHeader(t *testing.T) { + r, err := http.NewRequest(http.MethodHead, "http://hello.com/kevin/2017", nil) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, applicationJsonWithUtf8) + + type ( + Request struct { + Name string `path:"name"` + Year int `path:"year"` + } + + WrappedRequest struct { + Request + } + ) + + router := NewPatRouter() + err = router.Handle(http.MethodHead, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var v WrappedRequest + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017", rr.Body.String()) +} + +func TestParseWrappedRequestPtr(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil) + assert.Nil(t, err) + + type ( + Request struct { + Name string `path:"name"` + Year int `path:"year"` + } + + WrappedRequest struct { + *Request + } + ) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var v WrappedRequest + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Name, v.Year)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017", rr.Body.String()) +} + +func TestParseWithAll(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", + bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d:%s:%d", v.Name, v.Year, + v.Nickname, v.Zipcode, v.Location, v.Time)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017:whatever:200000:shanghai:20170912", rr.Body.String()) +} + +func TestParseWithAllUtf8(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", + bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, applicationJsonWithUtf8) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d:%s:%d:%s:%d", v.Name, v.Year, + v.Nickname, v.Zipcode, v.Location, v.Time)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "kevin:2017:whatever:200000:shanghai:20170912", rr.Body.String()) +} + +func TestParseWithMissingForm(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever", + bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + assert.Equal(t, "field zipcode is not set", err.Error()) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseWithMissingAllForms(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", + bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseWithMissingJson(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", + bytes.NewBufferString(`{"location": "shanghai"}`)) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotEqual(t, io.EOF, err) + assert.NotNil(t, httpx.Parse(r, &v)) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseWithMissingAllJsons(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotEqual(t, io.EOF, err) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseWithMissingPath(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/2017?nickname=whatever&zipcode=200000", + bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + assert.Equal(t, "field name is not set", err.Error()) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseWithMissingAllPaths(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/?nickname=whatever&zipcode=200000", + bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`)) + assert.Nil(t, err) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseGetWithContentLengthHeader(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, httpx.ApplicationJson) + r.Header.Set(contentLength, "1024") + + router := NewPatRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Location string `json:"location"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseJsonPostWithTypeMismatch(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", + bytes.NewBufferString(`{"time": "20170912"}`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, applicationJsonWithUtf8) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Nickname string `form:"nickname"` + Zipcode int64 `form:"zipcode"` + Time int64 `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + +func TestParseJsonPostWithInt2String(t *testing.T) { + r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017", + bytes.NewBufferString(`{"time": 20170912}`)) + assert.Nil(t, err) + r.Header.Set(httpx.ContentType, applicationJsonWithUtf8) + + router := NewPatRouter() + err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Name string `path:"name"` + Year int `path:"year"` + Time string `json:"time"` + }{} + + err = httpx.Parse(r, &v) + assert.NotNil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) +} + func BenchmarkPatRouter(b *testing.B) { b.ReportAllocs() diff --git a/rest/internal/router/router.go b/rest/internal/router/router.go deleted file mode 100644 index cbf1c3ec..00000000 --- a/rest/internal/router/router.go +++ /dev/null @@ -1,24 +0,0 @@ -package router - -import ( - "errors" - "net/http" -) - -var ( - ErrInvalidMethod = errors.New("not a valid http method") - ErrInvalidPath = errors.New("path must begin with '/'") -) - -type ( - Route struct { - Path string - Handler http.HandlerFunc - } - - Router interface { - http.Handler - Handle(method string, path string, handler http.Handler) error - SetNotFoundHandler(handler http.Handler) - } -) diff --git a/rest/ngin.go b/rest/ngin.go index b70089ea..1f37e718 100644 --- a/rest/ngin.go +++ b/rest/ngin.go @@ -6,7 +6,7 @@ import ( "zero/core/logx" "zero/rest/handler" - "zero/rest/internal/router" + "zero/rest/httpx" ) type ( @@ -124,7 +124,7 @@ func WithPriority() RouteOption { } } -func WithRouter(router router.Router) RunOption { +func WithRouter(router httpx.Router) RunOption { return func(server *Server) { server.opts.start = func(srv *engine) error { return srv.StartWithRouter(router) diff --git a/rest/server.go b/rest/server.go index 8818c5fc..48c6e283 100644 --- a/rest/server.go +++ b/rest/server.go @@ -10,6 +10,7 @@ import ( "zero/core/load" "zero/core/stat" "zero/rest/handler" + "zero/rest/httpx" "zero/rest/internal" "zero/rest/internal/router" @@ -60,7 +61,7 @@ func (s *engine) Start() error { return s.StartWithRouter(router.NewPatRouter()) } -func (s *engine) StartWithRouter(router router.Router) error { +func (s *engine) StartWithRouter(router httpx.Router) error { if err := s.bindRoutes(router); err != nil { return err } @@ -84,7 +85,7 @@ func (s *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain, return verifier(chain) } -func (s *engine) bindFeaturedRoutes(router router.Router, fr featuredRoutes, metrics *stat.Metrics) error { +func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error { verifier, err := s.signatureVerifier(fr.signature) if err != nil { return err @@ -99,7 +100,7 @@ func (s *engine) bindFeaturedRoutes(router router.Router, fr featuredRoutes, met return nil } -func (s *engine) bindRoute(fr featuredRoutes, router router.Router, metrics *stat.Metrics, +func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics, route Route, verifier func(chain alice.Chain) alice.Chain) error { chain := alice.New( handler.TracingHandler, @@ -124,7 +125,7 @@ func (s *engine) bindRoute(fr featuredRoutes, router router.Router, metrics *sta return router.Handle(route.Method, route.Path, handle) } -func (s *engine) bindRoutes(router router.Router) error { +func (s *engine) bindRoutes(router httpx.Router) error { metrics := s.createMetrics() for _, fr := range s.routes {