diff --git a/Gopkg.lock b/Gopkg.lock index 8a6d864..1c8c101 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -9,7 +9,7 @@ [[projects]] name = "github.com/kelseyhightower/envconfig" packages = ["."] - revision = "70f0258d44cbaa3b6a2581d82f58da01a38e4de4" + revision = "462fda1f11d8cad3660e52737b8beefd27acfb3f" [[projects]] name = "github.com/rs/xhandler" @@ -34,6 +34,12 @@ revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" version = "v1.0.3" +[[projects]] + name = "github.com/takama/bit" + packages = ["."] + revision = "a8e9c17d8de050aec0aa8b55a507eeaa08741320" + version = "0.2.3" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -55,6 +61,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e59ccb0cbb49d6ee79d6526fbc5be075343a1f24c46c3aa2c74d1e6c89f09ece" + inputs-digest = "cd21dc84b9fce104ffaab4872754cf3bd2b46c6ffa5bf128294af36c542d4c1d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 1e242ce..a0f78e1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -35,4 +35,8 @@ [[constraint]] name = "github.com/kelseyhightower/envconfig" - revision = "70f0258d44cbaa3b6a2581d82f58da01a38e4de4" + revision = "462fda1f11d8cad3660e52737b8beefd27acfb3f" + +[[constraint]] + name = "github.com/takama/bit" + version = "0.2.3" diff --git a/Makefile b/Makefile index 02cbc65..38bc0f2 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ REGISTRY?=docker.io/takama CA_DIR?=certs # Use the 0.0.0 tag for testing, it shouldn't clobber any release builds -RELEASE?=0.4.5 +RELEASE?=0.4.6 GOOS?=linux GOARCH?=amd64 diff --git a/charts/Chart.yaml b/charts/Chart.yaml index 297cac2..fbf5109 100755 --- a/charts/Chart.yaml +++ b/charts/Chart.yaml @@ -1,6 +1,6 @@ name: k8sapp description: A Helm charts for Kubernetes application -version: 0.4.5 +version: 0.4.6 home: https://my.domain/ sources: - https://github.com/takama/k8sapp diff --git a/charts/values-dev.yaml b/charts/values-dev.yaml index b3f7970..2fa1e69 100644 --- a/charts/values-dev.yaml +++ b/charts/values-dev.yaml @@ -36,7 +36,7 @@ image: ## registry: docker.io/takama name: k8sapp - tag: 0.4.5 + tag: 0.4.6 ## Docker Registry/Hub auth secret name, always use `registry-pull-secret` if registry inside if k8s ## diff --git a/charts/values-stable.yaml b/charts/values-stable.yaml index fce2659..335521c 100644 --- a/charts/values-stable.yaml +++ b/charts/values-stable.yaml @@ -36,7 +36,7 @@ image: ## registry: docker.io/takama name: k8sapp - tag: 0.4.5 + tag: 0.4.6 ## Docker Registry/Hub auth secret name, always use `registry-pull-secret` if registry inside if k8s ## diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c3ac26b..9263cb3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,7 +1,14 @@ -# Version 0.4.5 +# Version 0.4.6 [Documentation](README.md) + +## Changelog since 0.4.5 + +### Codebase + +- Released #34: move embedded http router into external package ([#39](https://github.com/takama/k8sapp/pull/39), [@takama](https://github.com/takama)) + ## Changelog since 0.4.4 ### Codebase diff --git a/pkg/handlers/handler.go b/pkg/handlers/handler.go index 2f69f59..309e903 100644 --- a/pkg/handlers/handler.go +++ b/pkg/handlers/handler.go @@ -9,9 +9,11 @@ import ( "net/http" "time" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/config" "github.com/takama/k8sapp/pkg/logger" - "github.com/takama/k8sapp/pkg/router" "github.com/takama/k8sapp/pkg/version" ) @@ -45,8 +47,8 @@ func New(logger logger.Logger, config *config.Config) *Handler { } // Base handler implements middleware logic -func (h *Handler) Base(handle func(router.Control)) func(router.Control) { - return func(c router.Control) { +func (h *Handler) Base(handle func(bit.Control)) func(bit.Control) { + return func(c bit.Control) { timer := time.Now() handle(c) h.countDuration(timer) @@ -55,7 +57,7 @@ func (h *Handler) Base(handle func(router.Control)) func(router.Control) { } // Root handler shows version -func (h *Handler) Root(c router.Control) { +func (h *Handler) Root(c bit.Control) { c.Code(http.StatusOK) c.Body(fmt.Sprintf("%s v%s", config.SERVICENAME, version.RELEASE)) } @@ -75,7 +77,7 @@ func (h *Handler) countDuration(timer time.Time) { } } -func (h *Handler) collectCodes(c router.Control) { +func (h *Handler) collectCodes(c bit.Control) { if c.GetCode() >= 500 { h.stats.requests.Codes.C5xx++ } else { diff --git a/pkg/handlers/handler_test.go b/pkg/handlers/handler_test.go index 5b53220..b9ff179 100644 --- a/pkg/handlers/handler_test.go +++ b/pkg/handlers/handler_test.go @@ -6,18 +6,19 @@ import ( "net/http/httptest" "testing" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/config" "github.com/takama/k8sapp/pkg/logger" "github.com/takama/k8sapp/pkg/logger/standard" - "github.com/takama/k8sapp/pkg/router" - "github.com/takama/k8sapp/pkg/router/bitroute" "github.com/takama/k8sapp/pkg/version" ) func TestRoot(t *testing.T) { h := New(standard.New(&logger.Config{}), new(config.Config)) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.Base(h.Root)(bitroute.NewControl(w, r)) + h.Base(h.Root)(bit.NewControl(w, r)) }) testHandler(t, handler, http.StatusOK, fmt.Sprintf("%s v%s", config.SERVICENAME, version.RELEASE)) @@ -43,18 +44,18 @@ func testHandler(t *testing.T, handler http.HandlerFunc, code int, body string) func TestCollectCodes(t *testing.T) { h := New(standard.New(&logger.Config{}), new(config.Config)) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.Base(func(c router.Control) { + h.Base(func(c bit.Control) { c.Code(http.StatusBadGateway) c.Body(http.StatusText(http.StatusBadGateway)) - })(bitroute.NewControl(w, r)) + })(bit.NewControl(w, r)) }) testHandler(t, handler, http.StatusBadGateway, http.StatusText(http.StatusBadGateway)) handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.Base(func(c router.Control) { + h.Base(func(c bit.Control) { c.Code(http.StatusNotFound) c.Body(http.StatusText(http.StatusNotFound)) - })(bitroute.NewControl(w, r)) + })(bit.NewControl(w, r)) }) testHandler(t, handler, http.StatusNotFound, http.StatusText(http.StatusNotFound)) } diff --git a/pkg/handlers/health.go b/pkg/handlers/health.go index 57bcc80..86ae64a 100644 --- a/pkg/handlers/health.go +++ b/pkg/handlers/health.go @@ -7,11 +7,13 @@ package handlers import ( "net/http" - "github.com/takama/k8sapp/pkg/router" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" ) // Health returns "OK" if service is alive -func (h *Handler) Health(c router.Control) { +func (h *Handler) Health(c bit.Control) { c.Code(http.StatusOK) c.Body(http.StatusText(http.StatusOK)) } diff --git a/pkg/handlers/health_test.go b/pkg/handlers/health_test.go index e7ad060..48fb7ac 100644 --- a/pkg/handlers/health_test.go +++ b/pkg/handlers/health_test.go @@ -4,16 +4,18 @@ import ( "net/http" "testing" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/config" "github.com/takama/k8sapp/pkg/logger" "github.com/takama/k8sapp/pkg/logger/standard" - "github.com/takama/k8sapp/pkg/router/bitroute" ) func TestHealth(t *testing.T) { h := New(standard.New(&logger.Config{}), new(config.Config)) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.Base(h.Health)(bitroute.NewControl(w, r)) + h.Base(h.Health)(bit.NewControl(w, r)) }) testHandler(t, handler, http.StatusOK, http.StatusText(http.StatusOK)) diff --git a/pkg/handlers/info.go b/pkg/handlers/info.go index 91606be..615aa4e 100644 --- a/pkg/handlers/info.go +++ b/pkg/handlers/info.go @@ -11,7 +11,9 @@ import ( "runtime" "time" - "github.com/takama/k8sapp/pkg/router" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/version" ) @@ -60,7 +62,7 @@ type Codes struct { } // Info returns detailed info about the service -func (h *Handler) Info(c router.Control) { +func (h *Handler) Info(c bit.Control) { host, _ := os.Hostname() m := new(runtime.MemStats) runtime.ReadMemStats(m) diff --git a/pkg/handlers/info_test.go b/pkg/handlers/info_test.go index 7926b70..f440c83 100644 --- a/pkg/handlers/info_test.go +++ b/pkg/handlers/info_test.go @@ -6,17 +6,19 @@ import ( "net/http/httptest" "testing" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/config" "github.com/takama/k8sapp/pkg/logger" "github.com/takama/k8sapp/pkg/logger/standard" - "github.com/takama/k8sapp/pkg/router/bitroute" "github.com/takama/k8sapp/pkg/version" ) func TestInfo(t *testing.T) { h := New(standard.New(&logger.Config{}), new(config.Config)) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.Base(h.Info)(bitroute.NewControl(w, r)) + h.Base(h.Info)(bit.NewControl(w, r)) }) req, err := http.NewRequest("GET", "/", nil) diff --git a/pkg/handlers/ready.go b/pkg/handlers/ready.go index 8f8ea9f..1c2d758 100644 --- a/pkg/handlers/ready.go +++ b/pkg/handlers/ready.go @@ -7,11 +7,13 @@ package handlers import ( "net/http" - "github.com/takama/k8sapp/pkg/router" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" ) // Ready returns "OK" if service is ready to serve traffic -func (h *Handler) Ready(c router.Control) { +func (h *Handler) Ready(c bit.Control) { // TODO: possible use cases: // load data from a database, a message broker, any external services, etc diff --git a/pkg/handlers/ready_test.go b/pkg/handlers/ready_test.go index 9089342..3d5b359 100644 --- a/pkg/handlers/ready_test.go +++ b/pkg/handlers/ready_test.go @@ -4,16 +4,18 @@ import ( "net/http" "testing" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/config" "github.com/takama/k8sapp/pkg/logger" "github.com/takama/k8sapp/pkg/logger/standard" - "github.com/takama/k8sapp/pkg/router/bitroute" ) func TestReady(t *testing.T) { h := New(standard.New(&logger.Config{}), new(config.Config)) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.Base(h.Ready)(bitroute.NewControl(w, r)) + h.Base(h.Ready)(bit.NewControl(w, r)) }) testHandler(t, handler, http.StatusOK, http.StatusText(http.StatusOK)) diff --git a/pkg/router/bitroute.go b/pkg/router/bitroute.go deleted file mode 100644 index 775cc2e..0000000 --- a/pkg/router/bitroute.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2017 Igor Dolzhikov. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package router - -import "net/http" - -// Control interface contains methods that control -// HTTP header, URL/post query parameters, request/response -// and HTTP output like Code(), Write(), etc. -type Control interface { - // Request returns *http.Request - Request() *http.Request - - // Query searches URL/Post query parameters by key. - // If there are no values associated with the key, an empty string is returned. - Query(key string) string - - // Param sets URL/Post key/value query parameters. - Param(key, value string) - - // Code sets HTTP status code e.g. http.StatusOk - Code(code int) - - // GetCode shows HTTP status code that set by Code() - GetCode() int - - // Body writes prepared header, status code and body data into http output. - // It is equal to using sequence of http.ResponseWriter methods: - // WriteHeader(code int) and Write(b []byte) int, error - Body(data interface{}) - - // Embedded response writer - http.ResponseWriter - - // TODO Add more control methods. -} - -// BitRoute interface contains base http methods e.g. GET, PUT, POST -// and defines your own handlers that is useful in some use cases -type BitRoute interface { - // Standard methods - - // GET registers a new request handle for HTTP GET method. - GET(path string, f func(Control)) - // PUT registers a new request handle for HTTP PUT method. - PUT(path string, f func(Control)) - // POST registers a new request handle for HTTP POST method. - POST(path string, f func(Control)) - // DELETE registers a new request handle for HTTP DELETE method. - DELETE(path string, f func(Control)) - // HEAD registers a new request handle for HTTP HEAD method. - HEAD(path string, f func(Control)) - // OPTIONS registers a new request handle for HTTP OPTIONS method. - OPTIONS(path string, f func(Control)) - // PATCH registers a new request handle for HTTP PATCH method. - PATCH(path string, f func(Control)) - - // User defined options and handlers - - // If enabled, the router automatically replies to OPTIONS requests. - // Nevertheless OPTIONS handlers take priority over automatic replies. - // By default this option is disabled - UseOptionsReplies(bool) - - // SetupNotAllowedHandler defines own handler which is called when a request - // cannot be routed. - SetupNotAllowedHandler(func(Control)) - - // SetupNotFoundHandler allows to define own handler for undefined URL path. - // If it is not set, http.NotFound is used. - SetupNotFoundHandler(func(Control)) - - // SetupRecoveryHandler allows to define handler that called when panic happen. - // The handler prevents your server from crashing and should be used to return - // http status code http.StatusInternalServerError (500) - SetupRecoveryHandler(func(Control)) - - // SetupMiddleware defines handler that is allowed to take control - // before it is called standard methods above e.g. GET, PUT. - SetupMiddleware(func(func(Control)) func(Control)) - - // Listen and serve on requested host and port e.g "0.0.0.0:8080" - Listen(hostPort string) error -} diff --git a/pkg/router/bitroute/control.go b/pkg/router/bitroute/control.go deleted file mode 100644 index 4584e98..0000000 --- a/pkg/router/bitroute/control.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2017 Igor Dolzhikov. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package bitroute - -import ( - "compress/gzip" - "encoding/json" - "net/http" - "strings" - - "github.com/takama/k8sapp/pkg/router" -) - -type control struct { - req *http.Request - w http.ResponseWriter - code int - params []struct { - key string - value string - } -} - -// NewControl returns new control that implement Control interface. -func NewControl(w http.ResponseWriter, req *http.Request) router.Control { - return &control{ - req: req, - w: w, - } -} - -// Request returns *http.Request -func (c *control) Request() *http.Request { - return c.req -} - -// Response writer implementation -// Header represents http.ResponseWriter header, the key-value pairs in an HTTP header. -func (c *control) Header() http.Header { - return c.w.Header() -} - -// Write writes the data to the connection as part of an HTTP reply. -func (c *control) Write(b []byte) (int, error) { - return c.w.Write(b) -} - -// WriteHeader sends an HTTP response header with status code. -func (c *control) WriteHeader(code int) { - c.w.WriteHeader(code) -} - -// Query searches URL/Post value by key. -// If there are no values associated with the key, an empty string is returned. -func (c *control) Query(key string) string { - for idx := range c.params { - if c.params[idx].key == key { - return c.params[idx].value - } - } - - return c.req.URL.Query().Get(key) -} - -// Param sets URL/Post key/value params. -func (c *control) Param(key, value string) { - c.params = append(c.params, struct{ key, value string }{key: key, value: value}) -} - -// Code sets HTTP status code e.g. http.StatusOk -func (c *control) Code(code int) { - if code >= 100 && code < 600 { - c.code = code - } -} - -// GetCode shows HTTP status code that set by Code() -func (c *control) GetCode() int { - return c.code -} - -// Body writes prepared header, status code and body data into http output. -// It is equal to using sequence of http.ResponseWriter methods: -// WriteHeader(code int) and Write(b []byte) int, error -func (c *control) Body(data interface{}) { - var content []byte - - if str, ok := data.(string); ok { - content = []byte(str) - } else { - var err error - content, err = json.Marshal(data) - if err != nil { - c.w.WriteHeader(http.StatusInternalServerError) - c.w.Write([]byte(err.Error())) - return - } - if c.w.Header().Get("Content-type") == "" { - c.w.Header().Add("Content-type", "application/json") - } - } - if strings.Contains(c.req.Header.Get("Accept-Encoding"), "gzip") { - // Detect content type before encoding if it isn't defined - if c.w.Header().Get("Content-type") == "" { - c.w.Header().Set("Content-type", http.DetectContentType(content)) - } - c.w.Header().Add("Content-Encoding", "gzip") - if c.code > 0 { - c.w.WriteHeader(c.code) - } - gz := gzip.NewWriter(c.w) - gz.Write(content) - gz.Close() - } else { - if c.code > 0 { - c.w.WriteHeader(c.code) - } - c.w.Write(content) - } -} diff --git a/pkg/router/bitroute/control_test.go b/pkg/router/bitroute/control_test.go deleted file mode 100644 index 5ddfa5d..0000000 --- a/pkg/router/bitroute/control_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package bitroute - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -type prm struct { - Key, Value string -} - -var params = []prm{ - {"name", "John"}, - {"age", "32"}, - {"gender", "M"}, -} - -var testParamsData = `[{"Key":"name","Value":"John"},{"Key":"age","Value":"32"},{"Key":"gender","Value":"M"}]` -var testParamGzipData = []byte{ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 138, 174, 86, 242, 78, 173, 84, 178, 82, 202, - 75, 204, 77, 85, 210, 81, 10, 75, 204, 41, 77, 85, 178, 82, 242, 202, 207, 200, - 83, 170, 213, 129, 201, 38, 166, 35, 75, 26, 27, 33, 73, 165, 167, 230, 165, 164, - 22, 33, 201, 250, 42, 213, 198, 2, 2, 0, 0, 255, 255, 196, 73, 247, 37, 87, 0, 0, 0, -} -var testStrData = "plain text" -var testStrGzipData = []byte{ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 42, 200, 73, 204, 204, 83, 40, - 73, 173, 40, 1, 4, 0, 0, 255, 255, 184, 15, 11, 68, 10, 0, 0, 0, -} - -func TestParamsQueryGet(t *testing.T) { - - c := new(control) - for _, param := range params { - c.Param(param.Key, param.Value) - } - for _, param := range params { - value := c.Query(param.Key) - if value != param.Value { - t.Error("Expected for", param.Key, ":", param.Value, ", got", value) - } - } -} - -func TestWriterHeader(t *testing.T) { - req, err := http.NewRequest("GET", "hello/:name", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - c := NewControl(trw, req) - request := c.Request() - if request != req { - t.Error("Expected", req.URL.String(), "got", request.URL) - } - trw.Header().Add("Test", "TestValue") - c = NewControl(trw, req) - expected := trw.Header().Get("Test") - value := c.Header().Get("Test") - if value != expected { - t.Error("Expected", expected, "got", value) - } -} - -func TestWriterCode(t *testing.T) { - c := new(control) - // code transcends, must be less than 600 - c.Code(777) - if c.code != 0 { - t.Error("Expected code", "0", "got", c.code) - } - c.Code(404) - if c.code != 404 { - t.Error("Expected code", "404", "got", c.code) - } -} - -func TestGetCode(t *testing.T) { - c := new(control) - c.Code(http.StatusOK) - code := c.GetCode() - if code != http.StatusOK { - t.Error("Expected code", http.StatusText(http.StatusOK), "got", code) - } -} - -func TestWrite(t *testing.T) { - req, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Error(err) - } - - // Write data using http.ResponseWriter - trw := httptest.NewRecorder() - c := NewControl(trw, req) - c.WriteHeader(http.StatusAccepted) - c.Write([]byte("Testing")) - if trw.Code != http.StatusAccepted { - t.Error("Expected", http.StatusAccepted, "got", trw.Code) - } - if trw.Body.String() != "Testing" { - t.Error("Expected", "Testing", "got", trw.Body) - } - - // Write plain text data - trw = httptest.NewRecorder() - c = NewControl(trw, req) - c.Body("Hello") - if trw.Body.String() != "Hello" { - t.Error("Expected", "Hello", "got", trw.Body) - } - contentType := trw.Header().Get("Content-type") - expected := "text/plain; charset=utf-8" - if contentType != expected { - t.Error("Expected", expected, "got", contentType) - } - - // Write JSON compatible data - trw = httptest.NewRecorder() - c = NewControl(trw, req) - c.Code(http.StatusOK) - c.Body(params) - if trw.Body.String() != testParamsData { - t.Error("Expected", testParamsData, "got", trw.Body) - } - contentType = trw.Header().Get("Content-type") - expected = "application/json" - if contentType != expected { - t.Error("Expected", expected, "got", contentType) - } - - // Write encoded string - req.Header.Add("Accept-Encoding", "gzip, deflate") - trw = httptest.NewRecorder() - c = NewControl(trw, req) - c.Code(http.StatusAccepted) - c.Body(testStrData) - if trw.Body.String() != string(testStrGzipData) { - t.Error("Expected", testStrGzipData, "got", trw.Body) - } - contentEncoding := trw.Header().Get("Content-Encoding") - expected = "gzip" - if contentEncoding != expected { - t.Error("Expected", expected, "got", contentEncoding) - } - - // Write encoded struct data - req.Header.Add("Accept-Encoding", "gzip, deflate") - trw = httptest.NewRecorder() - c = NewControl(trw, req) - c.Code(http.StatusAccepted) - c.Body(params) - if trw.Body.String() != string(testParamGzipData) { - t.Error("Expected", testParamGzipData, "got", trw.Body) - } - contentEncoding = trw.Header().Get("Content-Encoding") - expected = "gzip" - if contentEncoding != expected { - t.Error("Expected", expected, "got", contentEncoding) - } - - // Try to write unexpected data type - trw = httptest.NewRecorder() - c = NewControl(trw, req) - c.Body(func() {}) - if trw.Code != http.StatusInternalServerError { - t.Error("Expected", http.StatusInternalServerError, "got", trw.Code) - } - expected = "application/json" - if contentType != expected { - t.Error("Expected", expected, "got", contentType) - } -} diff --git a/pkg/router/bitroute/parser.go b/pkg/router/bitroute/parser.go deleted file mode 100644 index 1029091..0000000 --- a/pkg/router/bitroute/parser.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2017 Igor Dolzhikov. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package bitroute - -import ( - "sort" - - "github.com/takama/k8sapp/pkg/router" -) - -const ( - maxLevel = 255 - asterisk = "*" -) - -type handle func(router.Control) - -type parser struct { - fields map[uint8]records - static map[string]handle - wildcard records -} - -type record struct { - key uint16 - handle handle - parts []string -} - -type param struct { - key string - value string -} -type records []*record - -func (n records) Len() int { return len(n) } -func (n records) Swap(i, j int) { n[i], n[j] = n[j], n[i] } -func (n records) Less(i, j int) bool { return n[i].key < n[j].key } - -func newParser() *parser { - return &parser{ - fields: make(map[uint8]records), - static: make(map[string]handle), - wildcard: records{}, - } -} - -func (p *parser) register(path string, h handle) bool { - if trim(path, " ") == asterisk { - p.static[asterisk] = h - - return true - } - if parts, ok := split(path); ok { - var static, dynamic, wildcard uint16 - for _, value := range parts { - if len(value) >= 1 && value[0:1] == ":" { - dynamic++ - } else if len(value) == 1 && value == "*" { - wildcard++ - } else { - static++ - } - } - if wildcard > 0 { - p.wildcard = append(p.wildcard, &record{key: dynamic<<8 + static, handle: h, parts: parts}) - } else if dynamic == 0 { - p.static["/"+join(parts)] = h - } else { - level := uint8(len(parts)) - p.fields[level] = append(p.fields[level], &record{key: dynamic<<8 + static, handle: h, parts: parts}) - sort.Sort(records(p.fields[level])) - } - return true - } - - return false -} - -func (p *parser) get(path string) (h handle, result []param, ok bool) { - if h, ok := p.static[asterisk]; ok { - return h, nil, true - } - if h, ok := p.static[path]; ok { - return h, nil, true - } - if parts, ok := split(path); ok { - if h, ok := p.static["/"+join(parts)]; ok { - return h, nil, true - } - if data := p.fields[uint8(len(parts))]; data != nil { - if h, result, ok := parseParams(data, parts); ok { - return h, result, ok - } - } - // try to match wildcard route - if h, result, ok := parseParams(p.wildcard, parts); ok { - return h, result, ok - } - } - - return nil, nil, false -} - -func split(path string) ([]string, bool) { - sdata := explode(trim(path, "/")) - if len(sdata) == 0 { - return sdata, true - } - var result []string - ind := 0 - if len(sdata) < maxLevel { - result = make([]string, len(sdata)) - for _, value := range sdata { - if v := trim(value, " "); v == "" { - continue - } else { - result[ind] = v - ind++ - } - } - return result[0:ind], true - } - - return nil, false -} - -func trim(str, sep string) string { - result := str - for { - if len(result) >= 1 && result[0:1] == sep { - result = result[1:] - } else { - break - } - } - for { - if len(result) >= 1 && result[len(result)-1:] == sep { - result = result[:len(result)-1] - } else { - break - } - } - return result -} - -func join(parts []string) string { - if len(parts) == 0 { - return "" - } - if len(parts) == 1 { - return parts[0] - } - n := len(parts) - 1 - for i := 0; i < len(parts); i++ { - n += len(parts[i]) - } - - b := make([]byte, n) - bp := copy(b, parts[0]) - for _, s := range parts[1:] { - bp += copy(b[bp:], "/") - bp += copy(b[bp:], s) - } - return string(b) -} - -func explode(s string) []string { - if len(s) == 0 { - return []string{} - } - n := 1 - sep := "/" - c := sep[0] - for i := 0; i < len(s); i++ { - if s[i] == c { - n++ - } - } - start := 0 - a := make([]string, n) - na := 0 - for i := 0; i+1 <= len(s) && na+1 < n; i++ { - if s[i] == c { - a[na] = s[start:i] - na++ - start = i + 1 - } - } - a[na] = s[start:] - return a[0 : na+1] -} - -func parseParams(data records, parts []string) (h handle, result []param, ok bool) { - for _, nds := range data { - values := nds.parts - result = nil - found := true - for idx, value := range values { - if len(value) == 1 && value == "*" { - break - } else if value != parts[idx] && !(len(value) >= 1 && value[0:1] == ":") { - found = false - break - } else { - if len(value) >= 1 && value[0:1] == ":" { - result = append(result, param{key: value, value: parts[idx]}) - } - } - } - if found { - return nds.handle, result, true - } - } - - return nil, nil, false -} - -func (p *parser) routes() []string { - var rs []string - for path := range p.static { - rs = append(rs, path) - } - for _, records := range p.fields { - for _, record := range records { - rs = append(rs, "/"+join(record.parts)) - } - } - for _, record := range p.wildcard { - rs = append(rs, "/"+join(record.parts)) - } - - return rs -} diff --git a/pkg/router/bitroute/parser_test.go b/pkg/router/bitroute/parser_test.go deleted file mode 100644 index b53c422..0000000 --- a/pkg/router/bitroute/parser_test.go +++ /dev/null @@ -1,331 +0,0 @@ -package bitroute - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/takama/k8sapp/pkg/router" -) - -type registered struct { - path string - h handle -} - -type expected struct { - request string - data string - paramCount int - params []param -} - -var setOfRegistered = []registered{ - { - "/hello/John", - func(c router.Control) { - c.Body("Hello from static path") - }, - }, - { - "/hello/:name", - func(c router.Control) { - c.Body("Hello " + c.Query(":name")) - }, - }, - { - "/:h/:n", - func(c router.Control) { - c.Body(c.Query(":n") + " from " + c.Query(":h")) - }, - }, - { - "/products/book/orders/:id", - func(c router.Control) { - c.Body("Product: book order# " + c.Query(":id")) - }, - }, - { - "/products/:name/orders/:id", - func(c router.Control) { - c.Body("Product: " + c.Query(":name") + " order# " + c.Query(":id")) - }, - }, - { - "/products/:name/:order/:id", - func(c router.Control) { - c.Body("Product: " + c.Query(":name") + " # " + c.Query(":id")) - }, - }, - { - "/:product/:name/:order/:id", - func(c router.Control) { - c.Body(c.Query(":product") + " " + c.Query(":name") + " " + c.Query(":order") + " # " + c.Query(":id")) - }, - }, - { - "/static/*", - func(c router.Control) { - c.Body("Hello from star static path") - }, - }, - { - "/files/:dir/*", - func(c router.Control) { - c.Body(c.Query(":dir")) - }, - }, -} - -var setOfExpected = []expected{ - { - "/hello/John", - "Hello from static path", - 0, - []param{}, - }, - { - "/hello/Jane", - "Hello Jane", - 1, - []param{ - {":name", "Jane"}, - }, - }, - { - "/hell/jack", - "jack from hell", - 2, - []param{ - {":h", "hell"}, - {":n", "jack"}, - }, - }, - { - "/products/book/orders/12", - "Product: book order# 12", - 1, - []param{ - {":id", "12"}, - }, - }, - { - "/products/table/orders/23", - "Product: table order# 23", - 2, - []param{ - {":name", "table"}, - {":id", "23"}, - }, - }, - { - "/products/pen/orders/11", - "Product: pen order# 11", - 2, - []param{ - {":name", "pen"}, - {":id", "11"}, - }, - }, - { - "/products/pen/order/10", - "Product: pen # 10", - 3, - []param{ - {":name", "pen"}, - {":order", "order"}, - {":id", "10"}, - }, - }, - { - "/product/pen/order/10", - "product pen order # 10", - 4, - []param{ - {":product", "product"}, - {":name", "pen"}, - {":order", "order"}, - {":id", "10"}, - }, - }, - { - "/static/greetings/something", - "Hello from star static path", - 0, - []param{}, - }, - { - "/files/css/style.css", - "css", - 1, - []param{ - {":dir", "css"}, - }, - }, - { - "/files/js/app.js", - "js", - 1, - []param{ - {":dir", "js"}, - }, - }, -} - -func TestParserRegisterGet(t *testing.T) { - p := newParser() - for _, request := range setOfRegistered { - p.register(request.path, request.h) - } - for _, exp := range setOfExpected { - h, params, ok := p.get(exp.request) - if !ok { - t.Error("Error: get data for path", exp.request) - } - if len(params) != exp.paramCount { - t.Error("Expected length of param", exp.paramCount, "got", len(params)) - } - trw := httptest.NewRecorder() - req, err := http.NewRequest("GET", "", nil) - if err != nil { - t.Error("Error creating new request") - } - c := NewControl(trw, req) - for _, item := range params { - c.Param(item.key, item.value) - } - h(c) - if trw.Body.String() != exp.data { - t.Error("Expected", exp.data, "got", trw.Body.String()) - } - } -} - -func TestParserSplit(t *testing.T) { - path := []string{ - "/api/v1/module", - "/api//v1/module/", - "/module///name//", - "module//:name", - "/:param1/:param2/", - strings.Repeat("/A", 300), - } - expected := [][]string{ - {"api", "v1", "module"}, - {"api", "v1", "module"}, - {"module", "name"}, - {"module", ":name"}, - {":param1", ":param2"}, - } - - if part, ok := split(" "); ok { - if len(part) != 0 { - t.Error("Error: split data for path '/'", part) - } - } else { - t.Error("Error: split data for path '/'") - } - - if part, ok := split("///"); ok { - if len(part) != 0 { - t.Error("Error: split data for path '/'", part) - } - } else { - t.Error("Error: split data for path '/'") - } - - if part, ok := split(" / // "); ok { - if len(part) != 0 { - t.Error("Error: split data for path '/'", part) - } - } else { - t.Error("Error: split data for path '/'") - } - - for idx, p := range path { - parts, ok := split(p) - if !ok { - if strings.HasPrefix(p, "/A/A/A") { - parser := newParser() - result := parser.register(p, func(router.Control) {}) - if result { - t.Error("Expected false result, got", result) - } - continue - } - t.Error("Error: split data for path", p) - } - for i, part := range parts { - if expected[idx][i] != part { - t.Error("Expected", expected[idx][i], "got", part) - } - } - } -} - -func TestGetRoutes(t *testing.T) { - for _, request := range setOfRegistered { - p := newParser() - p.register(request.path, request.h) - routes := p.routes() - if len(routes) != 1 { - t.Error("Expected 1 route, got", len(routes)) - } - if request.path != routes[0] { - t.Error("Expected", request.path, "got", routes[0]) - } - } -} - -func TestRegisterAsterisk(t *testing.T) { - data := "Any path is ok" - p := newParser() - p.register("*", func(c router.Control) { - c.Body(data) - }) - path := "/any/path/is/ok" - h, params, ok := p.get(path) - if !ok { - t.Error("Error: get data for path", path) - } - trw := httptest.NewRecorder() - req, err := http.NewRequest("GET", path, nil) - if err != nil { - t.Error("Error creating new request") - } - c := NewControl(trw, req) - for _, item := range params { - c.Param(item.key, item.value) - } - h(c) - if trw.Body.String() != data { - t.Error("Expected", data, "got", trw.Body.String()) - } -} - -func TestSortRecords(t *testing.T) { - var r = records{ - { - key: 111, - }, - { - key: 222, - }, - } - if r.Len() != len(r) { - t.Error("Len doesn't work, expected", len(r), "got", r.Len()) - } - first := r[0].key - second := r[1].key - r.Swap(0, 1) - if r[0].key != second { - t.Error("Swap doesn't work, expected", second, "got", r[0].key) - } - if r[1].key != first { - t.Error("Swap doesn't work, expected", first, "got", r[1].key) - } - if r.Less(0, 1) { - t.Error("Less doesn't work, expected", r[1].key, "less then", r[0].key) - } -} diff --git a/pkg/router/bitroute/serve.go b/pkg/router/bitroute/serve.go deleted file mode 100644 index 5e3bd1b..0000000 --- a/pkg/router/bitroute/serve.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2017 Igor Dolzhikov. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package bitroute - -import ( - "net/http" - "strings" - - "github.com/takama/k8sapp/pkg/router" -) - -type bitroute struct { - // List of handlers that associated with known http methods (GET, POST ...) - handlers map[string]*parser - - // If enabled, the router automatically replies to OPTIONS requests. - // Nevertheless OPTIONS handlers take priority over automatic replies. - optionsRepliesEnabled bool - - // Configurable handler which is called when a request cannot be routed. - notAllowed func(router.Control) - - // Configurable handler which is called when panic happen. - recoveryHandler func(router.Control) - - // Configurable handler which is allowed to take control - // before it is called standard methods e.g. GET, PUT. - middlewareHandler func(func(router.Control)) func(router.Control) - - // Configurable http.Handler which is called when URL path has not defined method. - // If it is not set, http.NotFound is used. - notFound func(router.Control) -} - -// New returns new router that implement Router interface. -func New() router.BitRoute { - return &bitroute{ - handlers: make(map[string]*parser), - } -} - -// GET registers a new request handle for HTTP GET method. -func (r *bitroute) GET(path string, f func(router.Control)) { - r.register("GET", path, f) -} - -// PUT registers a new request handle for HTTP PUT method. -func (r *bitroute) PUT(path string, f func(router.Control)) { - r.register("PUT", path, f) -} - -// POST registers a new request handle for HTTP POST method. -func (r *bitroute) POST(path string, f func(router.Control)) { - r.register("POST", path, f) -} - -// DELETE registers a new request handle for HTTP DELETE method. -func (r *bitroute) DELETE(path string, f func(router.Control)) { - r.register("DELETE", path, f) -} - -// HEAD registers a new request handle for HTTP HEAD method. -func (r *bitroute) HEAD(path string, f func(router.Control)) { - r.register("HEAD", path, f) -} - -// OPTIONS registers a new request handle for HTTP OPTIONS method. -func (r *bitroute) OPTIONS(path string, f func(router.Control)) { - r.register("OPTIONS", path, f) -} - -// PATCH registers a new request handle for HTTP PATCH method. -func (r *bitroute) PATCH(path string, f func(router.Control)) { - r.register("PATCH", path, f) -} - -// If enabled, the router automatically replies to OPTIONS requests. -// Nevertheless OPTIONS handlers take priority over automatic replies. -// By default this option is disabled -func (r *bitroute) UseOptionsReplies(enabled bool) { - r.optionsRepliesEnabled = enabled -} - -// SetupNotAllowedHandler defines own handler which is called when a request -// cannot be routed. -func (r *bitroute) SetupNotAllowedHandler(f func(router.Control)) { - r.notAllowed = f -} - -// SetupNotFoundHandler allows to define own handler for undefined URL path. -// If it is not set, http.NotFound is used. -func (r *bitroute) SetupNotFoundHandler(f func(router.Control)) { - r.notFound = f -} - -// SetupRecoveryHandler allows to define handler that called when panic happen. -// The handler prevents your server from crashing and should be used to return -// http status code http.StatusInternalServerError (500) -func (r *bitroute) SetupRecoveryHandler(f func(router.Control)) { - r.recoveryHandler = f -} - -// SetupMiddleware defines handler is allowed to take control -// before it is called standard methods e.g. GET, PUT. -func (r *bitroute) SetupMiddleware(f func(func(router.Control)) func(router.Control)) { - r.middlewareHandler = f -} - -// Listen and serve on requested host and port -func (r *bitroute) Listen(hostPort string) error { - return http.ListenAndServe(hostPort, r) -} - -// registers a new handler with the given path and method. -func (r *bitroute) register(method, path string, f func(router.Control)) { - if r.handlers[method] == nil { - r.handlers[method] = newParser() - } - r.handlers[method].register(path, f) -} - -func (r *bitroute) recovery(w http.ResponseWriter, req *http.Request) { - if recv := recover(); recv != nil { - c := NewControl(w, req) - r.recoveryHandler(c) - } -} - -// AllowedMethods returns list of allowed methods -func (r *bitroute) allowedMethods(path string) []string { - var allowed []string - for method, parser := range r.handlers { - if _, _, ok := parser.get(path); ok { - allowed = append(allowed, method) - } - } - - return allowed -} - -// ServeHTTP implements http.Handler interface. -func (r *bitroute) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if r.recoveryHandler != nil { - defer r.recovery(w, req) - } - if _, ok := r.handlers[req.Method]; ok { - if handle, params, ok := r.handlers[req.Method].get(req.URL.Path); ok { - c := NewControl(w, req) - if len(params) > 0 { - for _, item := range params { - c.Param(item.key, item.value) - } - } - if r.middlewareHandler != nil { - r.middlewareHandler(handle)(c) - } else { - handle(c) - } - return - } - } - allowed := r.allowedMethods(req.URL.Path) - - if len(allowed) == 0 { - if r.notFound != nil { - c := NewControl(w, req) - r.notFound(c) - } else { - http.NotFound(w, req) - } - return - } - - w.Header().Set("Allow", strings.Join(allowed, ", ")) - if req.Method == "OPTIONS" && r.optionsRepliesEnabled { - return - } - if r.notAllowed != nil { - c := NewControl(w, req) - r.notAllowed(c) - } else { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - } -} diff --git a/pkg/router/bitroute/serve_test.go b/pkg/router/bitroute/serve_test.go deleted file mode 100644 index b2d1faf..0000000 --- a/pkg/router/bitroute/serve_test.go +++ /dev/null @@ -1,446 +0,0 @@ -package bitroute - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/takama/k8sapp/pkg/router" -) - -func getRouterForTesting() *bitroute { - return &bitroute{ - handlers: make(map[string]*parser), - } -} - -func TestNewRouter(t *testing.T) { - r := New() - if r == nil { - t.Error("Expected new router, got nil") - } - err := r.Listen("$") - if err == nil { - t.Error("Expected error if used incorrect host and port") - } -} - -func TestRouterGetRootStatic(t *testing.T) { - r := getRouterForTesting() - // Registers GET handler for root static path - r.GET("/", func(c router.Control) { - c.Body("Root") - }) - req, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - if trw.Body.String() != "Root" { - t.Error("Expected", "Root", "got", trw.Body.String()) - } -} - -func TestRouterGetStatic(t *testing.T) { - r := getRouterForTesting() - // Registers GET handler for static path - r.GET("/hello", func(c router.Control) { - c.Body("Hello") - }) - req, err := http.NewRequest("GET", "/hello", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - if trw.Body.String() != "Hello" { - t.Error("Expected", "Hello", "got", trw.Body.String()) - } -} - -func TestRouterGetParameter(t *testing.T) { - r := getRouterForTesting() - // Registers GET handler with parameter - r.GET("/hello/:name", func(c router.Control) { - c.Body("Hello " + c.Query(":name")) - }) - req, err := http.NewRequest("GET", "/hello/John", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - if trw.Body.String() != "Hello John" { - t.Error("Expected", "Hello John", "got", trw.Body.String()) - } -} - -func TestRouterGetParameterFromClassicUrl(t *testing.T) { - r := getRouterForTesting() - // Registers GET handler with two parameters - r.GET("/users/:name", func(c router.Control) { - c.Body("Users: " + c.Query(":name") + " " + c.Query("name")) - }) - req, err := http.NewRequest("GET", "/users/Jane/?name=Joe", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - if trw.Body.String() != "Users: Jane Joe" { - t.Error("Expected", "Users: Jane Joe", "got", trw.Body.String()) - } -} - -func TestRouterPostJSONData(t *testing.T) { - r := getRouterForTesting() - // Registers POST handler - r.POST("/users", func(c router.Control) { - body, err := ioutil.ReadAll(c.Request().Body) - if err != nil { - t.Error(err) - } - var values map[string]string - if err := json.Unmarshal(body, &values); err != nil { - t.Error(err) - } - c.Body("User: " + values["name"]) - }) - req, err := http.NewRequest("POST", "/users/", strings.NewReader(`{"name": "Tom"}`)) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - if trw.Body.String() != "User: Tom" { - t.Error("Expected", "User: Tom", "got", trw.Body.String()) - } -} - -func TestRouterPutJSONData(t *testing.T) { - r := getRouterForTesting() - // Registers PUT handler - r.PUT("/users", func(c router.Control) { - body, err := ioutil.ReadAll(c.Request().Body) - if err != nil { - t.Error(err) - } - var values map[string]string - if err := json.Unmarshal(body, &values); err != nil { - t.Error(err) - } - c.Body("Users: " + values["name1"] + " " + values["name2"]) - }) - req, err := http.NewRequest("PUT", "/users/", strings.NewReader(`{"name1": "user1", "name2": "user2"}`)) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - if trw.Body.String() != "Users: user1 user2" { - t.Error("Expected", "Users: user1 user2", "got", trw.Body.String()) - } -} - -func TestRouterDelete(t *testing.T) { - r := getRouterForTesting() - // Registers DELETE handler - r.DELETE("/users", func(c router.Control) { - c.Body("Users deleted") - }) - req, err := http.NewRequest("DELETE", "/users/", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - if trw.Body.String() != "Users deleted" { - t.Error("Expected", "Users deleted", "got", trw.Body.String()) - } -} - -func TestRouterHead(t *testing.T) { - r := getRouterForTesting() - // Registers HEAD handler - r.HEAD("/command", func(c router.Control) { - c.Header().Add("test", "value") - }) - req, err := http.NewRequest("HEAD", "/command/", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Header().Get("test") - if result != "value" { - t.Error("Expected value", "got", result) - } -} - -func TestRouterOptions(t *testing.T) { - r := getRouterForTesting() - // Registers OPTIONS handler - r.OPTIONS("/option", func(c router.Control) { - c.Code(http.StatusOK) - }) - req, err := http.NewRequest("OPTIONS", "/option/", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Code - if result != http.StatusOK { - t.Error("Expected", http.StatusOK, "got", result) - } -} - -func TestRouterPatch(t *testing.T) { - r := getRouterForTesting() - // Registers PATCH handler - r.PATCH("/patch", func(c router.Control) { - c.Code(http.StatusOK) - }) - req, err := http.NewRequest("PATCH", "/patch/", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Code - if result != http.StatusOK { - t.Error("Expected", http.StatusOK, "got", result) - } -} - -func TestRouterUseOptionsReplies(t *testing.T) { - r := getRouterForTesting() - path := "/options" - r.GET(path, func(c router.Control) { - c.Code(http.StatusOK) - }) - r.UseOptionsReplies(true) - req, err := http.NewRequest("OPTIONS", path, nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - code := trw.Code - if code != http.StatusOK { - t.Error("Expected", http.StatusOK, "got", code) - } - header := trw.Header().Get("Allow") - expected := "GET" - if header != expected { - t.Error("Expected", expected, "got", header) - } -} - -func TestRouterNotFound(t *testing.T) { - r := getRouterForTesting() - // Registers GET handler - r.GET("/found", func(c router.Control) { - c.Code(http.StatusOK) - }) - req, err := http.NewRequest("GET", "/not-found/", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Code - if result != http.StatusNotFound { - t.Error("Expected", http.StatusNotFound, "got", result) - } -} - -func TestRouterAllowedMethods(t *testing.T) { - r := getRouterForTesting() - // Registers GET handler - path := "/allowed" - r.GET(path, func(c router.Control) { - c.Code(http.StatusOK) - }) - // Registers PUT handler - r.PUT(path, func(c router.Control) { - c.Code(http.StatusAccepted) - }) - result := r.allowedMethods(path) - for _, method := range []string{"GET", "PUT"} { - var exists bool - for _, allowed := range result { - if method == allowed { - exists = true - } - } - if !exists { - t.Error("Allowed method(s) not found in", result) - } - } - for _, method := range []string{"POST", "DELETE", "HEAD", "OPTIONS", "PATCH"} { - var exists bool - for _, allowed := range result { - if method == allowed { - exists = true - } - } - if exists { - t.Error("Not allowed method(s) found in", result) - } - } -} - -func TestRouterNotAllowed(t *testing.T) { - r := getRouterForTesting() - // Registers GET handler - path := "/allowed" - message := http.StatusText(http.StatusMethodNotAllowed) + "\n" - r.GET(path, func(c router.Control) { - c.Code(http.StatusOK) - }) - // Registers PUT handler - r.PUT(path, func(c router.Control) { - c.Code(http.StatusAccepted) - }) - req, err := http.NewRequest("POST", path, nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Code - if result != http.StatusMethodNotAllowed { - t.Error("Expected", http.StatusMethodNotAllowed, "got", result) - } - header := trw.Header().Get("Allow") - expected1 := "GET, PUT" - expected2 := "PUT, GET" - if header != expected1 && header != expected2 { - t.Error("Expected", expected1, "or", expected2, "got", header) - } - if trw.Body.String() != message { - t.Error("Expected", message, "got", trw.Body.String()) - } -} - -func TestRouterSetupNotAllowedHandler(t *testing.T) { - r := getRouterForTesting() - message := http.StatusText(http.StatusForbidden) - path := "/not/allowed" - r.GET(path, func(c router.Control) { - c.Code(http.StatusOK) - }) - r.SetupNotAllowedHandler(func(c router.Control) { - c.Code(http.StatusForbidden) - c.Body(message) - }) - req, err := http.NewRequest("PUT", path, nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - code := trw.Code - if code != http.StatusForbidden { - t.Error("Expected", http.StatusForbidden, "got", code) - } - header := trw.Header().Get("Allow") - expected := "GET" - if header != expected { - t.Error("Expected", expected, "got", header) - } - if trw.Body.String() != message { - t.Error("Expected", message, "got", trw.Body.String()) - } -} - -func TestRouterSetupNotFound(t *testing.T) { - r := getRouterForTesting() - message := http.StatusText(http.StatusForbidden) - r.SetupNotFoundHandler(func(c router.Control) { - c.Code(http.StatusForbidden) - c.Body(message) - }) - req, err := http.NewRequest("GET", "/not/found", nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Code - if result != http.StatusForbidden { - t.Error("Expected", http.StatusForbidden, "got", result) - } - if trw.Body.String() != message { - t.Error("Expected", message, "got", trw.Body.String()) - } -} - -func TestRouterRecoveryHandler(t *testing.T) { - r := getRouterForTesting() - message := http.StatusText(http.StatusServiceUnavailable) - path := "/recovery" - r.GET(path, func(c router.Control) { - panic("test") - }) - r.SetupRecoveryHandler(func(c router.Control) { - c.Code(http.StatusServiceUnavailable) - c.Body(message) - }) - req, err := http.NewRequest("GET", path, nil) - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Code - if result != http.StatusServiceUnavailable { - t.Error("Expected", http.StatusForbidden, "got", result) - } - if trw.Body.String() != message { - t.Error("Expected", message, "got", trw.Body.String()) - } -} - -func TestRouterMiddleware(t *testing.T) { - r := getRouterForTesting() - message := http.StatusText(http.StatusOK) - path := "/middleware" - r.GET(path, func(c router.Control) { - c.Code(http.StatusOK) - c.Body(message) - }) - r.SetupMiddleware(func(f func(router.Control)) func(router.Control) { - return func(c router.Control) { - headers := c.Request().Header.Get("Access-Control-Request-Headers") - if headers != "" { - c.Header().Set("Access-Control-Allow-Headers", "content-type") - } - f(c) - } - }) - req, err := http.NewRequest("GET", path, nil) - req.Header.Set("Access-Control-Request-Headers", "All") - if err != nil { - t.Error(err) - } - trw := httptest.NewRecorder() - r.ServeHTTP(trw, req) - result := trw.Code - if result != http.StatusOK { - t.Error("Expected", http.StatusOK, "got", result) - } - header := trw.Header().Get("Access-Control-Allow-Headers") - expected := "content-type" - if header != expected { - t.Error("Expected", expected, "got", header) - } - if trw.Body.String() != message { - t.Error("Expected", message, "got", trw.Body.String()) - } -} diff --git a/pkg/service/service.go b/pkg/service/service.go index 2d1a3ad..a259e75 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -7,17 +7,18 @@ package service import ( "net/http" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/config" "github.com/takama/k8sapp/pkg/handlers" "github.com/takama/k8sapp/pkg/logger" stdlog "github.com/takama/k8sapp/pkg/logger/standard" - "github.com/takama/k8sapp/pkg/router" - "github.com/takama/k8sapp/pkg/router/bitroute" "github.com/takama/k8sapp/pkg/version" ) // Setup configures the service -func Setup(cfg *config.Config) (r router.BitRoute, log logger.Logger, err error) { +func Setup(cfg *config.Config) (r bit.Router, log logger.Logger, err error) { // Setup logger log = stdlog.New(&logger.Config{ Level: cfg.LogLevel, @@ -33,7 +34,7 @@ func Setup(cfg *config.Config) (r router.BitRoute, log logger.Logger, err error) h := handlers.New(log, cfg) // Register new router - r = bitroute.New() + r = bit.NewRouter() // Response for undefined methods r.SetupNotFoundHandler(h.Base(notFound)) @@ -49,7 +50,7 @@ func Setup(cfg *config.Config) (r router.BitRoute, log logger.Logger, err error) } // Response for undefined methods -func notFound(c router.Control) { +func notFound(c bit.Control) { c.Code(http.StatusNotFound) c.Body("Method not found for " + c.Request().URL.Path) } diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go index e489ed4..94a02e6 100644 --- a/pkg/service/service_test.go +++ b/pkg/service/service_test.go @@ -5,9 +5,11 @@ import ( "net/http/httptest" "testing" + "github.com/takama/bit" + // Alternative of the Bit router with the same Router interface + // "github.com/takama/k8sapp/pkg/router/httprouter" "github.com/takama/k8sapp/pkg/config" "github.com/takama/k8sapp/pkg/handlers" - "github.com/takama/k8sapp/pkg/router/bitroute" ) func TestSetup(t *testing.T) { @@ -29,7 +31,7 @@ func TestSetup(t *testing.T) { h := handlers.New(logger, cfg) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.Base(notFound)(bitroute.NewControl(w, r)) + h.Base(notFound)(bit.NewControl(w, r)) }) req, err := http.NewRequest("GET", "/notfound", nil)