mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
tsweb: replace NewMux with a more flexible DebugHandler.
Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
parent
b461ba9554
commit
0022c3d2e2
@ -12,8 +12,6 @@
|
|||||||
"errors"
|
"errors"
|
||||||
"expvar"
|
"expvar"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"html"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -35,7 +33,6 @@
|
|||||||
"tailscale.com/tsweb"
|
"tailscale.com/tsweb"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/wgkey"
|
"tailscale.com/types/wgkey"
|
||||||
"tailscale.com/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -143,8 +140,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
expvar.Publish("derp", s.ExpVar())
|
expvar.Publish("derp", s.ExpVar())
|
||||||
|
|
||||||
// Create our own mux so we don't expose /debug/ stuff to the world.
|
mux := http.NewServeMux()
|
||||||
mux := tsweb.NewMux(debugHandler(s))
|
|
||||||
mux.Handle("/derp", derphttp.Handler(s))
|
mux.Handle("/derp", derphttp.Handler(s))
|
||||||
go refreshBootstrapDNSLoop()
|
go refreshBootstrapDNSLoop()
|
||||||
mux.HandleFunc("/bootstrap-dns", handleBootstrapDNS)
|
mux.HandleFunc("/bootstrap-dns", handleBootstrapDNS)
|
||||||
@ -164,6 +160,17 @@ func main() {
|
|||||||
io.WriteString(w, "<p>Debug info at <a href='/debug/'>/debug/</a>.</p>\n")
|
io.WriteString(w, "<p>Debug info at <a href='/debug/'>/debug/</a>.</p>\n")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
debug := tsweb.Debugger(mux)
|
||||||
|
debug.KV("TLS hostname", *hostname)
|
||||||
|
debug.KV("Mesh key", s.HasMeshKey())
|
||||||
|
debug.Handle("check", "Consistency check", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := s.ConsistencyCheck()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
} else {
|
||||||
|
io.WriteString(w, "derp.Server ConsistencyCheck okay")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
if *runSTUN {
|
if *runSTUN {
|
||||||
go serveSTUN()
|
go serveSTUN()
|
||||||
@ -217,39 +224,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugHandler(s *derp.Server) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.RequestURI == "/debug/check" {
|
|
||||||
err := s.ConsistencyCheck()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), 500)
|
|
||||||
} else {
|
|
||||||
io.WriteString(w, "derp.Server ConsistencyCheck okay")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f := func(format string, args ...interface{}) { fmt.Fprintf(w, format, args...) }
|
|
||||||
f(`<html><body>
|
|
||||||
<h1>DERP debug</h1>
|
|
||||||
<ul>
|
|
||||||
`)
|
|
||||||
f("<li><b>Hostname:</b> %v</li>\n", html.EscapeString(*hostname))
|
|
||||||
f("<li><b>Uptime:</b> %v</li>\n", tsweb.Uptime())
|
|
||||||
f("<li><b>Mesh Key:</b> %v</li>\n", s.HasMeshKey())
|
|
||||||
f("<li><b>Version:</b> %v</li>\n", html.EscapeString(version.Long))
|
|
||||||
|
|
||||||
f(`<li><a href="/debug/vars">/debug/vars</a> (Go)</li>
|
|
||||||
<li><a href="/debug/varz">/debug/varz</a> (Prometheus)</li>
|
|
||||||
<li><a href="/debug/pprof/">/debug/pprof/</a></li>
|
|
||||||
<li><a href="/debug/pprof/goroutine?debug=1">/debug/pprof/goroutine</a> (collapsed)</li>
|
|
||||||
<li><a href="/debug/pprof/goroutine?debug=2">/debug/pprof/goroutine</a> (full)</li>
|
|
||||||
<li><a href="/debug/check">/debug/check</a> internal consistency check</li>
|
|
||||||
<ul>
|
|
||||||
</html>
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveSTUN() {
|
func serveSTUN() {
|
||||||
pc, err := net.ListenPacket("udp", ":3478")
|
pc, err := net.ListenPacket("udp", ":3478")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -55,7 +55,8 @@ func main() {
|
|||||||
log.Fatalf("Couldn't parse URL %q: %v", *goVarsURL, err)
|
log.Fatalf("Couldn't parse URL %q: %v", *goVarsURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := tsweb.NewMux(http.HandlerFunc(debugHandler))
|
mux := http.NewServeMux()
|
||||||
|
tsweb.Debugger(mux) // registers /debug/*
|
||||||
mux.Handle("/metrics", tsweb.Protected(proxy))
|
mux.Handle("/metrics", tsweb.Protected(proxy))
|
||||||
mux.Handle("/varz", tsweb.Protected(tsweb.StdHandler(&goVarsHandler{*goVarsURL}, tsweb.HandlerOptions{
|
mux.Handle("/varz", tsweb.Protected(tsweb.StdHandler(&goVarsHandler{*goVarsURL}, tsweb.HandlerOptions{
|
||||||
Quiet200s: true,
|
Quiet200s: true,
|
||||||
@ -170,23 +171,3 @@ func (c *certHolder) loadLocked() error {
|
|||||||
c.loaded = time.Now()
|
c.loaded = time.Now()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// debugHandler serves a page with links to tsweb-managed debug URLs
|
|
||||||
// at /debug/.
|
|
||||||
func debugHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
f := func(format string, args ...interface{}) { fmt.Fprintf(w, format, args...) }
|
|
||||||
f(`<html><body>
|
|
||||||
<h1>microproxy debug</h1>
|
|
||||||
<ul>
|
|
||||||
`)
|
|
||||||
f("<li><b>Hostname:</b> %v</li>\n", *hostname)
|
|
||||||
f("<li><b>Uptime:</b> %v</li>\n", tsweb.Uptime())
|
|
||||||
f(`<li><a href="/debug/vars">/debug/vars</a> (Go)</li>
|
|
||||||
<li><a href="/debug/varz">/debug/varz</a> (Prometheus)</li>
|
|
||||||
<li><a href="/debug/pprof/">/debug/pprof/</a></li>
|
|
||||||
<li><a href="/debug/pprof/goroutine?debug=1">/debug/pprof/goroutine</a> (collapsed)</li>
|
|
||||||
<li><a href="/debug/pprof/goroutine?debug=2">/debug/pprof/goroutine</a> (full)</li>
|
|
||||||
<ul>
|
|
||||||
</html>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
136
tsweb/debug.go
Normal file
136
tsweb/debug.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tsweb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"expvar"
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"tailscale.com/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugHandler is an http.Handler that serves a debugging "homepage",
|
||||||
|
// and provides helpers to register more debug endpoints and reports.
|
||||||
|
//
|
||||||
|
// The rendered page consists of three sections: informational
|
||||||
|
// key/value pairs, links to other pages, and additional
|
||||||
|
// program-specific HTML. Callers can add to these sections using the
|
||||||
|
// KV, URL and Section helpers respectively.
|
||||||
|
//
|
||||||
|
// Additionally, the Handle method offers a shorthand for correctly
|
||||||
|
// registering debug handlers and cross-linking them from /debug/.
|
||||||
|
type DebugHandler struct {
|
||||||
|
mux *http.ServeMux // where this handler is registered
|
||||||
|
kvs []func(io.Writer) // output one <li>...</li> each, see KV()
|
||||||
|
urls []string // one <li>...</li> block with link each
|
||||||
|
sections []func(io.Writer, *http.Request) // invoked in registration order prior to outputting </body>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugger returns the DebugHandler registered on mux at /debug/,
|
||||||
|
// creating it if necessary.
|
||||||
|
func Debugger(mux *http.ServeMux) *DebugHandler {
|
||||||
|
h, pat := mux.Handler(&http.Request{URL: &url.URL{Path: "/debug/"}})
|
||||||
|
if d, ok := h.(*DebugHandler); ok && pat == "/debug/" {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
ret := &DebugHandler{
|
||||||
|
mux: mux,
|
||||||
|
}
|
||||||
|
mux.Handle("/debug/", ret)
|
||||||
|
|
||||||
|
ret.KVFunc("Uptime", func() interface{} { return Uptime() })
|
||||||
|
ret.KV("Version", version.Long)
|
||||||
|
ret.Handle("vars", "Metrics (Go)", expvar.Handler())
|
||||||
|
ret.Handle("varz", "Metrics (Prometheus)", http.HandlerFunc(VarzHandler))
|
||||||
|
ret.Handle("pprof/", "pprof", http.HandlerFunc(pprof.Index))
|
||||||
|
ret.URL("/debug/pprof/goroutine?debug=1", "Goroutines (collapsed)")
|
||||||
|
ret.URL("/debug/pprof/goroutine?debug=2", "Goroutines (full)")
|
||||||
|
ret.Handle("gc", "force GC", http.HandlerFunc(gcHandler))
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err == nil {
|
||||||
|
ret.KV("Machine", hostname)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements http.Handler.
|
||||||
|
func (d *DebugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !AllowDebugAccess(r) {
|
||||||
|
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/debug/" {
|
||||||
|
// Sub-handlers are handled by the parent mux directly.
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f := func(format string, args ...interface{}) { fmt.Fprintf(w, format, args...) }
|
||||||
|
f("<html><body><h1>%s debug</h1><ul>", version.CmdName())
|
||||||
|
for _, kv := range d.kvs {
|
||||||
|
kv(w)
|
||||||
|
}
|
||||||
|
for _, url := range d.urls {
|
||||||
|
io.WriteString(w, url)
|
||||||
|
}
|
||||||
|
for _, section := range d.sections {
|
||||||
|
section(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers handler at /debug/<slug> and creates a descriptive
|
||||||
|
// entry in /debug/ for it.
|
||||||
|
func (d *DebugHandler) Handle(slug, desc string, handler http.Handler) {
|
||||||
|
href := "/debug/" + slug
|
||||||
|
d.mux.Handle(href, Protected(handler))
|
||||||
|
d.URL(href, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KV adds a key/value list item to /debug/.
|
||||||
|
func (d *DebugHandler) KV(k string, v interface{}) {
|
||||||
|
val := html.EscapeString(fmt.Sprintf("%v", v))
|
||||||
|
d.kvs = append(d.kvs, func(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// KVFunc adds a key/value list item to /debug/. v is called on every
|
||||||
|
// render of /debug/.
|
||||||
|
func (d *DebugHandler) KVFunc(k string, v func() interface{}) {
|
||||||
|
d.kvs = append(d.kvs, func(w io.Writer) {
|
||||||
|
val := html.EscapeString(fmt.Sprintf("%v", v()))
|
||||||
|
fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL adds a URL and description list item to /debug/.
|
||||||
|
func (d *DebugHandler) URL(url, desc string) {
|
||||||
|
if desc != "" {
|
||||||
|
desc = " (" + desc + ")"
|
||||||
|
}
|
||||||
|
d.urls = append(d.urls, fmt.Sprintf(`<li><a href="%s">%s</a>%s</li>`, url, url, html.EscapeString(desc)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section invokes f on every render of /debug/ to add supplemental
|
||||||
|
// HTML to the page body.
|
||||||
|
func (d *DebugHandler) Section(f func(w io.Writer, r *http.Request)) {
|
||||||
|
d.sections = append(d.sections, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gcHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("running GC...\n"))
|
||||||
|
if f, ok := w.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
w.Write([]byte("Done.\n"))
|
||||||
|
}
|
189
tsweb/debug_test.go
Normal file
189
tsweb/debug_test.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tsweb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDebugger(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
dbg1 := Debugger(mux)
|
||||||
|
if dbg1 == nil {
|
||||||
|
t.Fatal("didn't get a debugger from mux")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg2 := Debugger(mux)
|
||||||
|
if dbg2 != dbg1 {
|
||||||
|
t.Fatal("Debugger returned different debuggers for the same mux")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(m http.Handler, path, srcIP string) (int, string) {
|
||||||
|
req := httptest.NewRequest("GET", path, nil)
|
||||||
|
req.RemoteAddr = srcIP + ":1234"
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
m.ServeHTTP(rec, req)
|
||||||
|
return rec.Result().StatusCode, rec.Body.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tsIP = "100.100.100.100"
|
||||||
|
pubIP = "8.8.8.8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDebuggerKV(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
dbg.KV("Donuts", 42)
|
||||||
|
dbg.KV("Secret code", "hunter2")
|
||||||
|
val := "red"
|
||||||
|
dbg.KVFunc("Condition", func() interface{} { return val })
|
||||||
|
|
||||||
|
code, _ := get(mux, "/debug/", pubIP)
|
||||||
|
if code != 403 {
|
||||||
|
t.Fatalf("debug access wasn't denied, got %v", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
code, body := get(mux, "/debug/", tsIP)
|
||||||
|
if code != 200 {
|
||||||
|
t.Fatalf("debug access failed, got %v", code)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"Donuts", "42", "Secret code", "hunter2", "Condition", "red"} {
|
||||||
|
if !strings.Contains(body, want) {
|
||||||
|
t.Errorf("want %q in output, not found", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val = "green"
|
||||||
|
code, body = get(mux, "/debug/", tsIP)
|
||||||
|
if code != 200 {
|
||||||
|
t.Fatalf("debug access failed, got %v", code)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"Condition", "green"} {
|
||||||
|
if !strings.Contains(body, want) {
|
||||||
|
t.Errorf("want %q in output, not found", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebuggerURL(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
dbg.URL("https://www.tailscale.com", "Homepage")
|
||||||
|
|
||||||
|
code, body := get(mux, "/debug/", tsIP)
|
||||||
|
if code != 200 {
|
||||||
|
t.Fatalf("debug access failed, got %v", code)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"https://www.tailscale.com", "Homepage"} {
|
||||||
|
if !strings.Contains(body, want) {
|
||||||
|
t.Errorf("want %q in output, not found", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebuggerSection(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
dbg.Section(func(w io.Writer, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Test output %v", r.RemoteAddr)
|
||||||
|
})
|
||||||
|
|
||||||
|
code, body := get(mux, "/debug/", tsIP)
|
||||||
|
if code != 200 {
|
||||||
|
t.Fatalf("debug access failed, got %v", code)
|
||||||
|
}
|
||||||
|
want := `Test output 100.100.100.100:1234`
|
||||||
|
if !strings.Contains(body, want) {
|
||||||
|
t.Errorf("want %q in output, not found", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebuggerHandle(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
dbg.Handle("check", "Consistency check", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Test output %v", r.RemoteAddr)
|
||||||
|
}))
|
||||||
|
|
||||||
|
code, body := get(mux, "/debug/", tsIP)
|
||||||
|
if code != 200 {
|
||||||
|
t.Fatalf("debug access failed, got %v", code)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"/debug/check", "Consistency check"} {
|
||||||
|
if !strings.Contains(body, want) {
|
||||||
|
t.Errorf("want %q in output, not found", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code, _ = get(mux, "/debug/check", pubIP)
|
||||||
|
if code != 403 {
|
||||||
|
t.Fatal("/debug/check should be protected, but isn't")
|
||||||
|
}
|
||||||
|
|
||||||
|
code, body = get(mux, "/debug/check", tsIP)
|
||||||
|
if code != 200 {
|
||||||
|
t.Fatal("/debug/check denied debug access")
|
||||||
|
}
|
||||||
|
want := "Test output " + tsIP
|
||||||
|
if !strings.Contains(body, want) {
|
||||||
|
t.Errorf("want %q in output, not found", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDebugHandler_Handle() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
// Registers /debug/flushcache with the given handler, and adds a
|
||||||
|
// link to /debug/ with the description "Flush caches".
|
||||||
|
dbg.Handle("flushcache", "Flush caches", http.HandlerFunc(http.NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDebugHandler_KV() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
// Adds two list items to /debug/, showing that the condition is
|
||||||
|
// red and there are 42 donuts.
|
||||||
|
dbg.KV("Conditon", "red")
|
||||||
|
dbg.KV("Donuts", 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDebugHandler_KVFunc() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
// Adds an count of page renders to /debug/. Note this example
|
||||||
|
// isn't concurrency-safe.
|
||||||
|
views := 0
|
||||||
|
dbg.KVFunc("Debug pageviews", func() interface{} {
|
||||||
|
views = views + 1
|
||||||
|
return views
|
||||||
|
})
|
||||||
|
dbg.KV("Donuts", 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDebugHandler_URL() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
// Links to the Tailscale website from /debug/.
|
||||||
|
dbg.URL("https://www.tailscale.com", "Homepage")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDebugHandler_Section() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
dbg := Debugger(mux)
|
||||||
|
// Adds a section to /debug/ that dumps the HTTP request of the
|
||||||
|
// visitor.
|
||||||
|
dbg.Section(func(w io.Writer, r *http.Request) {
|
||||||
|
io.WriteString(w, "<h3>Dump of your HTTP request</h3>")
|
||||||
|
fmt.Fprintf(w, "<code>%#v</code>", r)
|
||||||
|
})
|
||||||
|
}
|
@ -37,30 +37,6 @@ func init() {
|
|||||||
// DevMode controls whether extra output in shown, for when the binary is being run in dev mode.
|
// DevMode controls whether extra output in shown, for when the binary is being run in dev mode.
|
||||||
var DevMode bool
|
var DevMode bool
|
||||||
|
|
||||||
// NewMux returns a new ServeMux with debugHandler registered (and protected) at /debug/.
|
|
||||||
func NewMux(debugHandler http.Handler) *http.ServeMux {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
registerCommonDebug(mux)
|
|
||||||
mux.Handle("/debug/", Protected(debugHandler))
|
|
||||||
return mux
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerCommonDebug(mux *http.ServeMux) {
|
|
||||||
mux.Handle("/debug/pprof/", Protected(http.DefaultServeMux)) // to net/http/pprof
|
|
||||||
mux.Handle("/debug/vars", Protected(http.DefaultServeMux)) // to expvar
|
|
||||||
mux.Handle("/debug/varz", Protected(http.HandlerFunc(VarzHandler)))
|
|
||||||
mux.Handle("/debug/gc", Protected(http.HandlerFunc(gcHandler)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func gcHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("running GC...\n"))
|
|
||||||
if f, ok := w.(http.Flusher); ok {
|
|
||||||
f.Flush()
|
|
||||||
}
|
|
||||||
runtime.GC()
|
|
||||||
w.Write([]byte("Done.\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultCertDir(leafDir string) string {
|
func DefaultCertDir(leafDir string) string {
|
||||||
cacheDir, err := os.UserCacheDir()
|
cacheDir, err := os.UserCacheDir()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user