From b94e5131166edd5ed8847b1588759747b4d2c9b2 Mon Sep 17 00:00:00 2001 From: "Marcelo E. Magallon" Date: Tue, 20 Oct 2015 21:23:03 -0600 Subject: [PATCH] Add expvar middleware Right now it has a very simple configuration: expvar /debug/vars It will return a JSON object with memory statistics and the command line used to start caddy, which are the two expvars that expvar registers by default. --- caddy/directives.go | 1 + caddy/setup/expvar.go | 41 ++++++++++++++++++++++++++++ caddy/setup/expvar_test.go | 36 +++++++++++++++++++++++++ middleware/expvar/expvar.go | 46 ++++++++++++++++++++++++++++++++ middleware/expvar/expvar_test.go | 46 ++++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 caddy/setup/expvar.go create mode 100644 caddy/setup/expvar_test.go create mode 100644 middleware/expvar/expvar.go create mode 100644 middleware/expvar/expvar_test.go diff --git a/caddy/directives.go b/caddy/directives.go index f9da35d2f..66e123a2d 100644 --- a/caddy/directives.go +++ b/caddy/directives.go @@ -62,6 +62,7 @@ var directiveOrder = []directive{ {"basicauth", setup.BasicAuth}, {"internal", setup.Internal}, {"pprof", setup.PProf}, + {"expvar", setup.ExpVar}, {"proxy", setup.Proxy}, {"fastcgi", setup.FastCGI}, {"websocket", setup.WebSocket}, diff --git a/caddy/setup/expvar.go b/caddy/setup/expvar.go new file mode 100644 index 000000000..7393a0a64 --- /dev/null +++ b/caddy/setup/expvar.go @@ -0,0 +1,41 @@ +package setup + +import ( + _ "expvar" + + "github.com/mholt/caddy/middleware" + "github.com/mholt/caddy/middleware/expvar" +) + +// ExpVar configures a new ExpVar middleware instance. +func ExpVar(c *Controller) (middleware.Middleware, error) { + resource, err := expVarParse(c) + if err != nil { + return nil, err + } + + expvar := expvar.ExpVar{Resource: resource} + + return func(next middleware.Handler) middleware.Handler { + expvar.Next = next + return expvar + }, nil +} + +func expVarParse(c *Controller) (expvar.Resource, error) { + var resource expvar.Resource + + var err error + for c.Next() { + args := c.RemainingArgs() + + switch len(args) { + case 1: + resource = expvar.Resource(args[0]) + default: + return resource, c.ArgErr() + } + } + + return resource, err +} diff --git a/caddy/setup/expvar_test.go b/caddy/setup/expvar_test.go new file mode 100644 index 000000000..2b125c893 --- /dev/null +++ b/caddy/setup/expvar_test.go @@ -0,0 +1,36 @@ +package setup + +import ( + "testing" + + "github.com/mholt/caddy/middleware/expvar" +) + +func TestExpvar(t *testing.T) { + c := NewTestController(`expvar /d/v`) + + mid, err := ExpVar(c) + + if err != nil { + t.Errorf("Expected no errors, got: %v", err) + } + + if mid == nil { + t.Fatal("Expected middleware, was nil instead") + } + + handler := mid(EmptyNext) + myHandler, ok := handler.(expvar.ExpVar) + + if !ok { + t.Fatalf("Expected handler to be type ExpVar, got: %#v", handler) + } + + if myHandler.Resource != "/d/v" { + t.Errorf("Expected /d/v as expvar resource") + } + + if !SameNext(myHandler.Next, EmptyNext) { + t.Error("'Next' field of handler was not set properly") + } +} diff --git a/middleware/expvar/expvar.go b/middleware/expvar/expvar.go new file mode 100644 index 000000000..178243486 --- /dev/null +++ b/middleware/expvar/expvar.go @@ -0,0 +1,46 @@ +package expvar + +import ( + "expvar" + "fmt" + "net/http" + + "github.com/mholt/caddy/middleware" +) + +// ExpVar is a simple struct to hold expvar's configuration +type ExpVar struct { + Next middleware.Handler + Resource Resource +} + +// ServeHTTP handles requests to expvar's configured entry point with +// expvar, or passes all other requests up the chain. +func (e ExpVar) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { + if middleware.Path(r.URL.Path).Matches(string(e.Resource)) { + expvarHandler(w, r) + return 0, nil + } + + return e.Next.ServeHTTP(w, r) +} + +// expvarHandler returns a JSON object will all the published variables. +// +// This is lifted straight from the expvar package. +func expvarHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + fmt.Fprintf(w, "{\n") + first := true + expvar.Do(func(kv expvar.KeyValue) { + if !first { + fmt.Fprintf(w, ",\n") + } + first = false + fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) + }) + fmt.Fprintf(w, "\n}\n") +} + +// Resource contains the path to the expvar entry point +type Resource string diff --git a/middleware/expvar/expvar_test.go b/middleware/expvar/expvar_test.go new file mode 100644 index 000000000..e702f9418 --- /dev/null +++ b/middleware/expvar/expvar_test.go @@ -0,0 +1,46 @@ +package expvar + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/mholt/caddy/middleware" +) + +func TestExpVar(t *testing.T) { + rw := ExpVar{ + Next: middleware.HandlerFunc(contentHandler), + Resource: "/d/v", + } + + tests := []struct { + from string + result int + }{ + {"/d/v", 0}, + {"/x/y", http.StatusOK}, + } + + for i, test := range tests { + req, err := http.NewRequest("GET", test.from, nil) + if err != nil { + t.Fatalf("Test %d: Could not create HTTP request %v", i, err) + } + rec := httptest.NewRecorder() + result, err := rw.ServeHTTP(rec, req) + if err != nil { + t.Fatalf("Test %d: Could not ServeHTTP %v", i, err) + } + if result != test.result { + t.Errorf("Test %d: Expected Header '%d' but was '%d'", + i, test.result, result) + } + } +} + +func contentHandler(w http.ResponseWriter, r *http.Request) (int, error) { + fmt.Fprintf(w, r.URL.String()) + return http.StatusOK, nil +}