github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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/hxx258456/ccgo/gmhttp"
   229  	"os"
   230  	"path/filepath"
   231  	"time"
   232  
   233  	"gitee.com/hxx258456/ccgo/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/hxx258456/ccgo/gmhttp"
   389  	"strings"
   390  
   391  	"gitee.com/hxx258456/ccgo/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/hxx258456/ccgo/gmhttp"
   450      "os"
   451      "os/signal"
   452      "time"
   453  
   454      "gitee.com/hxx258456/ccgo/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/hxx258456/ccgo/gmhttp"
   599  	"gitee.com/hxx258456/ccgo/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/hxx258456/ccgo/gmhttp"
   686      "gitee.com/hxx258456/ccgo/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/hxx258456/ccgo/gmhttp"
   785      "log"
   786      "gitee.com/hxx258456/ccgo/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.