パッケージは、着信要求を照合するための要求ルーターとディスパッチャーを実装します それぞれのハンドラー。
gorilla/mux
mux という名前は "HTTP 要求マルチプレクサ" を表します。標準と同様に、受信要求を登録済みルートのリストと照合し、URL またはその他の条件に一致するルートのハンドラーを呼び出します。主な機能は次のとおりです。
http.ServeMux
mux.Router
http.Handler
http.ServeMux
正しく構成されたGoツールチェーンの場合:
go get -u github.com/gorilla/mux
いくつかの URL パスとハンドラーの登録を始めましょう。
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", r)
}
ここでは、URL パスをハンドラーにマッピングする 3 つのルートを登録します。これは、着信要求 URL がパスの 1 つと一致する場合、対応するハンドラーがパラメーターとして渡す (, ) と呼ばれます。
http.HandleFunc()
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)
名前は、以下を呼び出すことができるルート変数のマップを作成するために使用されます。
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 スキーム:
r.Schemes("https")
...またはヘッダー値:
r.Headers("X-Requested-With", "XMLHttpRequest")
...またはクエリ値:
r.Queries("key", "value")
...またはカスタムマッチャー関数を使用するには:
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0
})
...そして最後に、複数のマッチャーを1つのルートに組み合わせることができます。
r.HandleFunc("/products", ProductsHandler).
Host("www.example.com").
Methods("GET").
Schemes("http")
ルートは、ルータに追加された順序でテストされます。2 つのルートが一致する場合、最初のルートが優先されます。
r := mux.NewRouter()
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)
同じマッチング条件を何度も設定するのは退屈な場合があるため、同じ要件を共有する複数のルートをグループ化する方法があります。これを「サブルーティング」と呼びます。
たとえば、ホストが である場合にのみ一致するはずのURLがいくつかあるとします。そのホストのルートを作成し、そこから「サブルーター」を取得します。
www.example.com
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)
上記で登録した3つのURLパスは、サブルーターが最初にテストされるため、ドメインが 、である場合にのみテストされます。これは便利なだけでなく、リクエストマッチングも最適化します。ルートで受け入れられる属性マッチャーを組み合わせたサブルーターを作成できます。
www.example.com
サブルーターは、ドメインまたはパスの「名前空間」を作成するために使用できます:中央の場所でサブルーターを定義し、アプリの一部が特定のサブルーターに相対的にパスを登録できます。
サブルートについてもう1つあります。サブルータにパス プレフィックスがある場合、内部ルートはそれをパスのベースとして使用します。
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)
指定されたパスは「ワイルドカード」を表すことに注意してください:呼び出しは、ハンドラーが渡されることを意味します "/static/*" に一致する要求。これにより、muxで静的ファイルを簡単に提供できます。
PathPrefix()
PathPrefix("/static/").Handler(...)
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/<filename>
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())
}
ほとんどの場合、APIとは別のWebサーバーでSPAを提供することは理にかなっています。 ただし、両方を1か所から提供することが望ましい場合もあります。簡単なものを書くことは可能です SPAを提供するためのハンドラー(たとえば、ReactルーターのBrowserRouterで使用するため)、およびレバレッジ APIエンドポイントに対するmuxの強力なルーティング。
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) {
// get the absolute path to prevent directory traversal
path, err := filepath.Abs(r.URL.Path)
if err != nil {
// if we failed to get the absolute path respond with a 400 bad request
// and stop
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// prepend the path with the path to the static directory
path = filepath.Join(h.staticPath, path)
// check whether a file exists at the given path
_, err = os.Stat(path)
if os.IsNotExist(err) {
// file does not exist, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
return
} else 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 dir
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 を構築するか、"反転" することができます。ルート上で呼び出す名前を定義します。例えば:
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"
これは、ホスト変数とクエリ値変数でも機能します。
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が常に登録されたルートと一致することを保証します - 唯一の例外は、明示的に定義された「ビルド専用」ルートであり、決して一致しません。
正規表現のサポートは、ルート内のヘッダーを照合するためにも存在します。たとえば、次のことができます。
r.HeadersRegexp("Content-Type", "application/(text|json)")
...ルートは、コンテンツタイプと両方のリクエストと一致します。
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")
機能 on を使用すると、ルーターに登録されているすべてのルートにアクセスできます。例えば 以下は、登録されているすべてのルートを出力します。
Walk
mux.Router
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では、.一緒にそれを行う方法は次のとおりです。
*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 はルーターへのミドルウェアの追加をサポートしており、サブルーターを含め、一致が見つかった場合に追加された順序で実行されます。 ミドルウェアは(通常)小さなコードであり、1つのリクエストを受け取り、それに対して何かを実行し、それを別のミドルウェアまたは最終ハンドラーに渡します。ミドルウェアの一般的なユースケースには、リクエストロギング、ヘッダー操作、ハイジャックなどがあります。
ResponseWriter
Mux ミドルウェアは、デファクト スタンダード タイプを使用して定義されます。
type MiddlewareFunc func(http.Handler) http.Handler
通常、返されるハンドラはhttpで何かを行うクロージャです。ResponseWriter と http.要求が渡され、パラメーターとして渡されたハンドラーが MiddlewareFunc に呼び出されます。これは、クロージャが作成されたコンテキストから変数にアクセスできる一方で、受信者によって強制された署名を保持できることを利用します。
処理されているリクエストの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)
セッショントークンをユーザーにマップするより複雑な認証ミドルウェアは、次のように記述できます。
// 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)
Note: The handler chain will be stopped if your middleware doesn't call with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares should write to if they are going to terminate the request, and they should not write to if they are not going to terminate it.
next.ServeHTTP()
ResponseWriter
ResponseWriter
CORSMethodMiddleware intends to make it easier to strictly set the response header.
Access-Control-Allow-Methods
Access-Control-Allow-Origin
Access-Control-Allow-Methods
r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)
Access-Control-Allow-Methods: GET,PUT,OPTIONS
Important: there must be an method matcher for the middleware to set the headers.
OPTIONS
Here is an example of using along with a custom handler to set all the required CORS headers:
CORSMethodMiddleware
OPTIONS
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"))
}
And an request to using something like:
/foo
curl localhost:8080/foo -v
Would look like:
* 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
Testing handlers in a Go web application is straightforward, and mux doesn't complicate this any further. Given two files: and , here's how we'd test an application using mux.
endpoints.go
endpoints_test.go
First, our simple 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))
}
Our test code:
// 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)
}
}
ルートに変数がある場合は、リクエストで変数を渡すことができます。複数をテストするためのテーブル駆動型テストを書くことができます 必要に応じて可能なルート変数。
// 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()
// Need to create a router that we can pass the request through so that the vars will be added to the context
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))
}
BSDライセンス。詳細については、LICENSE ファイルを参照してください。