gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/mux/README.md (about) 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 9 https://www.gorillatoolkit.org/pkg/mux 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 http "gitee.com/ks-custle/core-gm/gmhttp" 229 "os" 230 "path/filepath" 231 "time" 232 233 "gitee.com/ks-custle/core-gm/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 // 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 279 func 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 304 Now let's see how to build registered URLs. 305 306 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: 307 308 ```go 309 r := mux.NewRouter() 310 r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 311 Name("article") 312 ``` 313 314 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: 315 316 ```go 317 url, 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 326 This also works for host and query value variables: 327 328 ```go 329 r := mux.NewRouter() 330 r.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" 337 url, err := r.Get("article").URL("subdomain", "news", 338 "category", "technology", 339 "id", "42", 340 "filter", "gorilla") 341 ``` 342 343 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. 344 345 Regex support also exists for matching Headers within a route. For example, we could do: 346 347 ```go 348 r.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 353 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: 354 355 ```go 356 // "http://news.example.com/" 357 host, err := r.Get("article").URLHost("subdomain", "news") 358 359 // "/articles/technology/42" 360 path, err := r.Get("article").URLPath("category", "technology", "id", "42") 361 ``` 362 363 And if you use subrouters, host and path defined separately can be built as well: 364 365 ```go 366 r := mux.NewRouter() 367 s := r.Host("{subdomain}.example.com").Subrouter() 368 s.Path("/articles/{category}/{id:[0-9]+}"). 369 HandlerFunc(ArticleHandler). 370 Name("article") 371 372 // "http://news.example.com/articles/technology/42" 373 url, err := r.Get("article").URL("subdomain", "news", 374 "category", "technology", 375 "id", "42") 376 ``` 377 378 ### Walking Routes 379 380 The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, 381 the following prints all of the registered routes: 382 383 ```go 384 package main 385 386 import ( 387 "fmt" 388 http "gitee.com/ks-custle/core-gm/gmhttp" 389 "strings" 390 391 "gitee.com/ks-custle/core-gm/mux" 392 ) 393 394 func handler(w http.ResponseWriter, r *http.Request) { 395 return 396 } 397 398 func 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 440 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`: 441 442 ```go 443 package main 444 445 import ( 446 "context" 447 "flag" 448 "log" 449 http "gitee.com/ks-custle/core-gm/gmhttp" 450 "os" 451 "os/signal" 452 "time" 453 454 "gitee.com/ks-custle/core-gm/mux" 455 ) 456 457 func 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 505 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. 506 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. 507 508 Mux middlewares are defined using the de facto standard type: 509 510 ```go 511 type MiddlewareFunc func(http.Handler) http.Handler 512 ``` 513 514 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. 515 516 A very basic middleware which logs the URI of the request being handled could be written as: 517 518 ```go 519 func 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 529 Middlewares can be added to a router using `Router.Use()`: 530 531 ```go 532 r := mux.NewRouter() 533 r.HandleFunc("/", handler) 534 r.Use(loggingMiddleware) 535 ``` 536 537 A more complex authentication middleware, which maps session token to users, could be written as: 538 539 ```go 540 // Define our struct 541 type authenticationMiddleware struct { 542 tokenUsers map[string]string 543 } 544 545 // Initialize it somewhere 546 func (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 554 func (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 572 r := mux.NewRouter() 573 r.HandleFunc("/", handler) 574 575 amw := authenticationMiddleware{} 576 amw.Populate() 577 578 r.Use(amw.Middleware) 579 ``` 580 581 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. 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 592 Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers: 593 594 ```go 595 package main 596 597 import ( 598 http "gitee.com/ks-custle/core-gm/gmhttp" 599 "gitee.com/ks-custle/core-gm/mux" 600 ) 601 602 func 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 612 func 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 622 And an request to `/foo` using something like: 623 624 ```bash 625 curl localhost:8080/foo -v 626 ``` 627 628 Would 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 647 foo 648 ``` 649 650 ### Testing Handlers 651 652 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_. 653 654 First, our simple HTTP handler: 655 656 ```go 657 // endpoints.go 658 package main 659 660 func 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 670 func main() { 671 r := mux.NewRouter() 672 r.HandleFunc("/health", HealthCheckHandler) 673 674 log.Fatal(http.ListenAndServe("localhost:8080", r)) 675 } 676 ``` 677 678 Our test code: 679 680 ```go 681 // endpoints_test.go 682 package main 683 684 import ( 685 http "gitee.com/ks-custle/core-gm/gmhttp" 686 "gitee.com/ks-custle/core-gm/gmhttp/httptest" 687 "testing" 688 ) 689 690 func 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 721 In 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 723 possible route variables as needed. 724 725 ```go 726 // endpoints.go 727 func 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 736 Our test file, with a table-driven test of `routeVariables`: 737 738 ```go 739 // endpoints_test.go 740 func 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 778 Here's a complete, runnable example of a small `mux` based server: 779 780 ```go 781 package main 782 783 import ( 784 http "gitee.com/ks-custle/core-gm/gmhttp" 785 "log" 786 "gitee.com/ks-custle/core-gm/mux" 787 ) 788 789 func YourHandler(w http.ResponseWriter, r *http.Request) { 790 w.Write([]byte("Gorilla!\n")) 791 } 792 793 func 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 805 BSD licensed. See the LICENSE file for details.