• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

.circleci/H11-Jul-2020-

.github/H11-Jul-2020-

AUTHORSH A D11-Jul-2020276

LICENSEH A D11-Jul-20201.5 KiB

README.mdH A D11-Jul-202024.8 KiB

bench_test.goH A D11-Jul-20201.4 KiB

doc.goH A D11-Jul-202011 KiB

example_authentication_middleware_test.goH A D11-Jul-20201.1 KiB

example_cors_method_middleware_test.goH A D11-Jul-20201.2 KiB

example_route_test.goH A D11-Jul-20201.7 KiB

go.modH A D11-Jul-202039

middleware.goH A D11-Jul-20202.6 KiB

middleware_test.goH A D11-Jul-202016.2 KiB

mux.goH A D11-Jul-202017.3 KiB

mux_httpserver_test.goH A D11-Jul-20201.3 KiB

mux_test.goH A D11-Jul-202090.4 KiB

old_test.goH A D11-Jul-202017.6 KiB

regexp.goH A D11-Jul-202010.3 KiB

regexp_test.goH A D11-Jul-20202.4 KiB

route.goH A D11-Jul-202021.2 KiB

test_helpers.goH A D11-Jul-2020766

README.md

1# gorilla/mux
2
3[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
4[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux)
5[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
6
7![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png)
8
9https://www.gorillatoolkit.org/pkg/mux
10
11Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
12their respective handler.
13
14The 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:
15
16* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
17* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
18* URL hosts, paths and query values can have variables with an optional regular expression.
19* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
20* 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.
21
22---
23
24* [Install](#install)
25* [Examples](#examples)
26* [Matching Routes](#matching-routes)
27* [Static Files](#static-files)
28* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)
29* [Registered URLs](#registered-urls)
30* [Walking Routes](#walking-routes)
31* [Graceful Shutdown](#graceful-shutdown)
32* [Middleware](#middleware)
33* [Handling CORS Requests](#handling-cors-requests)
34* [Testing Handlers](#testing-handlers)
35* [Full Example](#full-example)
36
37---
38
39## Install
40
41With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
42
43```sh
44go get -u github.com/gorilla/mux
45```
46
47## Examples
48
49Let's start registering a couple of URL paths and handlers:
50
51```go
52func main() {
53    r := mux.NewRouter()
54    r.HandleFunc("/", HomeHandler)
55    r.HandleFunc("/products", ProductsHandler)
56    r.HandleFunc("/articles", ArticlesHandler)
57    http.Handle("/", r)
58}
59```
60
61Here 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.
62
63Paths 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:
64
65```go
66r := mux.NewRouter()
67r.HandleFunc("/products/{key}", ProductHandler)
68r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
69r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
70```
71
72The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
73
74```go
75func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
76    vars := mux.Vars(r)
77    w.WriteHeader(http.StatusOK)
78    fmt.Fprintf(w, "Category: %v\n", vars["category"])
79}
80```
81
82And this is all you need to know about the basic usage. More advanced options are explained below.
83
84### Matching Routes
85
86Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
87
88```go
89r := mux.NewRouter()
90// Only matches if domain is "www.example.com".
91r.Host("www.example.com")
92// Matches a dynamic subdomain.
93r.Host("{subdomain:[a-z]+}.example.com")
94```
95
96There are several other matchers that can be added. To match path prefixes:
97
98```go
99r.PathPrefix("/products/")
100```
101
102...or HTTP methods:
103
104```go
105r.Methods("GET", "POST")
106```
107
108...or URL schemes:
109
110```go
111r.Schemes("https")
112```
113
114...or header values:
115
116```go
117r.Headers("X-Requested-With", "XMLHttpRequest")
118```
119
120...or query values:
121
122```go
123r.Queries("key", "value")
124```
125
126...or to use a custom matcher function:
127
128```go
129r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
130    return r.ProtoMajor == 0
131})
132```
133
134...and finally, it is possible to combine several matchers in a single route:
135
136```go
137r.HandleFunc("/products", ProductsHandler).
138  Host("www.example.com").
139  Methods("GET").
140  Schemes("http")
141```
142
143Routes are tested in the order they were added to the router. If two routes match, the first one wins:
144
145```go
146r := mux.NewRouter()
147r.HandleFunc("/specific", specificHandler)
148r.PathPrefix("/").Handler(catchAllHandler)
149```
150
151Setting 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".
152
153For 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:
154
155```go
156r := mux.NewRouter()
157s := r.Host("www.example.com").Subrouter()
158```
159
160Then register routes in the subrouter:
161
162```go
163s.HandleFunc("/products/", ProductsHandler)
164s.HandleFunc("/products/{key}", ProductHandler)
165s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
166```
167
168The 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.
169
170Subrouters 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.
171
172There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
173
174```go
175r := mux.NewRouter()
176s := r.PathPrefix("/products").Subrouter()
177// "/products/"
178s.HandleFunc("/", ProductsHandler)
179// "/products/{key}/"
180s.HandleFunc("/{key}/", ProductHandler)
181// "/products/{key}/details"
182s.HandleFunc("/{key}/details", ProductDetailsHandler)
183```
184
185
186### Static Files
187
188Note that the path provided to `PathPrefix()` represents a "wildcard": calling
189`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
190request that matches "/static/\*". This makes it easy to serve static files with mux:
191
192```go
193func main() {
194    var dir string
195
196    flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
197    flag.Parse()
198    r := mux.NewRouter()
199
200    // This will serve files under http://localhost:8000/static/<filename>
201    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
202
203    srv := &http.Server{
204        Handler:      r,
205        Addr:         "127.0.0.1:8000",
206        // Good practice: enforce timeouts for servers you create!
207        WriteTimeout: 15 * time.Second,
208        ReadTimeout:  15 * time.Second,
209    }
210
211    log.Fatal(srv.ListenAndServe())
212}
213```
214
215### Serving Single Page Applications
216
217Most of the time it makes sense to serve your SPA on a separate web server from your API,
218but sometimes it's desirable to serve them both from one place. It's possible to write a simple
219handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage
220mux's powerful routing for your API endpoints.
221
222```go
223package main
224
225import (
226	"encoding/json"
227	"log"
228	"net/http"
229	"os"
230	"path/filepath"
231	"time"
232
233	"github.com/gorilla/mux"
234)
235
236// spaHandler implements the http.Handler interface, so we can use it
237// to respond to HTTP requests. The path to the static directory and
238// path to the index file within that static directory are used to
239// serve the SPA in the given static directory.
240type spaHandler struct {
241	staticPath string
242	indexPath  string
243}
244
245// ServeHTTP inspects the URL path to locate a file within the static dir
246// on the SPA handler. If a file is found, it will be served. If not, the
247// file located at the index path on the SPA handler will be served. This
248// is suitable behavior for serving an SPA (single page application).
249func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
250    // get the absolute path to prevent directory traversal
251	path, err := filepath.Abs(r.URL.Path)
252	if err != nil {
253        // if we failed to get the absolute path respond with a 400 bad request
254        // and stop
255		http.Error(w, err.Error(), http.StatusBadRequest)
256		return
257	}
258
259    // prepend the path with the path to the static directory
260	path = filepath.Join(h.staticPath, path)
261
262    // check whether a file exists at the given path
263	_, err = os.Stat(path)
264	if os.IsNotExist(err) {
265		// file does not exist, serve index.html
266		http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
267		return
268	} else if err != nil {
269        // if we got an error (that wasn't that the file doesn't exist) stating the
270        // file, return a 500 internal server error and stop
271		http.Error(w, err.Error(), http.StatusInternalServerError)
272		return
273	}
274
275    // otherwise, use http.FileServer to serve the static dir
276	http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
277}
278
279func main() {
280	router := mux.NewRouter()
281
282	router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
283		// an example API handler
284		json.NewEncoder(w).Encode(map[string]bool{"ok": true})
285	})
286
287	spa := spaHandler{staticPath: "build", indexPath: "index.html"}
288	router.PathPrefix("/").Handler(spa)
289
290	srv := &http.Server{
291		Handler: router,
292		Addr:    "127.0.0.1:8000",
293		// Good practice: enforce timeouts for servers you create!
294		WriteTimeout: 15 * time.Second,
295		ReadTimeout:  15 * time.Second,
296	}
297
298	log.Fatal(srv.ListenAndServe())
299}
300```
301
302### Registered URLs
303
304Now let's see how to build registered URLs.
305
306Routes 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:
307
308```go
309r := mux.NewRouter()
310r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
311  Name("article")
312```
313
314To 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:
315
316```go
317url, err := r.Get("article").URL("category", "technology", "id", "42")
318```
319
320...and the result will be a `url.URL` with the following path:
321
322```
323"/articles/technology/42"
324```
325
326This also works for host and query value variables:
327
328```go
329r := mux.NewRouter()
330r.Host("{subdomain}.example.com").
331  Path("/articles/{category}/{id:[0-9]+}").
332  Queries("filter", "{filter}").
333  HandlerFunc(ArticleHandler).
334  Name("article")
335
336// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
337url, err := r.Get("article").URL("subdomain", "news",
338                                 "category", "technology",
339                                 "id", "42",
340                                 "filter", "gorilla")
341```
342
343All 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.
344
345Regex support also exists for matching Headers within a route. For example, we could do:
346
347```go
348r.HeadersRegexp("Content-Type", "application/(text|json)")
349```
350
351...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
352
353There'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:
354
355```go
356// "http://news.example.com/"
357host, err := r.Get("article").URLHost("subdomain", "news")
358
359// "/articles/technology/42"
360path, err := r.Get("article").URLPath("category", "technology", "id", "42")
361```
362
363And if you use subrouters, host and path defined separately can be built as well:
364
365```go
366r := mux.NewRouter()
367s := r.Host("{subdomain}.example.com").Subrouter()
368s.Path("/articles/{category}/{id:[0-9]+}").
369  HandlerFunc(ArticleHandler).
370  Name("article")
371
372// "http://news.example.com/articles/technology/42"
373url, err := r.Get("article").URL("subdomain", "news",
374                                 "category", "technology",
375                                 "id", "42")
376```
377
378### Walking Routes
379
380The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
381the following prints all of the registered routes:
382
383```go
384package main
385
386import (
387	"fmt"
388	"net/http"
389	"strings"
390
391	"github.com/gorilla/mux"
392)
393
394func handler(w http.ResponseWriter, r *http.Request) {
395	return
396}
397
398func main() {
399	r := mux.NewRouter()
400	r.HandleFunc("/", handler)
401	r.HandleFunc("/products", handler).Methods("POST")
402	r.HandleFunc("/articles", handler).Methods("GET")
403	r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
404	r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
405	err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
406		pathTemplate, err := route.GetPathTemplate()
407		if err == nil {
408			fmt.Println("ROUTE:", pathTemplate)
409		}
410		pathRegexp, err := route.GetPathRegexp()
411		if err == nil {
412			fmt.Println("Path regexp:", pathRegexp)
413		}
414		queriesTemplates, err := route.GetQueriesTemplates()
415		if err == nil {
416			fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
417		}
418		queriesRegexps, err := route.GetQueriesRegexp()
419		if err == nil {
420			fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
421		}
422		methods, err := route.GetMethods()
423		if err == nil {
424			fmt.Println("Methods:", strings.Join(methods, ","))
425		}
426		fmt.Println()
427		return nil
428	})
429
430	if err != nil {
431		fmt.Println(err)
432	}
433
434	http.Handle("/", r)
435}
436```
437
438### Graceful Shutdown
439
440Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
441
442```go
443package main
444
445import (
446    "context"
447    "flag"
448    "log"
449    "net/http"
450    "os"
451    "os/signal"
452    "time"
453
454    "github.com/gorilla/mux"
455)
456
457func main() {
458    var wait time.Duration
459    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")
460    flag.Parse()
461
462    r := mux.NewRouter()
463    // Add your routes as needed
464
465    srv := &http.Server{
466        Addr:         "0.0.0.0:8080",
467        // Good practice to set timeouts to avoid Slowloris attacks.
468        WriteTimeout: time.Second * 15,
469        ReadTimeout:  time.Second * 15,
470        IdleTimeout:  time.Second * 60,
471        Handler: r, // Pass our instance of gorilla/mux in.
472    }
473
474    // Run our server in a goroutine so that it doesn't block.
475    go func() {
476        if err := srv.ListenAndServe(); err != nil {
477            log.Println(err)
478        }
479    }()
480
481    c := make(chan os.Signal, 1)
482    // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
483    // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
484    signal.Notify(c, os.Interrupt)
485
486    // Block until we receive our signal.
487    <-c
488
489    // Create a deadline to wait for.
490    ctx, cancel := context.WithTimeout(context.Background(), wait)
491    defer cancel()
492    // Doesn't block if no connections, but will otherwise wait
493    // until the timeout deadline.
494    srv.Shutdown(ctx)
495    // Optionally, you could run srv.Shutdown in a goroutine and block on
496    // <-ctx.Done() if your application should wait for other services
497    // to finalize based on context cancellation.
498    log.Println("shutting down")
499    os.Exit(0)
500}
501```
502
503### Middleware
504
505Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
506Middlewares 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.
507
508Mux middlewares are defined using the de facto standard type:
509
510```go
511type MiddlewareFunc func(http.Handler) http.Handler
512```
513
514Typically, 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.
515
516A very basic middleware which logs the URI of the request being handled could be written as:
517
518```go
519func loggingMiddleware(next http.Handler) http.Handler {
520    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
521        // Do stuff here
522        log.Println(r.RequestURI)
523        // Call the next handler, which can be another middleware in the chain, or the final handler.
524        next.ServeHTTP(w, r)
525    })
526}
527```
528
529Middlewares can be added to a router using `Router.Use()`:
530
531```go
532r := mux.NewRouter()
533r.HandleFunc("/", handler)
534r.Use(loggingMiddleware)
535```
536
537A more complex authentication middleware, which maps session token to users, could be written as:
538
539```go
540// Define our struct
541type authenticationMiddleware struct {
542	tokenUsers map[string]string
543}
544
545// Initialize it somewhere
546func (amw *authenticationMiddleware) Populate() {
547	amw.tokenUsers["00000000"] = "user0"
548	amw.tokenUsers["aaaaaaaa"] = "userA"
549	amw.tokenUsers["05f717e5"] = "randomUser"
550	amw.tokenUsers["deadbeef"] = "user0"
551}
552
553// Middleware function, which will be called for each request
554func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
555    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
556        token := r.Header.Get("X-Session-Token")
557
558        if user, found := amw.tokenUsers[token]; found {
559        	// We found the token in our map
560        	log.Printf("Authenticated user %s\n", user)
561        	// Pass down the request to the next middleware (or final handler)
562        	next.ServeHTTP(w, r)
563        } else {
564        	// Write an error and stop the handler chain
565        	http.Error(w, "Forbidden", http.StatusForbidden)
566        }
567    })
568}
569```
570
571```go
572r := mux.NewRouter()
573r.HandleFunc("/", handler)
574
575amw := authenticationMiddleware{}
576amw.Populate()
577
578r.Use(amw.Middleware)
579```
580
581Note: 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.
582
583### Handling CORS Requests
584
585[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.
586
587* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
588* 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
589* If you do not specify any methods, then:
590> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.
591
592Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:
593
594```go
595package main
596
597import (
598	"net/http"
599	"github.com/gorilla/mux"
600)
601
602func main() {
603    r := mux.NewRouter()
604
605    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
606    r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
607    r.Use(mux.CORSMethodMiddleware(r))
608
609    http.ListenAndServe(":8080", r)
610}
611
612func fooHandler(w http.ResponseWriter, r *http.Request) {
613    w.Header().Set("Access-Control-Allow-Origin", "*")
614    if r.Method == http.MethodOptions {
615        return
616    }
617
618    w.Write([]byte("foo"))
619}
620```
621
622And an request to `/foo` using something like:
623
624```bash
625curl localhost:8080/foo -v
626```
627
628Would look like:
629
630```bash
631*   Trying ::1...
632* TCP_NODELAY set
633* Connected to localhost (::1) port 8080 (#0)
634> GET /foo HTTP/1.1
635> Host: localhost:8080
636> User-Agent: curl/7.59.0
637> Accept: */*
638>
639< HTTP/1.1 200 OK
640< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
641< Access-Control-Allow-Origin: *
642< Date: Fri, 28 Jun 2019 20:13:30 GMT
643< Content-Length: 3
644< Content-Type: text/plain; charset=utf-8
645<
646* Connection #0 to host localhost left intact
647foo
648```
649
650### Testing Handlers
651
652Testing 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_.
653
654First, our simple HTTP handler:
655
656```go
657// endpoints.go
658package main
659
660func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
661    // A very simple health check.
662    w.Header().Set("Content-Type", "application/json")
663    w.WriteHeader(http.StatusOK)
664
665    // In the future we could report back on the status of our DB, or our cache
666    // (e.g. Redis) by performing a simple PING, and include them in the response.
667    io.WriteString(w, `{"alive": true}`)
668}
669
670func main() {
671    r := mux.NewRouter()
672    r.HandleFunc("/health", HealthCheckHandler)
673
674    log.Fatal(http.ListenAndServe("localhost:8080", r))
675}
676```
677
678Our test code:
679
680```go
681// endpoints_test.go
682package main
683
684import (
685    "net/http"
686    "net/http/httptest"
687    "testing"
688)
689
690func TestHealthCheckHandler(t *testing.T) {
691    // Create a request to pass to our handler. We don't have any query parameters for now, so we'll
692    // pass 'nil' as the third parameter.
693    req, err := http.NewRequest("GET", "/health", nil)
694    if err != nil {
695        t.Fatal(err)
696    }
697
698    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
699    rr := httptest.NewRecorder()
700    handler := http.HandlerFunc(HealthCheckHandler)
701
702    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
703    // directly and pass in our Request and ResponseRecorder.
704    handler.ServeHTTP(rr, req)
705
706    // Check the status code is what we expect.
707    if status := rr.Code; status != http.StatusOK {
708        t.Errorf("handler returned wrong status code: got %v want %v",
709            status, http.StatusOK)
710    }
711
712    // Check the response body is what we expect.
713    expected := `{"alive": true}`
714    if rr.Body.String() != expected {
715        t.Errorf("handler returned unexpected body: got %v want %v",
716            rr.Body.String(), expected)
717    }
718}
719```
720
721In the case that our routes have [variables](#examples), we can pass those in the request. We could write
722[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
723possible route variables as needed.
724
725```go
726// endpoints.go
727func main() {
728    r := mux.NewRouter()
729    // A route with a route variable:
730    r.HandleFunc("/metrics/{type}", MetricsHandler)
731
732    log.Fatal(http.ListenAndServe("localhost:8080", r))
733}
734```
735
736Our test file, with a table-driven test of `routeVariables`:
737
738```go
739// endpoints_test.go
740func TestMetricsHandler(t *testing.T) {
741    tt := []struct{
742        routeVariable string
743        shouldPass bool
744    }{
745        {"goroutines", true},
746        {"heap", true},
747        {"counters", true},
748        {"queries", true},
749        {"adhadaeqm3k", false},
750    }
751
752    for _, tc := range tt {
753        path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
754        req, err := http.NewRequest("GET", path, nil)
755        if err != nil {
756            t.Fatal(err)
757        }
758
759        rr := httptest.NewRecorder()
760
761	// Need to create a router that we can pass the request through so that the vars will be added to the context
762	router := mux.NewRouter()
763        router.HandleFunc("/metrics/{type}", MetricsHandler)
764        router.ServeHTTP(rr, req)
765
766        // In this case, our MetricsHandler returns a non-200 response
767        // for a route variable it doesn't know about.
768        if rr.Code == http.StatusOK && !tc.shouldPass {
769            t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
770                tc.routeVariable, rr.Code, http.StatusOK)
771        }
772    }
773}
774```
775
776## Full Example
777
778Here's a complete, runnable example of a small `mux` based server:
779
780```go
781package main
782
783import (
784    "net/http"
785    "log"
786    "github.com/gorilla/mux"
787)
788
789func YourHandler(w http.ResponseWriter, r *http.Request) {
790    w.Write([]byte("Gorilla!\n"))
791}
792
793func main() {
794    r := mux.NewRouter()
795    // Routes consist of a path and a handler function.
796    r.HandleFunc("/", YourHandler)
797
798    // Bind to a port and pass our router in
799    log.Fatal(http.ListenAndServe(":8000", r))
800}
801```
802
803## License
804
805BSD licensed. See the LICENSE file for details.
806