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