A powerful HTTP router and URL matcher for building Go web servers with 🦍

gorilla/mux

GoDoc CircleCI Sourcegraph

Gorilla Logo

https://www.gorillatoolkit.org/pkg/mux

Package gorilla/mux implements a request router and dispatcher for matching incoming requests to their respective handler.

The name mux stands for "HTTP request multiplexer". Like the standard http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:

  • It implements the http.Handler interface so it is compatible with the standard http.ServeMux.
  • Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
  • URL hosts, paths and query values can have variables with an optional regular expression.
  • Registered URLs can be built, or "reversed", which helps maintaining references to resources.
  • Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.


Install

With a correctly configured Go toolchain:

go get -u github.com/gorilla/mux

Examples

Let's start registering a couple of URL paths and handlers:

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", HomeHandler)
    r.HandleFunc("/products", ProductsHandler)
    r.HandleFunc("/articles", ArticlesHandler)
    http.Handle("/", r)
}

Here we register three routes mapping URL paths to handlers. This is equivalent to how http.HandleFunc() works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (http.ResponseWriter, *http.Request) as parameters.

Paths can have variables. They are defined using the format {name} or {name:pattern}. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:

r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

The names are used to create a map of route variables which can be retrieved calling mux.Vars():

func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Category: %v\n", vars["category"])
}

And this is all you need to know about the basic usage. More advanced options are explained below.

Matching Routes

Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:

r := mux.NewRouter()
// Only matches if domain is "www.example.com".
r.Host("www.example.com")
// Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.example.com")

There are several other matchers that can be added. To match path prefixes:

r.PathPrefix("/products/")

...or HTTP methods:

r.Methods("GET", "POST")

...or URL schemes:

r.Schemes("https")

...or header values:

r.Headers("X-Requested-With", "XMLHttpRequest")

...or query values:

r.Queries("key", "value")

...or to use a custom matcher function:

r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
    return r.ProtoMajor == 0
})

...and finally, it is possible to combine several matchers in a single route:

r.HandleFunc("/products", ProductsHandler).
  Host("www.example.com").
  Methods("GET").
  Schemes("http")

Routes are tested in the order they were added to the router. If two routes match, the first one wins:

r := mux.NewRouter()
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)

Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".

For example, let's say we have several URLs that should only match when the host is www.example.com. Create a route for that host and get a "subrouter" from it:

r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()

Then register routes in the subrouter:

s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

The three URL paths we registered above will only be tested if the domain is www.example.com, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.

Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.

There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:

r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()
// "/products/"
s.HandleFunc("/", ProductsHandler)
// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)

Static Files

Note that the path provided to PathPrefix() represents a "wildcard": calling PathPrefix("/static/").Handler(...) means that the handler will be passed any request that matches "/static/*". This makes it easy to serve static files with mux:

func main() {
    var dir string

    flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
    flag.Parse()
    r := mux.NewRouter()

    // This will serve files under http://localhost:8000/static/<filename>
    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))

    srv := &http.Server{
        Handler:      r,
        Addr:         "127.0.0.1:8000",
        // Good practice: enforce timeouts for servers you create!
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

    log.Fatal(srv.ListenAndServe())
}

Serving Single Page Applications

Most of the time it makes sense to serve your SPA on a separate web server from your API, but sometimes it's desirable to serve them both from one place. It's possible to write a simple handler for serving your SPA (for use with React Router's BrowserRouter for example), and leverage mux's powerful routing for your API endpoints.

package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/gorilla/mux"
)

// spaHandler implements the http.Handler interface, so we can use it
// to respond to HTTP requests. The path to the static directory and
// path to the index file within that static directory are used to
// serve the SPA in the given static directory.
type spaHandler struct {
	staticPath string
	indexPath  string
}

// ServeHTTP inspects the URL path to locate a file within the static dir
// on the SPA handler. If a file is found, it will be served. If not, the
// file located at the index path on the SPA handler will be served. This
// is suitable behavior for serving an SPA (single page application).
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // get the absolute path to prevent directory traversal
	path, err := filepath.Abs(r.URL.Path)
	if err != nil {
        // if we failed to get the absolute path respond with a 400 bad request
        // and stop
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

    // prepend the path with the path to the static directory
	path = filepath.Join(h.staticPath, path)

    // check whether a file exists at the given path
	_, err = os.Stat(path)
	if os.IsNotExist(err) {
		// file does not exist, serve index.html
		http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
		return
	} else if err != nil {
        // if we got an error (that wasn't that the file doesn't exist) stating the
        // file, return a 500 internal server error and stop
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

    // otherwise, use http.FileServer to serve the static dir
	http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
}

func main() {
	router := mux.NewRouter()

	router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
		// an example API handler
		json.NewEncoder(w).Encode(map[string]bool{"ok": true})
	})

	spa := spaHandler{staticPath: "build", indexPath: "index.html"}
	router.PathPrefix("/").Handler(spa)

	srv := &http.Server{
		Handler: router,
		Addr:    "127.0.0.1:8000",
		// Good practice: enforce timeouts for servers you create!
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Fatal(srv.ListenAndServe())
}

Registered URLs

Now let's see how to build registered URLs.

Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling Name() on a route. For example:

r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
  Name("article")

To build a URL, get the route and call the URL() method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:

url, err := r.Get("article").URL("category", "technology", "id", "42")

...and the result will be a url.URL with the following path:

"/articles/technology/42"

This also works for host and query value variables:

r := mux.NewRouter()
r.Host("{subdomain}.example.com").
  Path("/articles/{category}/{id:[0-9]+}").
  Queries("filter", "{filter}").
  HandlerFunc(ArticleHandler).
  Name("article")

// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42",
                                 "filter", "gorilla")

All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.

Regex support also exists for matching Headers within a route. For example, we could do:

r.HeadersRegexp("Content-Type", "application/(text|json)")

...and the route will match both requests with a Content-Type of application/json as well as application/text

There's also a way to build only the URL host or path for a route: use the methods URLHost() or URLPath() instead. For the previous route, we would do:

// "http://news.example.com/"
host, err := r.Get("article").URLHost("subdomain", "news")

// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")

And if you use subrouters, host and path defined separately can be built as well:

r := mux.NewRouter()
s := r.Host("{subdomain}.example.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
  HandlerFunc(ArticleHandler).
  Name("article")

// "http://news.example.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42")

Walking Routes

The Walk function on mux.Router can be used to visit all of the routes that are registered on a router. For example, the following prints all of the registered routes:

package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/gorilla/mux"
)

func handler(w http.ResponseWriter, r *http.Request) {
	return
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/", handler)
	r.HandleFunc("/products", handler).Methods("POST")
	r.HandleFunc("/articles", handler).Methods("GET")
	r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
	r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
	err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
		pathTemplate, err := route.GetPathTemplate()
		if err == nil {
			fmt.Println("ROUTE:", pathTemplate)
		}
		pathRegexp, err := route.GetPathRegexp()
		if err == nil {
			fmt.Println("Path regexp:", pathRegexp)
		}
		queriesTemplates, err := route.GetQueriesTemplates()
		if err == nil {
			fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
		}
		queriesRegexps, err := route.GetQueriesRegexp()
		if err == nil {
			fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
		}
		methods, err := route.GetMethods()
		if err == nil {
			fmt.Println("Methods:", strings.Join(methods, ","))
		}
		fmt.Println()
		return nil
	})

	if err != nil {
		fmt.Println(err)
	}

	http.Handle("/", r)
}

Graceful Shutdown

Go 1.8 introduced the ability to gracefully shutdown a *http.Server. Here's how to do that alongside mux:

package main

import (
    "context"
    "flag"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"

    "github.com/gorilla/mux"
)

func main() {
    var wait time.Duration
    flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
    flag.Parse()

    r := mux.NewRouter()
    // Add your routes as needed

    srv := &http.Server{
        Addr:         "0.0.0.0:8080",
        // Good practice to set timeouts to avoid Slowloris attacks.
        WriteTimeout: time.Second * 15,
        ReadTimeout:  time.Second * 15,
        IdleTimeout:  time.Second * 60,
        Handler: r, // Pass our instance of gorilla/mux in.
    }

    // Run our server in a goroutine so that it doesn't block.
    go func() {
        if err := srv.ListenAndServe(); err != nil {
            log.Println(err)
        }
    }()

    c := make(chan os.Signal, 1)
    // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
    // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
    signal.Notify(c, os.Interrupt)

    // Block until we receive our signal.
    <-c

    // Create a deadline to wait for.
    ctx, cancel := context.WithTimeout(context.Background(), wait)
    defer cancel()
    // Doesn't block if no connections, but will otherwise wait
    // until the timeout deadline.
    srv.Shutdown(ctx)
    // Optionally, you could run srv.Shutdown in a goroutine and block on
    // <-ctx.Done() if your application should wait for other services
    // to finalize based on context cancellation.
    log.Println("shutting down")
    os.Exit(0)
}

Middleware

Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.

Mux middlewares are defined using the de facto standard type:

type MiddlewareFunc func(http.Handler) http.Handler

Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.

A very basic middleware which logs the URI of the request being handled could be written as:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Do stuff here
        log.Println(r.RequestURI)
        // Call the next handler, which can be another middleware in the chain, or the final handler.
        next.ServeHTTP(w, r)
    })
}

Middlewares can be added to a router using Router.Use():

r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)

A more complex authentication middleware, which maps session token to users, could be written as:

// Define our struct
type authenticationMiddleware struct {
	tokenUsers map[string]string
}

// Initialize it somewhere
func (amw *authenticationMiddleware) Populate() {
	amw.tokenUsers["00000000"] = "user0"
	amw.tokenUsers["aaaaaaaa"] = "userA"
	amw.tokenUsers["05f717e5"] = "randomUser"
	amw.tokenUsers["deadbeef"] = "user0"
}

// Middleware function, which will be called for each request
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("X-Session-Token")

        if user, found := amw.tokenUsers[token]; found {
        	// We found the token in our map
        	log.Printf("Authenticated user %s\n", user)
        	// Pass down the request to the next middleware (or final handler)
        	next.ServeHTTP(w, r)
        } else {
        	// Write an error and stop the handler chain
        	http.Error(w, "Forbidden", http.StatusForbidden)
        }
    })
}
r := mux.NewRouter()
r.HandleFunc("/", handler)

amw := authenticationMiddleware{}
amw.Populate()

r.Use(amw.Middleware)

Note: The handler chain will be stopped if your middleware doesn't call next.ServeHTTP() with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares should write to ResponseWriter if they are going to terminate the request, and they should not write to ResponseWriter if they are not going to terminate it.

Handling CORS Requests

CORSMethodMiddleware intends to make it easier to strictly set the Access-Control-Allow-Methods response header.

  • You will still need to use your own CORS handler to set the other CORS headers such as Access-Control-Allow-Origin
  • The middleware will set the Access-Control-Allow-Methods header to all the method matchers (e.g. r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions) -> Access-Control-Allow-Methods: GET,PUT,OPTIONS) on a route
  • If you do not specify any methods, then:

Important: there must be an OPTIONS method matcher for the middleware to set the headers.

Here is an example of using CORSMethodMiddleware along with a custom OPTIONS handler to set all the required CORS headers:

package main

import (
	"net/http"
	"github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()

    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
    r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
    r.Use(mux.CORSMethodMiddleware(r))
    
    http.ListenAndServe(":8080", r)
}

func fooHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Access-Control-Allow-Origin", "*")
    if r.Method == http.MethodOptions {
        return
    }

    w.Write([]byte("foo"))
}

And an request to /foo using something like:

curl localhost:8080/foo -v

Would look like:

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /foo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.59.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
< Access-Control-Allow-Origin: *
< Date: Fri, 28 Jun 2019 20:13:30 GMT
< Content-Length: 3
< Content-Type: text/plain; charset=utf-8
< 
* Connection #0 to host localhost left intact
foo

Testing Handlers

Testing handlers in a Go web application is straightforward, and mux doesn't complicate this any further. Given two files: endpoints.go and endpoints_test.go, here's how we'd test an application using mux.

First, our simple HTTP handler:

// endpoints.go
package main

func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
    // A very simple health check.
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)

    // In the future we could report back on the status of our DB, or our cache
    // (e.g. Redis) by performing a simple PING, and include them in the response.
    io.WriteString(w, `{"alive": true}`)
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/health", HealthCheckHandler)

    log.Fatal(http.ListenAndServe("localhost:8080", r))
}

Our test code:

// endpoints_test.go
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHealthCheckHandler(t *testing.T) {
    // Create a request to pass to our handler. We don't have any query parameters for now, so we'll
    // pass 'nil' as the third parameter.
    req, err := http.NewRequest("GET", "/health", nil)
    if err != nil {
        t.Fatal(err)
    }

    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(HealthCheckHandler)

    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
    // directly and pass in our Request and ResponseRecorder.
    handler.ServeHTTP(rr, req)

    // Check the status code is what we expect.
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }

    // Check the response body is what we expect.
    expected := `{"alive": true}`
    if rr.Body.String() != expected {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), expected)
    }
}

In the case that our routes have variables, we can pass those in the request. We could write table-driven tests to test multiple possible route variables as needed.

// endpoints.go
func main() {
    r := mux.NewRouter()
    // A route with a route variable:
    r.HandleFunc("/metrics/{type}", MetricsHandler)

    log.Fatal(http.ListenAndServe("localhost:8080", r))
}

Our test file, with a table-driven test of routeVariables:

// endpoints_test.go
func TestMetricsHandler(t *testing.T) {
    tt := []struct{
        routeVariable string
        shouldPass bool
    }{
        {"goroutines", true},
        {"heap", true},
        {"counters", true},
        {"queries", true},
        {"adhadaeqm3k", false},
    }

    for _, tc := range tt {
        path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
        req, err := http.NewRequest("GET", path, nil)
        if err != nil {
            t.Fatal(err)
        }

        rr := httptest.NewRecorder()
	
	// Need to create a router that we can pass the request through so that the vars will be added to the context
	router := mux.NewRouter()
        router.HandleFunc("/metrics/{type}", MetricsHandler)
        router.ServeHTTP(rr, req)

        // In this case, our MetricsHandler returns a non-200 response
        // for a route variable it doesn't know about.
        if rr.Code == http.StatusOK && !tc.shouldPass {
            t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
                tc.routeVariable, rr.Code, http.StatusOK)
        }
    }
}

Full Example

Here's a complete, runnable example of a small mux based server:

package main

import (
    "net/http"
    "log"
    "github.com/gorilla/mux"
)

func YourHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Gorilla!\n"))
}

func main() {
    r := mux.NewRouter()
    // Routes consist of a path and a handler function.
    r.HandleFunc("/", YourHandler)

    // Bind to a port and pass our router in
    log.Fatal(http.ListenAndServe(":8000", r))
}

License

BSD licensed. See the LICENSE file for details.

Owner
Gorilla Web Toolkit
Gorilla is a web toolkit for the Go programming language that provides useful, composable packages for writing HTTP-based applications.
Gorilla Web Toolkit
Comments
  • ⚠️ The Gorilla Toolkit is Looking for a New Maintainer

    ⚠️ The Gorilla Toolkit is Looking for a New Maintainer

    The Gorilla Toolkit is looking for a new maintainer (or maintainers, plural). As the last standing maintainer of the project, I no longer have time to fully dedicate to maintaining the libraries across this project.

    The major libraries — mux (https://github.com/gorilla/mux), schema (https://github.com/gorilla/schema), handlers (https://github.com/gorilla/handlers), and sessions (https://github.com/gorilla/sessions) — are all reasonably mature libraries, but ongoing stewardship around bug triage, feature enhancements, and potential "version 2.0s" are all possibilities.

    The core asks of any new maintainers:

    • Have a demonstrated history of OSS contributions. This is important, as you need to be trustworthy: no maintainer is better than an adversarial maintainer!
    • Ideally, you actively contribute for 3-6 months, I merge after you review, and you gain the commit bit on the relevant repos after that period and/or active engagement on your part.
    • I transition you to admin of the project.

    Note: I don't expect this to be quick or easy - the websocket library, with 16k stars & 15k unique clones per week, has been looking for a new maintainer 3.5+ years, and has yet to have anyone reliably stick.

    If I don't have any luck finding new maintainer(s) in the next 6 months or so, it's likely I'll mark these projects as in maintenance mode only and archive the repos.

    Please keep the replies on-topic.

  • PR for middleware support

    PR for middleware support

    Hello there,

    I've been using gorilla/mux for a while on an internal project at my organization, and due to some requirements, I ended up forking the project and adding some extra features, such as internal support for middleware.

    Yes, I am aware that negroni is a thing, and that it also provides this functionality. However, there are a few key things you can't do just chaining mux after your middleware:

    • Acting only if a route match is found: Waiting to see if a route actually matches before acting allows for more flexibility and coherent design. For example, an authentication middleware can happily return 403 if the required credentials are not supplied, and at the same time a normal 404 will be returned by mux if the route does not exist. This option does not exist if you need to process authentication headers before the request is matched.

    • Adding middleware for subrouters is simpler if it is embed inside mux.

    • It is more efficient. If your site receives heavy traffic and your middleware performs heavy tasks, you'll appreciate this kind of saving.

    After pondering a bit, I decided that this simple addon was worth implementing, so I did.

    As a middleware needs to be able to stop the handlers chain, I implemented it using a slightly modified http.Handler interface:

    type Middleware interface {
    	ServeHTTP(http.ResponseWriter, *http.Request) (http.ResponseWriter, *http.Request)
    }
    
    type MiddlewareFunc func(http.ResponseWriter, *http.Request) (http.ResponseWriter, *http.Request)
    

    If the middleware implementations return nil as either http.ResponseWriter or *http.Request the chain is stopped. Also, http.ResponseWriter and *http.Request can be hijacked if the middleware wants to. This is an approach slightly different to negroni's, where a third parameter of *http.HandlerFunc is added. I thought this way were better as it simplifies greatly building and iterating the middleware chain. Also, I don't like recursion. Of course, wrapper functions which receive http.Handler and http.HandlerFunc exists, so standard handlers can be chained too.

    I'm not sure if you think this feature is kind of out-of scope. But if you don't, I'll happily open a PR and discuss any modifications or style changes you'd want to be done.

  • router.Use middleware not being hit

    router.Use middleware not being hit

    What version of Go are you running? go version go1.10.4 linux/amd64

    What version of gorilla/mux are you at? 3d80bc801bb034e17cae38591335b3b1110f1c47

    Describe your problem (and what you have tried so far) I am registering middleware, but they don't seem to be hit.

    Paste a minimal, runnable, reproduction of your issue below (use backticks to format it)

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"github.com/dgrijalva/jwt-go"
    	"github.com/gorilla/context"
    	"github.com/mitchellh/mapstructure"
    	"huru/migrations"
    	"huru/models"
    	"huru/models/person"
    	"huru/routes"
    	"net/http"
    	"os"
    
    	"github.com/gorilla/mux"
    	_ "github.com/lib/pq"
    	log "github.com/sirupsen/logrus"
    )
    
    func loggingMiddleware(next http.Handler) http.Handler {
    
    	log.Println("logging middleware registered");
    
    	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    		// Do stuff here
    		log.Println("Here is the request URI:",r.RequestURI)
    		// Call the next handler, which can be another middleware in the chain, or the final handler.
    		next.ServeHTTP(w, r)
    	})
    }
    
    type Exception struct {
    	Message string `json:"message"`
    }
    
    func authMiddleware(next http.Handler) http.Handler {
    
    	log.Println("auth middleware registered");
    
    	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    
    		params := r.URL.Query()
    		fmt.Println("the params are:", params);
    
    		token, _ := jwt.Parse(params["token"][0], func(token *jwt.Token) (interface{}, error) {
    			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
    				return nil, fmt.Errorf("there was an error")
    			}
    			return []byte("secret"), nil
    		})
    		claims, ok := token.Claims.(jwt.MapClaims)
    
    		if ! (ok && token.Valid) {
    			json.NewEncoder(w).Encode(Exception{Message: "Invalid authorization token"})
    			return;
    		}
    
    		var user person.Model
    		mapstructure.Decode(claims, &user)
    		context.Set(r, "logged_in_user", user)
    		next.ServeHTTP(w, r)
    	})
    }
    
    func errorMiddleware(next http.Handler) http.Handler {
    
    	log.Println("error handling middleware registered");
    
    	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    		defer func() {
    			if err := recover(); err != nil {
    
    				log.Error("Caught error in defer/recover middleware: ", err)
    				originalError := err.(struct{ OriginalError error }).OriginalError
    
    				if originalError != nil {
    					log.Error("Original error in defer/recover middleware: ", originalError)
    				}
    
    				statusCode := err.(struct{ StatusCode int }).StatusCode
    
    				if statusCode != 0 {
    					w.WriteHeader(statusCode)
    				} else {
    					w.WriteHeader(http.StatusInternalServerError)
    				}
    
    				message := err.(struct{ Message string }).Message
    
    				if message == "" {
    					message = "Unknown error message."
    				}
    
    				json.NewEncoder(w).Encode(struct {
    					ID string
    				}{
    					message,
    				})
    			}
    		}()
    		next.ServeHTTP(w, r)
    	})
    }
    
    func main() {
    
    	routerParent := mux.NewRouter()
    	routerParent.Use(loggingMiddleware)
    	routerParent.Use(errorMiddleware)
    	routerParent.Use(authMiddleware)
    
    	router := routerParent.PathPrefix("/api/v1").Subrouter();
    	router.Use(loggingMiddleware)
    	router.Use(errorMiddleware)
    	router.Use(authMiddleware)
    
    
    	// register and login
    	{
    		handler := routes.LoginHandler{}
    		subRouter := router.PathPrefix("/").Subrouter()
    		handler.Mount(subRouter, struct{}{});
    	}
    
    	{
    
    		handler := routes.RegisterHandler{}
    		subRouter := router.PathPrefix("/").Subrouter()
    		handler.Mount(subRouter, struct{}{})
    	}
    
    	{
    		// people
    		handler := routes.PersonHandler{}
    		subRouter := router.PathPrefix("/").Subrouter()
    		subRouter.Use(authMiddleware)
    		handler.Mount(subRouter, routes.PersonInjection{People: models.PersonInit()})
    	}
    }
    

    none of these get logged:

    	log.Println("error handling middleware registered");
    	log.Println("auth middleware registered");
    	log.Println("logging middleware registered");
    

    and at runtime none of middleware routes seem to get hit, nothing is logged there.

  • mux.Vars is Empty don't understand why

    mux.Vars is Empty don't understand why

    Hello, i'm using you powerful package, it's very good, but i have a problem when i try to implement basic authentication (https://github.com/abbot/go-http-auth)

    folowing lines: htpasswd := auth.HtpasswdFileProvider(authFile) authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd)

    r := mux.NewRouter() r.HandleFunc("/licenses/{key}/status", authenticator.Wrap(apilsd.CancelLicenseStatus)).Methods("PATCH")

    func CancelLicenseStatus(w http.ResponseWriter, r *auth.AuthenticatedRequest) { vars := mux.Vars(&r.Request) fmt.Printf(" %#v", &r.Request) licenseFk := vars["key"] }

    in this case licenseFk equals empty string

  • Return a 405 when HTTP method is not supported

    Return a 405 when HTTP method is not supported

    What steps will reproduce the problem?

    1. Register a URL & handler with r.HandleFunc("/foo", myHandlerFunc).Methods("POST")
    2. Using a web browser, or cURL, hit the endpoint using an HTTP GET request

    What is the expected output? What do you see instead? Ideally, a 405 Method Not Allowed status should be returned, but the response status is a 404 File Not Found.

    (issue moved from Google Code: http://code.google.com/p/gorilla/issues/detail?id=50)

  • mux/v2: need feedback

    mux/v2: need feedback

    I'd like to start a conversation and brainstorm about the future of gorilla/mux.

    Background: gorilla/mux has evolved to be the most featureful router in Go, but this came with a price: the API feels bloated and has unneeded complications. It is also far from shining in benchmarks compared to other routers (not that gorilla/mux is a bottleneck to care about, but not-so-shiny numbers give a bad impression to new users).

    I'd like to take gorilla/mux to the next level with these goals in mind:

    1. Slim API;
    2. Intuitive usage: simple rules, minimized unexpected behavior;
    3. Improved performance;

    After playing around, I came up with an implementation draft that is a radical departure from what gorilla/mux is now, and to achieve the slim-intuitive-fast goals some cuts were made:

    • Merged Path(), Handler() and HandlerFunc();
    • Merged URL(), URLHost() and URLPath();
    • Moved Scheme, Host and PathPrefix matching to the route declaration syntax;
    • Dropped duplicated Router/Route methods everywhere;

    ...but the most controversial ones are probably:

    • Dropped query, header and custom matching;
    • Dropped support for regular expressions;

    The initial result is a much slimmer mux which, besides the elemental features (scheme, host, path and method matching), offers some conveniences not easily found in other packages (subrouting, URL building), and a damn high speed (teaser: it almost beats net/http in static matching and knocks out everybody else in matching with variables; and, oh, it doesn't make a single memory allocation for any match — zero, zip, zilch, nada).

    But this is not gorilla/mux. It is something else. Maybe it doesn't even exist! Well, maybe it does. Enough teasing for now: I'd like to hear your crazy ideas, frustrations and aspirations for gorilla/mux, and to ask how could we manage an eventual API breakage in the best possible way.

  • Better error messages

    Better error messages

    I opened bug #21, which was duplicate of #6 with more information. This pull request implements most of the suggestions from there. I didn't get fancy with the header missing requests, so I just ended up using 412 regardless of the headers (some headers, per the protocol, should use different codes, it seems).

    I have a good amount of tests in there, and a couple of helper methods. I have implemented a new NotFound method handler (the default was the one from net/http, which is still used if my code doesn't find an error match).

    I saw the response from @nesv about how it would take a lot of refactoring. I kind of skirted around that by using the context to store error messages/codes that the default NotFound() handler looks for. If those context variables aren't found, it defaults to the http.NotFound handler. I thought this would be ok since this package is already tied to gorilla/context since it has the clearing of the context in ServeHTTP.

    I hope this is accepted by the project. I think this is a good way of handling custom errors, and it seems to fit in with being able to write your own matchers, as custom matchers aren't excluded from being able to set context.

    If you notice that there is some code that shows up in the diff around clearing the context, it's because I have another pull request for optionally turning off context clearing. However, I forgot to make a topic branch before I did the implementation for that facepalm. I took that logic out of the code before I started this topic branch, and I double checked that the code from that pull request was taken out, but it still shows up in the diff.

    Please let me know if there are any changes you want from me for this pull request.

    Thanks!

  • Serve React Router

    Serve React Router

    Go Version: 1.12.

    Mux-Version: latest (not important)

    Iam able to serve a react app which I created with "create-react-app", but how I serve an app which is created with "create-react-app" and has react router in it? Is this possible?

  • Merging PR #169 breaks existing code

    Merging PR #169 breaks existing code

    This change seems to have the very unfortunate side effect of breaking code that stores any request context data keyed by a request's address (such as gorilla/context). I have code that sets a value using gorilla/context.Set, routes the request with gorilla/mux and then expects to be able to retrieve that value in my handlers with gorilla/context.Get. With this change, the call to Get returns nil because the request address changed when setVars/setCurrentRoute is called in Router.ServeHTTP. I'm assuming it should be possible to use gorilla/mux in combination with gorilla/context, so this seems like an issue to me.

  • URL encoded parameters not working

    URL encoded parameters not working

    if you have sth like Router.HandleFunc("/bla/{foo}", someHandler)

    the "foo" parameter cant be an URL-encoded string like "hmm%2Fstrange"

  • URL Encoded Parameters Still Not Working in Go 1.5.1

    URL Encoded Parameters Still Not Working in Go 1.5.1

    So as a follow up to #77, I'm running Go 1.5.1 which claims to have been the cause of that issue: https://github.com/gorilla/mux/issues/77#issuecomment-101772763 and I am still seeing issues with uriEncoded paths.

    Example:

        // router build
        router = mux.NewRouter()
        router.HandleFunc("/status", status).Methods("GET")
    
        apiRouter := mux.NewRouter()
        apiRouter.HandleFunc("/foo", getFooList).Methods("GET")
        apiRouter.HandleFunc("/foo/{bar}", getFoo).Methods("GET")
    
        router.PathPrefix("/foo").Handler(apiRouter)
    
        url := httptest.NewServer(router).URL
        url1 := strings.Join([]string{url, "/foo/bar%2ftest"}, "")
        // url2 := strings.Join([]string{url, "/foo/bar.test}", "")
    
        req, _ := http.NewRequest("GET", url1, nil)
    
        client := &http.Client{}
        req, err := client.Do(req)
        fmt.Printf("Req: %+v \n Err: %+v \n", req, err)
    

    If I use url1 I get a 404, if I use url2 I get a 200. Thoughts? I am using Ember-Data to generate API calls so short of performing a huge hack on both ends which doesn't seem reasonable.

  • Path variable not parsed

    Path variable not parsed

    Describe the bug

    A clear and concise description of what the bug is.

    I'm trying to return information about a specific user. Somehow the path variable is not recognised

    Versions

    Go version: 1.19 package version: run v1.8.0

    Steps to Reproduce

    How can the bug be triggered?

    1. Create a simple server with a path variable endpoint
    2. Return the path variable to the user from that handler

    NB When I replaced the id path variable with a specific nr (e.g. 123) then it returned a 400 error ("userId is required")

    Expected behavior

    What output or behaviour were you expecting instead?

    The router recognises the path variable.

    Code Snippets

    A minimum viable code snippet can be useful! (use backticks to format it).

    ENDPOINT /v1/user/{id}

    HANDLER

    func FindUserById() http.HandlerFunc {
    	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    		vars := mux.Vars(r)
    		id, ok := vars["id"]
    		if !ok {
    			w.WriteHeader(http.StatusBadRequest)
    			w.Write([]byte("userId is required"))
    			return
    		}
    ....
    		w.WriteHeader(http.StatusOK)
    	})
    }
    
  • [bug]

    [bug]

    Describe the bug

    A clear and concise description of what the bug is.

    Versions

    Go version: go version package version: run git rev-parse HEAD inside the repo

    Steps to Reproduce

    How can the bug be triggered?

    Expected behavior

    What output or behaviour were you expecting instead?

    Code Snippets

    A minimum viable code snippet can be useful! (use backticks to format it).

  • [feature]

    [feature]

    Is your feature request related to a problem? Please describe.

    A clear and concise description of what the problem is - e.g. "I'm always frustrated when [...]"

    Describe the solution you'd like

    What would the feature look like? How would it work? How would it change the API?

    Describe alternatives you've considered

    Are there alternatives you've tried, and/or workarounds in-place?

  • [question] How to retrieve the handler func without the middleware?

    [question] How to retrieve the handler func without the middleware?

    I'm trying to write a test to confirm that both the POST and PATCH methods of the same URL go to the same handler. So I tried the following

    var match mux.RouteMatch
    var handler http.Handler
    req := &http.Request{}
    req.URL = &url.URL{Path: "/post"}
    req.Method = "POST"
    if builder.router.Match(req, &match) {
        handler = match.Handler
    }
    g.Expect(handler).ToNot(BeNil())
    fmt.Println(handler)
    

    handler func is actually the handler func but wrapped in the MDW. Therefore, I couldn't assert that the handler was equal to my desired handler func

    Any tips ?

  • [bug] Adding to GET the same endpoint with POST and different Queries ends up with inconsistent error messages

    [bug] Adding to GET the same endpoint with POST and different Queries ends up with inconsistent error messages

    Describe the bug Adding the same endpoint POST with different Queries ends up with incosnsistent error messages

    Versions Go version: 1.18

    Steps to Reproduce Playground: https://go.dev/play/p/xzoAkpEhGgy

    // You can edit this code!
    // Click here and start typing.
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"net/http"
    	"net/http/httptest"
    	"time"
    
    	"github.com/gorilla/mux"
    )
    
    func main() {
    
    	r := mux.NewRouter()
    
    	r.HandleFunc("/api/v2", func(w http.ResponseWriter, r *http.Request) {
    		// an example API handler
    		fmt.Fprintf(w, "You made a POST request")
    		json.NewEncoder(w).Encode(map[string]bool{"ok": true})
    	}).Methods("POST")
    
    	r.HandleFunc("/api/v2", func(w http.ResponseWriter, r *http.Request) {
    		// an example API handler
    		fmt.Fprintf(w, "You made a GET request")
    		json.NewEncoder(w).Encode(map[string]bool{"ok": true})
    	}).
    		Queries("from", "{from:[0-9]+}",
    			"to", "{to:[0-9]+}").Methods("GET")
    
    
    	srv := &http.Server{
    		Handler: r,
    		Addr:    "127.0.0.1:8000",
    		// Good practice: enforce timeouts for servers you create!
    		WriteTimeout: 15 * time.Second,
    		ReadTimeout:  15 * time.Second,
    	}
    	go srv.ListenAndServe()
    
    
    	req2 := httptest.NewRequest("GET", "/api/v2?from=3&to=-5", nil)
    	out2 := httptest.NewRecorder()
    
    	r.ServeHTTP(out2, req2)
    
    	fmt.Println(out2.Code)
    	fmt.Println(out2)
    
    }
    

    ENDS with the result, I was expecting 404:

    405
    

    HOWEVER

    When I remove the POST request playground: https://go.dev/play/p/EXiF00_WrFW

    // You can edit this code!
    // Click here and start typing.
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"net/http"
    	"net/http/httptest"
    	"time"
    
    	"github.com/gorilla/mux"
    )
    
    func main() {
    
    	r := mux.NewRouter()
    
    	r.HandleFunc("/api/v2", func(w http.ResponseWriter, r *http.Request) {
    		// an example API handler
    		fmt.Fprintf(w, "You made a GET request")
    		json.NewEncoder(w).Encode(map[string]bool{"ok": true})
    	}).
    		Queries("from", "{from:[0-9]+}",
    			"to", "{to:[0-9]+}").Methods("GET")
    
    
    	srv := &http.Server{
    		Handler: r,
    		Addr:    "127.0.0.1:8000",
    		// Good practice: enforce timeouts for servers you create!
    		WriteTimeout: 15 * time.Second,
    		ReadTimeout:  15 * time.Second,
    	}
    	go srv.ListenAndServe()
    
    
    	req2 := httptest.NewRequest("GET", "/api/v2?from=3&to=-5", nil)
    	out2 := httptest.NewRecorder()
    
    	r.ServeHTTP(out2, req2)
    
    	fmt.Println(out2.Code)
    
    }
    

    Result is

     404
    

    Expected behavior Both cases should end in only 404-s

An extremely fast Go (golang) HTTP router that supports regular expression route matching. Comes with full support for building RESTful APIs.

ozzo-routing You may consider using go-rest-api to jumpstart your new RESTful applications with ozzo-routing. Description ozzo-routing is a Go package

Dec 31, 2022
Echo Inspired Stand Alone URL Router

Vestigo - A Standalone Golang URL Router Abstract Many fast Golang URL routers are often embedded inside frameworks. Vestigo is a stand alone url rout

Dec 1, 2022
xujiajun/gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework.

gorouter xujiajun/gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework. Motivation I wanted a sim

Dec 8, 2022
Go HTTP request router and web framework benchmark

Go HTTP Router Benchmark This benchmark suite aims to compare the performance of HTTP request routers for Go by implementing the routing structure of

Dec 27, 2022
Bxog is a simple and fast HTTP router for Go (HTTP request multiplexer).

Bxog is a simple and fast HTTP router for Go (HTTP request multiplexer). Usage An example of using the multiplexer: package main import ( "io" "net

Dec 26, 2022
:rotating_light: Is a lightweight, fast and extensible zero allocation HTTP router for Go used to create customizable frameworks.
:rotating_light: Is a lightweight, fast and extensible zero allocation HTTP router for Go used to create customizable frameworks.

LARS LARS is a fast radix-tree based, zero allocation, HTTP router for Go. view examples. If looking for a more pure Go solution, be sure to check out

Dec 27, 2022
:tongue: CleverGo is a lightweight, feature rich and high performance HTTP router for Go.

CleverGo CleverGo is a lightweight, feature rich and trie based high performance HTTP request router. go get -u clevergo.tech/clevergo English 简体中文 Fe

Nov 17, 2022
Fast and flexible HTTP router
Fast and flexible HTTP router

treemux - fast and flexible HTTP router Basic example Debug logging CORS example Error handling Rate limiting using Redis Gzip compression OpenTelemet

Dec 27, 2022
Fast, simple, and lightweight HTTP router for Golang

Sariaf Fast, simple and lightweight HTTP router for golang Install go get -u github.com/majidsajadi/sariaf Features Lightweight compatible with net/ht

Aug 19, 2022
Lightweight Router for Golang using net/http standard library with custom route parsing, handler and context.

Go-Lightweight-Router Lightweight Router for Golang using net/http standard library with custom route parsing, handler and context. Further developmen

Nov 3, 2021
Simple Golang HTTP router
Simple Golang HTTP router

Bellt Simple Golang HTTP router Bellt Package implements a request router with the aim of managing controller actions based on fixed and parameterized

Sep 27, 2022
FastRouter is a fast, flexible HTTP router written in Go.

FastRouter FastRouter is a fast, flexible HTTP router written in Go. FastRouter contains some customizable options, such as TrailingSlashesPolicy, Pan

Sep 27, 2022
Go Server/API micro framework, HTTP request router, multiplexer, mux
Go Server/API micro framework, HTTP request router, multiplexer, mux

?? gorouter Go Server/API micro framework, HTTP request router, multiplexer, mux. ?? ABOUT Contributors: Rafał Lorenz Want to contribute ? Feel free t

Dec 16, 2022
A high performance HTTP request router that scales well

HttpRouter HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go. In contrast to the

Dec 28, 2022
High-speed, flexible tree-based HTTP router for Go.

httptreemux High-speed, flexible, tree-based HTTP router for Go. This is inspired by Julien Schmidt's httprouter, in that it uses a patricia tree, but

Dec 28, 2022
Pure is a fast radix-tree based HTTP router
Pure is a fast radix-tree based HTTP router

package pure Pure is a fast radix-tree based HTTP router that sticks to the native implementations of Go's "net/http" package; in essence, keeping the

Dec 1, 2022
Go HTTP router

violetear Go HTTP router http://violetear.org Design Goals Keep it simple and small, avoiding extra complexity at all cost. KISS Support for static an

Dec 10, 2022
Simple router build on `net/http` supports custom middleWare.

XMUS-ROUTER Fast lightweight router build on net/http supports delegate and in url params. usage : Create new router using NewRouter() which need rout

Dec 27, 2021
Simple HTTP router for Go

ngamux Simple HTTP router for Go Installation Examples Todo Installation Run this command with correctly configured Go toolchain. go get github.com/ng

Dec 13, 2022