From 1fa2ee87bf61940bfac616e98513448af97a1813 Mon Sep 17 00:00:00 2001 From: Apoorva Jagtap <35304110+apoorvajagtap@users.noreply.github.com> Date: Mon, 24 Jul 2023 01:45:04 +0530 Subject: [PATCH] [GPT-95] Update go version, add tools for verification and testing (#718) Fixes # https://gorilla-web-toolkit.atlassian.net/browse/GPT-95 **Summary of Changes** Added `.github/workflows/test.yml` that runs golangci-lint & go tests on any PR created or each push to main branch. --------- Signed-off-by: Corey Daley Signed-off-by: Apoorva Jagtap <35304110+apoorvajagtap@users.noreply.github.com> Co-authored-by: Corey Daley --- .circleci/config.yml | 70 -------------------------- .editorconfig | 20 ++++++++ .github/workflows/issues.yml | 20 ++++++++ .github/workflows/test.yml | 55 +++++++++++++++++++++ .gitignore | 1 + Makefile | 34 +++++++++++++ README.md | 8 +-- go.mod | 2 +- middleware_test.go | 95 ++++++++++++++++++++++++++++-------- mux_httpserver_test.go | 14 ++++-- mux_test.go | 20 ++++++-- regexp.go | 2 +- regexp_test.go | 4 +- route.go | 80 +++++++++++++++--------------- 14 files changed, 280 insertions(+), 145 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .editorconfig create mode 100644 .github/workflows/issues.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 Makefile 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/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c6b74c3e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +; https://editorconfig.org/ + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go,.gitmodules}] +indent_style = tab +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +eclint_indent_style = unset \ No newline at end of file diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 00000000..055ca822 --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,20 @@ +# Add issues or pull-requests created to the project. +name: Add issue or pull request to Project + +on: + issues: + types: + - opened + pull_request: + types: + - opened + +jobs: + add-to-project: + runs-on: ubuntu-latest + steps: + - name: Add issue to project + uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/gorilla/projects/4 + github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..af48d228 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,55 @@ +name: CI +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + verify-and-test: + strategy: + matrix: + go: ['1.19','1.20'] + os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: true + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + cache: false + + - name: Run GolangCI-Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.53 + args: --timeout=5m + + - name: Run GoSec + if: matrix.os == 'ubuntu-latest' + uses: securego/gosec@master + with: + args: ./... + + - name: Run GoVulnCheck + uses: golang/govulncheck-action@v1 + with: + go-version-input: ${{ matrix.go }} + go-package: ./... + + - name: Run Tests + run: go test -race -cover -coverprofile=coverage -covermode=atomic -v ./... + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./coverage \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..84039fec --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +coverage.coverprofile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..98f5ab75 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') +GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +GO_SEC=$(shell which gosec 2> /dev/null || echo '') +GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest + +GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') +GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest + +.PHONY: golangci-lint +golangci-lint: + $(if $(GO_LINT), ,go install $(GO_LINT_URI)) + @echo "##### Running golangci-lint" + golangci-lint run -v + +.PHONY: gosec +gosec: + $(if $(GO_SEC), ,go install $(GO_SEC_URI)) + @echo "##### Running gosec" + gosec ./... + +.PHONY: govulncheck +govulncheck: + $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) + @echo "##### Running govulncheck" + govulncheck ./... + +.PHONY: verify +verify: golangci-lint gosec govulncheck + +.PHONY: test +test: + @echo "##### Running tests" + go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... \ No newline at end of file diff --git a/README.md b/README.md index f836a4e7..551f3c67 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # gorilla/mux -[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) -[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux) -[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) +![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg) +[![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux) +[![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) +[![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) + ![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png) 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.