gorilla/mux
GitHub: gorilla/mux
一个功能强大的 Go 语言 HTTP 路由器和 URL 匹配器,用于将传入的 Web 请求精准分发到对应的处理函数。
Stars: 21835 | Forks: 1889
# gorilla/mux

[](https://codecov.io/github/gorilla/mux)
[](https://godoc.org/github.com/gorilla/mux)
[](https://sourcegraph.com/github/gorilla/mux?badge)

包 `gorilla/mux` 实现了一个请求路由器和分发器,用于将传入的请求匹配到它们各自的 handler。
mux 这个名字代表“HTTP 请求多路复用器”。与标准的 `http.ServeMux` 一样,`mux.Router` 会根据已注册的路由列表匹配传入的请求,并为匹配 URL 或其他条件的路由调用 handler。主要特性包括:
* 它实现了 `http.Handler` 接口,因此与标准的 `http.ServeMux` 兼容。
* 可以根据 URL 主机、路径、路径前缀、协议 (scheme)、header 和 query 值、HTTP 方法或使用自定义匹配器来匹配请求。
* URL 主机、路径和 query 值可以包含带有可选正则表达式的变量。
* 可以构建或“反转”已注册的 URL,这有助于维护对资源的引用。
* 路由可以用作子路由器:只有在父路由匹配时,才会对嵌套路由进行测试。这对于定义共享公共条件(如主机、路径前缀或其他重复属性)的路由组非常有用。作为额外的好处,这优化了请求匹配。
* [安装](#install)
* [示例](#examples)
* [匹配路由](#matching-routes)
* [静态文件](#static-files)
* [服务单页应用](#serving-single-page-applications)(例如 React、Vue、Ember.js 等)
* [已注册的 URL](#registered-urls)
* [遍历路由](#walking-routes)
* [优雅关闭](#graceful-shutdown)
* [中间件](#middleware)
* [处理 CORS 请求](#handling-cors-requests)
* [测试 Handlers](#testing-handlers)
* [完整示例](#full-example)
## 安装
拥有一个[正确配置](https://golang.org/doc/install#testing)的 Go 工具链后:
```
go get -u github.com/gorilla/mux
```
## 示例
让我们从注册几个 URL 路径和 handler 开始:
```
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", r)
}
```
这里我们注册了三个将 URL 路径映射到 handler 的路由。这相当于 `http.HandleFunc()` 的工作方式:如果传入的请求 URL 匹配其中一个路径,就会调用相应的 handler,并传入 (`http.ResponseWriter`, `*http.Request`) 作为参数。
路径可以包含变量。它们使用 `{name}` 或 `{name:pattern}` 格式来定义。如果没有定义正则表达式模式,匹配的变量将是直到下一个斜杠之前的任何内容。例如:
```
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
```
这些名称用于创建路由变量的映射 (map),可以通过调用 `mux.Vars()` 来获取:
```
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Category: %v\n", vars["category"])
}
```
以上就是关于基本用法你需要了解的全部内容。更多高级选项在下面进行说明。
### 匹配路由
路由也可以被限制在某个域名或子域名上。只需定义一个要匹配的主机模式即可。它们同样可以包含变量:
```
r := mux.NewRouter()
// Only matches if domain is "www.example.com".
r.Host("www.example.com")
// Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.example.com")
```
还可以添加几个其他的匹配器。匹配路径前缀:
```
r.PathPrefix("/products/")
```
...或者 HTTP 方法:
```
r.Methods("GET", "POST")
```
...或者 URL scheme:
```
r.Schemes("https")
```
...或者 header 值:
```
r.Headers("X-Requested-With", "XMLHttpRequest")
```
...或者 query 值:
```
r.Queries("key", "value")
```
...或者使用自定义匹配器函数:
```
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0
})
```
...最后,还可以在单个路由中组合多个匹配器:
```
r.HandleFunc("/products", ProductsHandler).
Host("www.example.com").
Methods("GET").
Schemes("http")
```
路由按照它们被添加到路由器的顺序进行测试。如果两个路由匹配,第一个胜出:
```
r := mux.NewRouter()
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)
```
一遍又一遍地设置相同的匹配条件可能会令人厌烦,因此我们提供了一种方法来将共享相同要求的几个路由进行分组。我们称之为“子路由”。
例如,假设我们有几个只有在主机是 `www.example.com` 时才应该匹配的 URL。为该主机创建一个路由,并从中获取一个“子路由器”:
```
r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()
```
然后在子路由器中注册路由:
```
s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
```
只有在域名为 `www.example.com` 时,才会对我们上面注册的三个 URL 路径进行测试,因为会首先测试子路由器。这不仅方便,还优化了请求匹配。你可以结合路由接受的任何属性匹配器来创建子路由器。
子路由器可用于创建域名或路径“命名空间”:你在一个中心位置定义子路由器,然后应用程序的各个部分可以注册相对于给定子路由器的路径。
关于子路由还有一件事。当子路由器具有路径前缀时,内部路由会将其作为路径的基础:
```
r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()
// "/products/"
s.HandleFunc("/", ProductsHandler)
// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```
### 静态文件
请注意,提供给 `PathPrefix()` 的路径代表一个“通配符”:调用
`PathPrefix("/static/").Handler(...)` 意味着该 handler 将接收并处理
任何匹配 "/static/\*" 的请求。这使得使用 mux 提供 (serve) 静态文件变得非常容易:
```
func main() {
var dir string
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
flag.Parse()
r := mux.NewRouter()
// This will serve files under http://localhost:8000/static/
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
```
### 服务单页应用
大多数情况下,将你的 SPA 部署在与 API 不同的独立 Web 服务器上是有意义的,
但有时也希望能从同一个地方同时提供它们。你可以编写一个简单的
handler 来提供你的 SPA(例如与 React Router 的 [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) 一起使用),并利用
mux 强大的路由功能来处理你的 API endpoint。
```
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/gorilla/mux"
)
// spaHandler implements the http.Handler interface, so we can use it
// to respond to HTTP requests. The path to the static directory and
// path to the index file within that static directory are used to
// serve the SPA in the given static directory.
type spaHandler struct {
staticPath string
indexPath string
}
// ServeHTTP inspects the URL path to locate a file within the static dir
// on the SPA handler. If a file is found, it will be served. If not, the
// file located at the index path on the SPA handler will be served. This
// is suitable behavior for serving an SPA (single page application).
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Join internally call path.Clean to prevent directory traversal
path := filepath.Join(h.staticPath, r.URL.Path)
// check whether a file exists or is a directory at the given path
fi, err := os.Stat(path)
if os.IsNotExist(err) || fi.IsDir() {
// file does not exist or path is a directory, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
return
}
if err != nil {
// if we got an error (that wasn't that the file doesn't exist) stating the
// file, return a 500 internal server error and stop
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// otherwise, use http.FileServer to serve the static file
http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
// an example API handler
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
})
spa := spaHandler{staticPath: "build", indexPath: "index.html"}
router.PathPrefix("/").Handler(spa)
srv := &http.Server{
Handler: router,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
```
### 已注册的 URL
现在让我们看看如何构建已注册的 URL。
路由可以被命名。所有定义了名称的路由都可以构建或“反转”其 URL。我们通过在路由上调用 `Name()` 来定义名称。例如:
```
r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
Name("article")
```
要构建一个 URL,需获取该路由并调用 `URL()` 方法,为路由变量传入一系列键值对。对于上面的路由,我们会这样做:
```
url, err := r.Get("article").URL("category", "technology", "id", "42")
```
...结果将是一个具有以下路径的 `url.URL`:
```
"/articles/technology/42"
```
这也适用于主机和 query 值变量:
```
r := mux.NewRouter()
r.Host("{subdomain}.example.com").
Path("/articles/{category}/{id:[0-9]+}").
Queries("filter", "{filter}").
HandlerFunc(ArticleHandler).
Name("article")
// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42",
"filter", "gorilla")
```
路由中定义的所有变量都是必需的,并且它们的值必须符合相应的模式。这些要求保证了生成的 URL 将始终匹配已注册的路由 —— 唯一的例外是显式定义的“仅用于构建 (build-only)”路由,它们永远不会被匹配。
```
r.HeadersRegexp("Content-Type", "application/(text|json)")
```
...该路由将同时匹配 Content-Type 为 `application/json` 以及 `application/text` 的请求。
还有一种方法可以仅构建路由的 URL 主机或路径:改用 `URLHost()` 或 `URLPath()` 方法。对于前面的路由,我们会这样做:
```
// "http://news.example.com/"
host, err := r.Get("article").URLHost("subdomain", "news")
// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
```
如果你使用子路由器,单独定义的主机和路径也可以被构建:
```
r := mux.NewRouter()
s := r.Host("{subdomain}.example.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
HandlerFunc(ArticleHandler).
Name("article")
// "http://news.example.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42")
```
要在调用 `URL()` 时查找给定路由所需的所有变量,可以使用 `GetVarNames()` 方法:
```
r := mux.NewRouter()
r.Host("{domain}").
Path("/{group}/{item_id}").
Queries("some_data1", "{some_data1}").
Queries("some_data2", "{some_data2}").
Name("article")
// Will print [domain group item_id some_data1 some_data2]
fmt.Println(r.Get("article").GetVarNames())
```
### 遍历路由
`mux.Router` 上的 `Walk` 函数可用于访问注册到路由器上的所有路由。例如,
以下代码打印出所有已注册的路由:
```
package main
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
return
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.HandleFunc("/products", handler).Methods("POST")
r.HandleFunc("/articles", handler).Methods("GET")
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
pathTemplate, err := route.GetPathTemplate()
if err == nil {
fmt.Println("ROUTE:", pathTemplate)
}
pathRegexp, err := route.GetPathRegexp()
if err == nil {
fmt.Println("Path regexp:", pathRegexp)
}
queriesTemplates, err := route.GetQueriesTemplates()
if err == nil {
fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
}
queriesRegexps, err := route.GetQueriesRegexp()
if err == nil {
fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
}
methods, err := route.GetMethods()
if err == nil {
fmt.Println("Methods:", strings.Join(methods, ","))
}
fmt.Println()
return nil
})
if err != nil {
fmt.Println(err)
}
http.Handle("/", r)
}
```
### 优雅关闭
Go 1.8 引入了[优雅关闭](https://golang.org/doc/go1.8#http_shutdown) `*http.Server` 的功能。以下是如何结合 `mux` 来实现这一点:
```
package main
import (
"context"
"flag"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gorilla/mux"
)
func main() {
var wait time.Duration
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")
flag.Parse()
r := mux.NewRouter()
// Add your routes as needed
srv := &http.Server{
Addr: "0.0.0.0:8080",
// Good practice to set timeouts to avoid Slowloris attacks.
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r, // Pass our instance of gorilla/mux in.
}
// Run our server in a goroutine so that it doesn't block.
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
}()
c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
// Block until we receive our signal.
<-c
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
log.Println("shutting down")
os.Exit(0)
}
```
### 中间件
Mux 支持向一个 [Router](https://godoc.org/github.com/gorilla/mux#Router) 添加中间件,如果找到匹配项,中间件将按添加顺序执行,包括其子路由器。
中间件(通常)是一小段代码,它接收一个请求,对其进行某些处理,然后将其传递给另一个中间件或最终的 handler。中间件的一些常见用例是请求日志记录、header 操作或 `ResponseWriter` 劫持。
Mux 中间件使用事实上的标准类型来定义:
```
type MiddlewareFunc func(http.Handler) http.Handler
```
通常,返回的 handler 是一个闭包,它对传递给它的 http.ResponseWriter 和 http.Request 进行某些处理,然后调用作为参数传递给 MiddlewareFunc 的 handler。这利用了闭包能够访问其创建上下文中的变量的特性,同时保留了接收者强制要求的签名。
一个用于记录正在处理的请求 URI 的非常基本的中间件可以这样编写:
```
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println(r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
```
可以使用 `Router.Use()` 将中间件添加到路由器:
```
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)
```
一个更复杂的、将 session token 映射到用户的身份验证中间件可以这样编写:
```
// Define our struct
type authenticationMiddleware struct {
tokenUsers map[string]string
}
// Initialize it somewhere
func (amw *authenticationMiddleware) Populate() {
amw.tokenUsers["00000000"] = "user0"
amw.tokenUsers["aaaaaaaa"] = "userA"
amw.tokenUsers["05f717e5"] = "randomUser"
amw.tokenUsers["deadbeef"] = "user0"
}
// Middleware function, which will be called for each request
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Session-Token")
if user, found := amw.tokenUsers[token]; found {
// We found the token in our map
log.Printf("Authenticated user %s\n", user)
// Pass down the request to the next middleware (or final handler)
next.ServeHTTP(w, r)
} else {
// Write an error and stop the handler chain
http.Error(w, "Forbidden", http.StatusForbidden)
}
})
}
```
```
r := mux.NewRouter()
r.HandleFunc("/", handler)
amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
amw.Populate()
r.Use(amw.Middleware)
```
注意:如果你的中间件没有调用 `next.ServeHTTP()` 并传入相应的参数,handler 链将会停止。如果中间件编写者希望中止请求,可以使用此方法。如果中间件 _确实_ 要终止请求,它 _应该_ 写入 `ResponseWriter`;如果它 _不_ 打算终止请求,则 _不应_ 写入 `ResponseWriter`。
### 处理 CORS 请求
[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) 旨在让严格设置 `Access-Control-Allow-Methods` 响应 header 变得更容易。
* 你仍然需要使用自己的 CORS handler 来设置其他的 CORS header,例如 `Access-Control-Allow-Origin`
* 中间件会将 `Access-Control-Allow-Methods` header 设置为路由上所有的匹配方法(例如 `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`)
* 如果你没有指定任何方法,那么:
这是一个使用 `CORSMethodMiddleware` 以及自定义 `OPTIONS` handler 来设置所有必需的 CORS header 的示例:
```
package main
import (
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
r.Use(mux.CORSMethodMiddleware(r))
http.ListenAndServe(":8080", r)
}
func fooHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == http.MethodOptions {
return
}
w.Write([]byte("foo"))
}
```
使用类似以下方式向 `/foo` 发出请求:
```
curl localhost:8080/foo -v
```
看起来会像这样:
```
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /foo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.59.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
< Access-Control-Allow-Origin: *
< Date: Fri, 28 Jun 2019 20:13:30 GMT
< Content-Length: 3
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
foo
```
### 测试 Handlers
在 Go Web 应用程序中测试 handler 非常简单,而且 _mux_ 也不会使其变得更加复杂。假设有两个文件:`endpoints.go` 和 `endpoints_test.go`,下面是我们如何测试一个使用 _mux_ 的应用程序。
首先,我们简单的 HTTP handler:
```
// endpoints.go
package main
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
// A very simple health check.
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// In the future we could report back on the status of our DB, or our cache
// (e.g. Redis) by performing a simple PING, and include them in the response.
io.WriteString(w, `{"alive": true}`)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/health", HealthCheckHandler)
log.Fatal(http.ListenAndServe("localhost:8080", r))
}
```
我们的测试代码:
```
// endpoints_test.go
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHealthCheckHandler(t *testing.T) {
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
// pass 'nil' as the third parameter.
req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(HealthCheckHandler)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
expected := `{"alive": true}`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}
```
如果我们的路由包含[变量](#examples),我们可以在请求中传递它们。我们可以编写
[表驱动测试](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) 来根据需要测试
多种可能的路由变量。
```
// endpoints.go
func main() {
r := mux.NewRouter()
// A route with a route variable:
r.HandleFunc("/metrics/{type}", MetricsHandler)
log.Fatal(http.ListenAndServe("localhost:8080", r))
}
```
我们的测试文件,包含针对 `routeVariables` 的表驱动测试:
```
// endpoints_test.go
func TestMetricsHandler(t *testing.T) {
tt := []struct{
routeVariable string
shouldPass bool
}{
{"goroutines", true},
{"heap", true},
{"counters", true},
{"queries", true},
{"adhadaeqm3k", false},
}
for _, tc := range tt {
path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
req, err := http.NewRequest("GET", path, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
// To add the vars to the context,
// we need to create a router through which we can pass the request.
router := mux.NewRouter()
router.HandleFunc("/metrics/{type}", MetricsHandler)
router.ServeHTTP(rr, req)
// In this case, our MetricsHandler returns a non-200 response
// for a route variable it doesn't know about.
if rr.Code == http.StatusOK && !tc.shouldPass {
t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
tc.routeVariable, rr.Code, http.StatusOK)
}
}
}
```
## 完整示例
这是一个基于 `mux` 的小型服务器的完整、可运行的示例:
```
package main
import (
"net/http"
"log"
"github.com/gorilla/mux"
)
func YourHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Gorilla!\n"))
}
func main() {
r := mux.NewRouter()
// Routes consist of a path and a handler function.
r.HandleFunc("/", YourHandler)
// Bind to a port and pass our router in
log.Fatal(http.ListenAndServe(":8000", r))
}
```
## License
BSD licensed。有关详细信息,请参阅 LICENSE 文件。
标签:EVTX分析, 日志审计