diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index ead3e1d4..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,70 +0,0 @@ -version: 2.1 - -jobs: - "test": - parameters: - version: - type: string - default: "latest" - golint: - type: boolean - default: true - modules: - type: boolean - default: true - goproxy: - type: string - default: "" - docker: - - image: "circleci/golang:<< parameters.version >>" - working_directory: /go/src/github.com/gorilla/mux - environment: - GO111MODULE: "on" - GOPROXY: "<< parameters.goproxy >>" - steps: - - checkout - - run: - name: "Print the Go version" - command: > - go version - - run: - name: "Fetch dependencies" - command: > - if [[ << parameters.modules >> = true ]]; then - go mod download - export GO111MODULE=on - else - go get -v ./... - fi - # Only run gofmt, vet & lint against the latest Go version - - run: - name: "Run golint" - command: > - if [ << parameters.version >> = "latest" ] && [ << parameters.golint >> = true ]; then - go get -u golang.org/x/lint/golint - golint ./... - fi - - run: - name: "Run gofmt" - command: > - if [[ << parameters.version >> = "latest" ]]; then - diff -u <(echo -n) <(gofmt -d -e .) - fi - - run: - name: "Run go vet" - command: > - if [[ << parameters.version >> = "latest" ]]; then - go vet -v ./... - fi - - run: - name: "Run go test (+ race detector)" - command: > - go test -v -race ./... - -workflows: - tests: - jobs: - - test: - matrix: - parameters: - version: ["latest", "1.15", "1.14", "1.13", "1.12", "1.11"] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43c2bf32..3f95d104 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,8 +14,8 @@ jobs: golangci: strategy: matrix: - go: ['1.19','1.20'] - os: [ubuntu-latest] + go: ['1.18', '1.19','1.20'] + os: [ubuntu-latest, macos-latest, windows-latest] fail-fast: true name: verify runs-on: ${{ matrix.os }} diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..bdf224b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +SHELL := /bin/bash + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# LINT is the path to the golangci-lint binary +LINT = $(shell which golangci-lint) + +.PHONY: golangci-lint +golangci-lint: +ifeq (, $(LINT)) + ifeq (, $(shell which golangci-lint)) + @{ \ + set -e ;\ + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest ;\ + } + override LINT=$(GOBIN)/golangci-lint + else + override LINT=$(shell which golangci-lint) + endif +endif + +.PHONY: verify +verify: golangci-lint + $(LINT) run + +.PHONY: test +test: + go test -race --coverprofile=coverage.coverprofile --covermode=atomic -v ./... diff --git a/go.mod b/go.mod index df170a39..7bcfa026 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/gorilla/mux -go 1.12 +go 1.19 diff --git a/middleware_test.go b/middleware_test.go index e9f0ef55..4963b66f 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -158,7 +158,10 @@ func TestMiddlewareExecution(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) t.Run("responds normally without middleware", func(t *testing.T) { @@ -178,7 +181,10 @@ func TestMiddlewareExecution(t *testing.T) { router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -196,11 +202,17 @@ func TestMiddlewareNotFound(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -221,7 +233,10 @@ func TestMiddlewareNotFound(t *testing.T) { req := newRequest("GET", "/notfound") router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Custom 404 handler")) + _, err := rw.Write([]byte("Custom 404 handler")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -237,12 +252,18 @@ func TestMiddlewareMethodMismatch(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Methods("GET") router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -262,7 +283,10 @@ func TestMiddlewareMethodMismatch(t *testing.T) { req := newRequest("POST", "/") router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Method not allowed")) + _, err := rw.Write([]byte("Method not allowed")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -278,17 +302,26 @@ func TestMiddlewareNotFoundSubrouter(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) subrouter := router.PathPrefix("/sub/").Subrouter() subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -308,7 +341,10 @@ func TestMiddlewareNotFoundSubrouter(t *testing.T) { req := newRequest("GET", "/sub/notfound") subrouter.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Custom 404 handler")) + _, err := rw.Write([]byte("Custom 404 handler")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -324,17 +360,26 @@ func TestMiddlewareMethodMismatchSubrouter(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) subrouter := router.PathPrefix("/sub/").Subrouter() subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Methods("GET") router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -354,7 +399,10 @@ func TestMiddlewareMethodMismatchSubrouter(t *testing.T) { req := newRequest("POST", "/sub/") router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Method not allowed")) + _, err := rw.Write([]byte("Method not allowed")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -508,7 +556,10 @@ func TestMiddlewareOnMultiSubrouter(t *testing.T) { secondSubRouter := router.PathPrefix("/").Subrouter() router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte(notFound)) + _, err := rw.Write([]byte(notFound)) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) firstSubRouter.HandleFunc("/first", func(w http.ResponseWriter, r *http.Request) { @@ -521,14 +572,20 @@ func TestMiddlewareOnMultiSubrouter(t *testing.T) { firstSubRouter.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(first)) + _, err := w.Write([]byte(first)) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) secondSubRouter.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(second)) + _, err := w.Write([]byte(second)) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) diff --git a/mux_httpserver_test.go b/mux_httpserver_test.go index 907ab91d..f55a2de3 100644 --- a/mux_httpserver_test.go +++ b/mux_httpserver_test.go @@ -5,7 +5,7 @@ package mux import ( "bytes" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -14,10 +14,16 @@ import ( func TestSchemeMatchers(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("hello http world")) + _, err := rw.Write([]byte("hello http world")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Schemes("http") router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("hello https world")) + _, err := rw.Write([]byte("hello https world")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Schemes("https") assertResponseBody := func(t *testing.T, s *httptest.Server, expectedBody string) { @@ -28,7 +34,7 @@ func TestSchemeMatchers(t *testing.T) { if resp.StatusCode != 200 { t.Fatalf("expected a status code of 200, got %v", resp.StatusCode) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("unexpected error reading body: %v", err) } diff --git a/mux_test.go b/mux_test.go index 2d8d2b3e..4345254b 100644 --- a/mux_test.go +++ b/mux_test.go @@ -10,7 +10,8 @@ import ( "context" "errors" "fmt" - "io/ioutil" + "io" + "log" "net/http" "net/http/httptest" "net/url" @@ -2136,7 +2137,10 @@ type methodsSubrouterTest struct { // methodHandler writes the method string in response. func methodHandler(method string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(method)) + _, err := w.Write([]byte(method)) + if err != nil { + log.Printf("Failed writing HTTP response: %v", err) + } } } @@ -2778,7 +2782,7 @@ func TestSubrouterCustomMethodNotAllowed(t *testing.T) { tt.Errorf("Expected status code 405 (got %d)", w.Code) } - b, err := ioutil.ReadAll(w.Body) + b, err := io.ReadAll(w.Body) if err != nil { tt.Errorf("failed to read body: %v", err) } @@ -2859,7 +2863,10 @@ func stringMapEqual(m1, m2 map[string]string) bool { // http.ResponseWriter. func stringHandler(s string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(s)) + _, err := w.Write([]byte(s)) + if err != nil { + log.Printf("Failed writing HTTP response: %v", err) + } } } @@ -2892,7 +2899,10 @@ func newRequest(method, url string) *http.Request { // Simulate writing to wire var buff bytes.Buffer - req.Write(&buff) + err = req.Write(&buff) + if err != nil { + log.Printf("Failed writing HTTP request: %v", err) + } ioreader := bufio.NewReader(&buff) // Parse request off of 'wire' diff --git a/regexp.go b/regexp.go index 37c11edc..5d05cfa0 100644 --- a/regexp.go +++ b/regexp.go @@ -195,7 +195,7 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { // url builds a URL part using the given values. func (r *routeRegexp) url(values map[string]string) (string, error) { - urlValues := make([]interface{}, len(r.varsN), len(r.varsN)) + urlValues := make([]interface{}, len(r.varsN)) for k, v := range r.varsN { value, ok := values[v] if !ok { diff --git a/regexp_test.go b/regexp_test.go index 0d80e6a5..d7518f3e 100644 --- a/regexp_test.go +++ b/regexp_test.go @@ -54,7 +54,7 @@ func Benchmark_findQueryKey(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - for key, _ := range all { + for key := range all { _, _ = findFirstQueryKey(query, key) } } @@ -79,7 +79,7 @@ func Benchmark_findQueryKeyGoLib(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - for key, _ := range all { + for key := range all { v := u.Query()[key] if len(v) > 0 { _ = v[0] diff --git a/route.go b/route.go index 750afe57..ce1c9bfe 100644 --- a/route.go +++ b/route.go @@ -64,7 +64,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { match.MatchErr = nil } - matchErr = nil + matchErr = nil // nolint:ineffassign return false } } @@ -230,9 +230,9 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { // Headers adds a matcher for request header values. // It accepts a sequence of key/value pairs to be matched. For example: // -// r := mux.NewRouter() -// r.Headers("Content-Type", "application/json", -// "X-Requested-With", "XMLHttpRequest") +// r := mux.NewRouter() +// r.Headers("Content-Type", "application/json", +// "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both request header values match. // If the value is an empty string, it will match any value if the key is set. @@ -255,9 +255,9 @@ func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex // support. For example: // -// r := mux.NewRouter() -// r.HeadersRegexp("Content-Type", "application/(text|json)", -// "X-Requested-With", "XMLHttpRequest") +// r := mux.NewRouter() +// r.HeadersRegexp("Content-Type", "application/(text|json)", +// "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both the request header matches both regular expressions. // If the value is an empty string, it will match any value if the key is set. @@ -283,10 +283,10 @@ func (r *Route) HeadersRegexp(pairs ...string) *Route { // // For example: // -// r := mux.NewRouter() -// r.Host("www.example.com") -// r.Host("{subdomain}.domain.com") -// r.Host("{subdomain:[a-z]+}.domain.com") +// r := mux.NewRouter() +// r.Host("www.example.com") +// r.Host("{subdomain}.domain.com") +// r.Host("{subdomain:[a-z]+}.domain.com") // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). @@ -342,11 +342,11 @@ func (r *Route) Methods(methods ...string) *Route { // // For example: // -// r := mux.NewRouter() -// r.Path("/products/").Handler(ProductsHandler) -// r.Path("/products/{key}").Handler(ProductsHandler) -// r.Path("/articles/{category}/{id:[0-9]+}"). -// Handler(ArticleHandler) +// r := mux.NewRouter() +// r.Path("/products/").Handler(ProductsHandler) +// r.Path("/products/{key}").Handler(ProductsHandler) +// r.Path("/articles/{category}/{id:[0-9]+}"). +// Handler(ArticleHandler) // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). @@ -377,8 +377,8 @@ func (r *Route) PathPrefix(tpl string) *Route { // It accepts a sequence of key/value pairs. Values may define variables. // For example: // -// r := mux.NewRouter() -// r.Queries("foo", "bar", "id", "{id:[0-9]+}") +// r := mux.NewRouter() +// r.Queries("foo", "bar", "id", "{id:[0-9]+}") // // The above route will only match if the URL contains the defined queries // values, e.g.: ?foo=bar&id=42. @@ -473,11 +473,11 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { // // It will test the inner routes only if the parent route matched. For example: // -// r := mux.NewRouter() -// s := r.Host("www.example.com").Subrouter() -// s.HandleFunc("/products/", ProductsHandler) -// s.HandleFunc("/products/{key}", ProductHandler) -// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) +// r := mux.NewRouter() +// s := r.Host("www.example.com").Subrouter() +// s.HandleFunc("/products/", ProductsHandler) +// s.HandleFunc("/products/{key}", ProductHandler) +// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) // // Here, the routes registered in the subrouter won't be tested if the host // doesn't match. @@ -497,36 +497,36 @@ func (r *Route) Subrouter() *Router { // It accepts a sequence of key/value pairs for the route variables. For // example, given this route: // -// r := mux.NewRouter() -// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Name("article") +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Name("article") // // ...a URL for it can be built using: // -// url, err := r.Get("article").URL("category", "technology", "id", "42") +// url, err := r.Get("article").URL("category", "technology", "id", "42") // // ...which will return an url.URL with the following path: // -// "/articles/technology/42" +// "/articles/technology/42" // // This also works for host variables: // -// r := mux.NewRouter() -// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Host("{subdomain}.domain.com"). -// Name("article") +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Host("{subdomain}.domain.com"). +// Name("article") // -// // url.String() will be "http://news.domain.com/articles/technology/42" -// url, err := r.Get("article").URL("subdomain", "news", -// "category", "technology", -// "id", "42") +// // url.String() will be "http://news.domain.com/articles/technology/42" +// url, err := r.Get("article").URL("subdomain", "news", +// "category", "technology", +// "id", "42") // // The scheme of the resulting url will be the first argument that was passed to Schemes: // -// // url.String() will be "https://example.com" -// r := mux.NewRouter() -// url, err := r.Host("example.com") -// .Schemes("https", "http").URL() +// // url.String() will be "https://example.com" +// r := mux.NewRouter() +// url, err := r.Host("example.com") +// .Schemes("https", "http").URL() // // All variables defined in the route are required, and their values must // conform to the corresponding patterns.