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

..03-May-2022-

.circleci/H30-Jun-2019-

.github/H30-Jun-2019-

AUTHORSH A D30-Jun-2019276

LICENSEH A D30-Jun-20191.5 KiB

README.mdH A D30-Jun-201922.1 KiB

bench_test.goH A D30-Jun-20191.4 KiB

context.goH A D30-Jun-2019315

context_test.goH A D30-Jun-2019673

doc.goH A D30-Jun-201911 KiB

example_authentication_middleware_test.goH A D30-Jun-20191.1 KiB

example_cors_method_middleware_test.goH A D30-Jun-20191.2 KiB

example_route_test.goH A D30-Jun-20191.7 KiB

go.modH A D30-Jun-201930

middleware.goH A D30-Jun-20192.6 KiB

middleware_test.goH A D30-Jun-201915.5 KiB

mux.goH A D30-Jun-201917.3 KiB

mux_test.goH A D30-Jun-201988.3 KiB

old_test.goH A D30-Jun-201917.1 KiB

regexp.goH A D30-Jun-20199.2 KiB

route.goH A D30-Jun-201920.1 KiB

test_helpers.goH A D30-Jun-2019758

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[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
5[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux)
6[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
7
8![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
9
10https://www.gorillatoolkit.org/pkg/mux
11
12Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
13their respective handler.
14
15The 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:
16
17* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
18* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
19* URL hosts, paths and query values can have variables with an optional regular expression.
20* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
21* 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.
22
23---
24
25* [Install](#install)
26* [Examples](#examples)
27* [Matching Routes](#matching-routes)
28* [Static Files](#static-files)
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### Registered URLs
216
217Now let's see how to build registered URLs.
218
219Routes 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:
220
221```go
222r := mux.NewRouter()
223r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
224  Name("article")
225```
226
227To 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:
228
229```go
230url, err := r.Get("article").URL("category", "technology", "id", "42")
231```
232
233...and the result will be a `url.URL` with the following path:
234
235```
236"/articles/technology/42"
237```
238
239This also works for host and query value variables:
240
241```go
242r := mux.NewRouter()
243r.Host("{subdomain}.example.com").
244  Path("/articles/{category}/{id:[0-9]+}").
245  Queries("filter", "{filter}").
246  HandlerFunc(ArticleHandler).
247  Name("article")
248
249// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
250url, err := r.Get("article").URL("subdomain", "news",
251                                 "category", "technology",
252                                 "id", "42",
253                                 "filter", "gorilla")
254```
255
256All 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.
257
258Regex support also exists for matching Headers within a route. For example, we could do:
259
260```go
261r.HeadersRegexp("Content-Type", "application/(text|json)")
262```
263
264...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
265
266There'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:
267
268```go
269// "http://news.example.com/"
270host, err := r.Get("article").URLHost("subdomain", "news")
271
272// "/articles/technology/42"
273path, err := r.Get("article").URLPath("category", "technology", "id", "42")
274```
275
276And if you use subrouters, host and path defined separately can be built as well:
277
278```go
279r := mux.NewRouter()
280s := r.Host("{subdomain}.example.com").Subrouter()
281s.Path("/articles/{category}/{id:[0-9]+}").
282  HandlerFunc(ArticleHandler).
283  Name("article")
284
285// "http://news.example.com/articles/technology/42"
286url, err := r.Get("article").URL("subdomain", "news",
287                                 "category", "technology",
288                                 "id", "42")
289```
290
291### Walking Routes
292
293The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
294the following prints all of the registered routes:
295
296```go
297package main
298
299import (
300	"fmt"
301	"net/http"
302	"strings"
303
304	"github.com/gorilla/mux"
305)
306
307func handler(w http.ResponseWriter, r *http.Request) {
308	return
309}
310
311func main() {
312	r := mux.NewRouter()
313	r.HandleFunc("/", handler)
314	r.HandleFunc("/products", handler).Methods("POST")
315	r.HandleFunc("/articles", handler).Methods("GET")
316	r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
317	r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
318	err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
319		pathTemplate, err := route.GetPathTemplate()
320		if err == nil {
321			fmt.Println("ROUTE:", pathTemplate)
322		}
323		pathRegexp, err := route.GetPathRegexp()
324		if err == nil {
325			fmt.Println("Path regexp:", pathRegexp)
326		}
327		queriesTemplates, err := route.GetQueriesTemplates()
328		if err == nil {
329			fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
330		}
331		queriesRegexps, err := route.GetQueriesRegexp()
332		if err == nil {
333			fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
334		}
335		methods, err := route.GetMethods()
336		if err == nil {
337			fmt.Println("Methods:", strings.Join(methods, ","))
338		}
339		fmt.Println()
340		return nil
341	})
342
343	if err != nil {
344		fmt.Println(err)
345	}
346
347	http.Handle("/", r)
348}
349```
350
351### Graceful Shutdown
352
353Go 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`:
354
355```go
356package main
357
358import (
359    "context"
360    "flag"
361    "log"
362    "net/http"
363    "os"
364    "os/signal"
365    "time"
366
367    "github.com/gorilla/mux"
368)
369
370func main() {
371    var wait time.Duration
372    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")
373    flag.Parse()
374
375    r := mux.NewRouter()
376    // Add your routes as needed
377
378    srv := &http.Server{
379        Addr:         "0.0.0.0:8080",
380        // Good practice to set timeouts to avoid Slowloris attacks.
381        WriteTimeout: time.Second * 15,
382        ReadTimeout:  time.Second * 15,
383        IdleTimeout:  time.Second * 60,
384        Handler: r, // Pass our instance of gorilla/mux in.
385    }
386
387    // Run our server in a goroutine so that it doesn't block.
388    go func() {
389        if err := srv.ListenAndServe(); err != nil {
390            log.Println(err)
391        }
392    }()
393
394    c := make(chan os.Signal, 1)
395    // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
396    // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
397    signal.Notify(c, os.Interrupt)
398
399    // Block until we receive our signal.
400    <-c
401
402    // Create a deadline to wait for.
403    ctx, cancel := context.WithTimeout(context.Background(), wait)
404    defer cancel()
405    // Doesn't block if no connections, but will otherwise wait
406    // until the timeout deadline.
407    srv.Shutdown(ctx)
408    // Optionally, you could run srv.Shutdown in a goroutine and block on
409    // <-ctx.Done() if your application should wait for other services
410    // to finalize based on context cancellation.
411    log.Println("shutting down")
412    os.Exit(0)
413}
414```
415
416### Middleware
417
418Mux 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.
419Middlewares 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.
420
421Mux middlewares are defined using the de facto standard type:
422
423```go
424type MiddlewareFunc func(http.Handler) http.Handler
425```
426
427Typically, 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.
428
429A very basic middleware which logs the URI of the request being handled could be written as:
430
431```go
432func loggingMiddleware(next http.Handler) http.Handler {
433    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
434        // Do stuff here
435        log.Println(r.RequestURI)
436        // Call the next handler, which can be another middleware in the chain, or the final handler.
437        next.ServeHTTP(w, r)
438    })
439}
440```
441
442Middlewares can be added to a router using `Router.Use()`:
443
444```go
445r := mux.NewRouter()
446r.HandleFunc("/", handler)
447r.Use(loggingMiddleware)
448```
449
450A more complex authentication middleware, which maps session token to users, could be written as:
451
452```go
453// Define our struct
454type authenticationMiddleware struct {
455	tokenUsers map[string]string
456}
457
458// Initialize it somewhere
459func (amw *authenticationMiddleware) Populate() {
460	amw.tokenUsers["00000000"] = "user0"
461	amw.tokenUsers["aaaaaaaa"] = "userA"
462	amw.tokenUsers["05f717e5"] = "randomUser"
463	amw.tokenUsers["deadbeef"] = "user0"
464}
465
466// Middleware function, which will be called for each request
467func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
468    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
469        token := r.Header.Get("X-Session-Token")
470
471        if user, found := amw.tokenUsers[token]; found {
472        	// We found the token in our map
473        	log.Printf("Authenticated user %s\n", user)
474        	// Pass down the request to the next middleware (or final handler)
475        	next.ServeHTTP(w, r)
476        } else {
477        	// Write an error and stop the handler chain
478        	http.Error(w, "Forbidden", http.StatusForbidden)
479        }
480    })
481}
482```
483
484```go
485r := mux.NewRouter()
486r.HandleFunc("/", handler)
487
488amw := authenticationMiddleware{}
489amw.Populate()
490
491r.Use(amw.Middleware)
492```
493
494Note: 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.
495
496### Handling CORS Requests
497
498[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.
499
500* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
501* 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
502* If you do not specify any methods, then:
503> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.
504
505Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:
506
507```go
508package main
509
510import (
511	"net/http"
512	"github.com/gorilla/mux"
513)
514
515func main() {
516    r := mux.NewRouter()
517
518    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
519    r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
520    r.Use(mux.CORSMethodMiddleware(r))
521
522    http.ListenAndServe(":8080", r)
523}
524
525func fooHandler(w http.ResponseWriter, r *http.Request) {
526    w.Header().Set("Access-Control-Allow-Origin", "*")
527    if r.Method == http.MethodOptions {
528        return
529    }
530
531    w.Write([]byte("foo"))
532}
533```
534
535And an request to `/foo` using something like:
536
537```bash
538curl localhost:8080/foo -v
539```
540
541Would look like:
542
543```bash
544*   Trying ::1...
545* TCP_NODELAY set
546* Connected to localhost (::1) port 8080 (#0)
547> GET /foo HTTP/1.1
548> Host: localhost:8080
549> User-Agent: curl/7.59.0
550> Accept: */*
551>
552< HTTP/1.1 200 OK
553< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
554< Access-Control-Allow-Origin: *
555< Date: Fri, 28 Jun 2019 20:13:30 GMT
556< Content-Length: 3
557< Content-Type: text/plain; charset=utf-8
558<
559* Connection #0 to host localhost left intact
560foo
561```
562
563### Testing Handlers
564
565Testing 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_.
566
567First, our simple HTTP handler:
568
569```go
570// endpoints.go
571package main
572
573func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
574    // A very simple health check.
575    w.Header().Set("Content-Type", "application/json")
576    w.WriteHeader(http.StatusOK)
577
578    // In the future we could report back on the status of our DB, or our cache
579    // (e.g. Redis) by performing a simple PING, and include them in the response.
580    io.WriteString(w, `{"alive": true}`)
581}
582
583func main() {
584    r := mux.NewRouter()
585    r.HandleFunc("/health", HealthCheckHandler)
586
587    log.Fatal(http.ListenAndServe("localhost:8080", r))
588}
589```
590
591Our test code:
592
593```go
594// endpoints_test.go
595package main
596
597import (
598    "net/http"
599    "net/http/httptest"
600    "testing"
601)
602
603func TestHealthCheckHandler(t *testing.T) {
604    // Create a request to pass to our handler. We don't have any query parameters for now, so we'll
605    // pass 'nil' as the third parameter.
606    req, err := http.NewRequest("GET", "/health", nil)
607    if err != nil {
608        t.Fatal(err)
609    }
610
611    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
612    rr := httptest.NewRecorder()
613    handler := http.HandlerFunc(HealthCheckHandler)
614
615    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
616    // directly and pass in our Request and ResponseRecorder.
617    handler.ServeHTTP(rr, req)
618
619    // Check the status code is what we expect.
620    if status := rr.Code; status != http.StatusOK {
621        t.Errorf("handler returned wrong status code: got %v want %v",
622            status, http.StatusOK)
623    }
624
625    // Check the response body is what we expect.
626    expected := `{"alive": true}`
627    if rr.Body.String() != expected {
628        t.Errorf("handler returned unexpected body: got %v want %v",
629            rr.Body.String(), expected)
630    }
631}
632```
633
634In the case that our routes have [variables](#examples), we can pass those in the request. We could write
635[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
636possible route variables as needed.
637
638```go
639// endpoints.go
640func main() {
641    r := mux.NewRouter()
642    // A route with a route variable:
643    r.HandleFunc("/metrics/{type}", MetricsHandler)
644
645    log.Fatal(http.ListenAndServe("localhost:8080", r))
646}
647```
648
649Our test file, with a table-driven test of `routeVariables`:
650
651```go
652// endpoints_test.go
653func TestMetricsHandler(t *testing.T) {
654    tt := []struct{
655        routeVariable string
656        shouldPass bool
657    }{
658        {"goroutines", true},
659        {"heap", true},
660        {"counters", true},
661        {"queries", true},
662        {"adhadaeqm3k", false},
663    }
664
665    for _, tc := range tt {
666        path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
667        req, err := http.NewRequest("GET", path, nil)
668        if err != nil {
669            t.Fatal(err)
670        }
671
672        rr := httptest.NewRecorder()
673
674	// Need to create a router that we can pass the request through so that the vars will be added to the context
675	router := mux.NewRouter()
676        router.HandleFunc("/metrics/{type}", MetricsHandler)
677        router.ServeHTTP(rr, req)
678
679        // In this case, our MetricsHandler returns a non-200 response
680        // for a route variable it doesn't know about.
681        if rr.Code == http.StatusOK && !tc.shouldPass {
682            t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
683                tc.routeVariable, rr.Code, http.StatusOK)
684        }
685    }
686}
687```
688
689## Full Example
690
691Here's a complete, runnable example of a small `mux` based server:
692
693```go
694package main
695
696import (
697    "net/http"
698    "log"
699    "github.com/gorilla/mux"
700)
701
702func YourHandler(w http.ResponseWriter, r *http.Request) {
703    w.Write([]byte("Gorilla!\n"))
704}
705
706func main() {
707    r := mux.NewRouter()
708    // Routes consist of a path and a handler function.
709    r.HandleFunc("/", YourHandler)
710
711    // Bind to a port and pass our router in
712    log.Fatal(http.ListenAndServe(":8000", r))
713}
714```
715
716## License
717
718BSD licensed. See the LICENSE file for details.
719