Skip to content

Commit e498c60

Browse files
committed
oops
2 parents 760c5a6 + 8ea74c2 commit e498c60

File tree

4 files changed

+290
-124
lines changed

4 files changed

+290
-124
lines changed

README.md

+50-18
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,49 @@
1-
# rdb
1+
# netbug
22

3-
Package `rdb` provides an `http.Handler` for accessing the profiling and debug tools available in the `/net/http/pprof` and `/runtime/pprof` packages.
3+
[![GoDoc](https://godoc.org/github.com/e-dard/netbug?status.svg)](https://godoc.org/github.com/e-dard/netbug)
44

5-
The advantages of using `rdb` over the existing `/net/http/pprof` handlers are:
5+
Package `netbug` provides access to an `http.Handler` that accesses the profiling and debug tools available in the `/net/http/pprof` and `/runtime/pprof` packages.
66

7-
1. You can register the handler under an arbitrary route-prefix. A use-case might be to have a secret endpoint for keeping this information hidden from prying eyes on production boxes;
8-
2. It pulls together all the handlers from `/net/http/pprof` and `/runtime/pprof` into a single index page, for when you can't quite remember the URL for the profile you want; and
9-
3. You can register the handlers onto `http.ServeMux`'s that aren't `http.DefaultServeMux`.
7+
The advantages of using `netbug` over the existing `/net/http/pprof` handlers are:
8+
9+
1. You can register the handler under an arbitrary route-prefix. A use-case might be to have a secret endpoint for keeping this information hidden from prying eyes, rather than `/debug/pprof`;
10+
2. It pulls together all the handlers from `/net/http/pprof` *and* `/runtime/pprof` into a single index page, for when you can't quite remember the URL for the profile you want;
11+
3. You can register the handlers onto `http.ServeMux`'s that aren't `http.DefaultServeMux`;
12+
4. It provides an optional handler that requires a token URL parameter. This is useful if you want that little bit of extra security (use this over HTTPS connections only).
1013

1114
**Note**:
12-
It still imports `/net/http/pprof`, which means the `/debug/pprof` routes in that package get registered on `http.DefaultServeMux`. If you're using this package to avoid those routes being registered, you should use it with your own `http.ServeMux`.
15+
It still imports `/net/http/pprof`, which means the `/debug/pprof` routes in that package *still* get registered on `http.DefaultServeMux`.
16+
If you're using this package to avoid those routes being registered, you should use it with your *own* `http.ServeMux`.
1317

14-
`rdb` is trying to cater for the situation where you want all profiling tools available remotely on your running services, but you don't want to expose the `/debug/pprof` routes that `net/http/pprof` forces you to expose.
18+
`netbug` is trying to cater for the situation where you want all profiling tools available remotely on your running services, but you don't want to expose the `/debug/pprof` routes that `net/http/pprof` forces you to expose.
1519

1620
## How do I use it?
17-
All you have to do is give `rdb` the `http.ServeMux` you want to register the handlers on, and you're away.
21+
In the simplest case give `netbug` the `http.ServeMux` you want to register the handlers on, as well as where you want to register the handler and you're away.
22+
23+
```go
24+
package main
25+
26+
import (
27+
"log"
28+
"net/http"
29+
30+
"github.com/e-dard/netbug"
31+
)
32+
33+
func main() {
34+
r := http.NewServeMux()
35+
rdb.RegisterHandler("/myroute/", r)
36+
if err := http.ListenAndServe(":8080", r); err != nil {
37+
log.Fatal(err)
38+
}
39+
}
40+
```
41+
42+
Visiting [http://localhost:8080/myroute/](http://localhost:8080/myroute/) will then return:
43+
44+
![](http://f.cl.ly/items/2d13110V2S2H3T1c0n3b/Screen%20Shot%202015-03-19%20at%2017.22.19.png)
45+
46+
`netbug` also provides a simple way of adding some authentication:
1847

1948
```go
2049
package main
@@ -23,30 +52,31 @@ import (
2352
"log"
2453
"net/http"
2554

26-
"github.com/e-dard/remote-debug"
55+
"github.com/e-dard/netbug"
2756
)
2857

2958
func main() {
3059
r := http.NewServeMux()
31-
rdb.Register("/some-prefix/", r)
60+
rdb.RegisterAuthHandler("password", "/myroute/", r)
3261
if err := http.ListenAndServe(":8080", r); err != nil {
3362
log.Fatal(err)
3463
}
3564
}
3665
```
3766

38-
Visiting `http://localhost:8080/some-prefix/debug/pprof/` will then return:
67+
And visit [http://localhost:8080/myroute/?token=password](http://localhost:8080/myroute/?token=password).
3968

40-
![](http://cl.ly/image/3x1U3O1B2L3C/Screen%20Shot%202015-03-07%20at%2019.53.28.png)
69+
**Obviously** this form of authentication is pointless if you're not accessing the routes over an HTTPS connection.
70+
If you want to use a different form of authentication, e.g., HTTP Basic Authentication, then you can use the handler returned by `netbug.Handler()`, and wrap it with handlers provided by packages like [github.com/abbot/go-http-auth](https://github.com/abbot/go-http-auth/).
4171

4272
### What can you do with it?
4373

44-
It just wraps the behaviour of the `/net/http/pprof` and `/runtime/pprof` packages.
74+
It just wraps the behaviour of the [/net/http/pprof](http://golang.org/pkg/net/http/pprof/) and [/runtime/pprof](http://golang.org/pkg/runtime/pprof/) packages.
4575
Check out their documentation to see what's available.
4676
As an example though, if you want to run a 30-second CPU profile on your running service it's really simple:
4777

4878
```
49-
$ go tool pprof https://example.com/some-hidden-prefix/debug/pprof/profile
79+
$ go tool pprof https://example.com/myroute/profile
5080
```
5181

5282
## Background
@@ -58,9 +88,11 @@ However, there are a couple of problems with the `net/http/pprof` package.
5888

5989
1. It assumes you're cool about the relevant handlers being registered under the `/debug/pprof` route.
6090
2. It assumes you're cool about handlers being registered on `http.DefaultServeMux`.
91+
3. You can't wrap the handlers in any way, say to add authentication or other logic.
6192

62-
You can sort of fix (1) and (2) by digging around the `net/http/pprof` package and registering all all the exported handlers under different paths on your own `http.ServeMux`, but you still have the problem of the index page—which is useful to visit if you don't profile much—using hard-coded paths. It doesn't quite work well.
63-
Also, the index page doesn't provide you with easy links to the debug information that the `net/http/pprof` has handlers for.
93+
You can sort of fix (1) and (2) by digging around the `net/http/pprof` package and registering all all the exported handlers under different paths on your own `http.ServeMux`, but you still have the problem of the index page—which is useful to visit if you don't profile much—using hard-coded paths.
94+
It doesn't really work well.
95+
Also, the index page doesn't provide you with easy links to the debug information that the `net/http/pprof` package has handlers for.
6496

65-
So, `rdb` is just a simple package to fix (1) and (2).
97+
`netdebug` is just a small package to fix these issues.
6698

doc.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Package netbug provides a http.Handler for executing the various
2+
// profilers and debug tools in the Go std library.
3+
//
4+
// netbug provides some advantages over the /net/http/pprof and
5+
// /runtime/pprof packages:
6+
//
7+
// 1. You can register the handler under an arbitrary route or with
8+
// some authentication on the handler, making it easier to to keep
9+
// the profilers and debug information away from prying eyes;
10+
// 2. It pulls together all the handlers from /net/http/pprof and
11+
// runtime/pprof into a single index page, for when you can't
12+
// quite remember the URL for the profile you want; and
13+
// 3. You can register the handler onto an `http.ServeMux` that
14+
// isn't `http.DefaultServeMux`.
15+
//
16+
//
17+
// The simplest integration of netbug looks like:
18+
//
19+
// package main
20+
//
21+
// import (
22+
// "log"
23+
// "net/http"
24+
//
25+
// "github.com/e-dard/netbug"
26+
// )
27+
//
28+
// func main() {
29+
// r := http.NewServeMux()
30+
// netbug.Register("/myroute/", r)
31+
//
32+
// if err := http.ListenAndServe(":8080", r); err != nil {
33+
// log.Fatal(err)
34+
// }
35+
// }
36+
//
37+
// You can then access the index page via GET /myroute/
38+
//
39+
// The netbug.RegisterAuthHandler function lets you register the handler on
40+
// your http.ServeMux and add some simple authentication, in the
41+
// form of a URL parameter:
42+
//
43+
// package main
44+
//
45+
// import (
46+
// "log"
47+
// "net/http"
48+
//
49+
// "github.com/e-dard/netbug"
50+
// )
51+
//
52+
// func main() {
53+
// r := http.NewServeMux()
54+
// netbug.RegisterAuthHandler("open sesame", "/myroute/", r)
55+
//
56+
// if err := http.ListenAndServe(":8080", r); err != nil {
57+
// log.Fatal(err)
58+
// }
59+
// }
60+
//
61+
// You can then access the index page via GET /myroute/?token=open%20sesame
62+
//
63+
// The package also provides access to the handlers directly, for when
64+
// you want to, say, wrap them in your own logic. Just be sure that
65+
// when you use the handlers netbug provides you take care to use
66+
// `http.StripPrefix` to strip the route you register the handler on.
67+
// This is because the handlers' logic expect them to be registered on
68+
// "/".
69+
//
70+
// package main
71+
//
72+
// import (
73+
// "log"
74+
// "net/http"
75+
//
76+
// "github.com/e-dard/netbug"
77+
// )
78+
//
79+
// func myHandler(h http.Handler) http.Handler {
80+
// mh := func(w http.ResponseWriter, r *http.Request) {
81+
// // Some logic here.
82+
// h.ServeHTTP(w, r)
83+
// }
84+
// return http.HandlerFunc(mh)
85+
// }
86+
//
87+
// func main() {
88+
// r := http.NewServeMux()
89+
// rh := http.StripPrefix("/myroute/", netbug.Handler())
90+
// r.Handle("/myroute/", myHandler(rh))
91+
//
92+
// if err := http.ListenAndServe(":8080", r); err != nil {
93+
// log.Fatal(err)
94+
// }
95+
// }
96+
//
97+
// As you would expect, netbug works the same way with the go profiler
98+
// tool as /net/http/pprof does. To run a 30 second CPU profile on your
99+
// service for example:
100+
//
101+
// $ go tool pprof https://example.com/myroute/profile
102+
//
103+
package netbug

netbug.go

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package netbug
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"net/http"
7+
nhpprof "net/http/pprof"
8+
"net/url"
9+
"runtime/pprof"
10+
"strings"
11+
"text/template"
12+
)
13+
14+
func handler(token string) http.Handler {
15+
info := struct {
16+
Profiles []*pprof.Profile
17+
Token string
18+
}{
19+
Profiles: pprof.Profiles(),
20+
Token: url.QueryEscape(token),
21+
}
22+
23+
h := func(w http.ResponseWriter, r *http.Request) {
24+
name := strings.TrimPrefix(r.URL.Path, "/")
25+
switch name {
26+
case "":
27+
// Index page.
28+
if err := indexTmpl.Execute(w, info); err != nil {
29+
log.Println(err)
30+
return
31+
}
32+
case "cmdline":
33+
nhpprof.Cmdline(w, r)
34+
case "profile":
35+
nhpprof.Profile(w, r)
36+
case "symbol":
37+
nhpprof.Symbol(w, r)
38+
default:
39+
// Provides access to all profiles under runtime/pprof
40+
nhpprof.Handler(name).ServeHTTP(w, r)
41+
}
42+
}
43+
return http.HandlerFunc(h)
44+
}
45+
46+
// Handler returns an http.Handler that provides access to the various
47+
// profiler and debug tools in the /net/http/pprof and /runtime/pprof
48+
// packages.
49+
//
50+
// The returned handler assumed it is registered on "/" so if you wish
51+
// to register on any other route, you should strip the route prefix
52+
// before passing a request on to the handler.
53+
//
54+
// This is best done with:
55+
//
56+
// h := http.StripPrefix("/myroute/", netbug.Handler())
57+
//
58+
// Unless you need to wrap or chain the handler you probably want to use
59+
// netbug.RegisterHandler.
60+
func Handler() http.Handler {
61+
return handler("")
62+
}
63+
64+
// RegisterHandler registers the netbug handler on the provided
65+
// http.ServeMux, using the provided prefix to form the route.
66+
//
67+
// The provided prefix needs to have a trailing slash. The full list of
68+
// routes registered for available profiles and debug information can
69+
// be examined by visiting prefix.
70+
//
71+
func RegisterHandler(prefix string, mux *http.ServeMux) {
72+
mux.Handle(prefix, http.StripPrefix(prefix, Handler()))
73+
}
74+
75+
// AuthHandler returns an http.Handler that provides authenticated
76+
// access to the various profiler and debug tools in the
77+
// /net/http/pprof and /runtime/pprof packages.
78+
//
79+
// The token provided is required as a URL parameter called token for
80+
// all requests. The netbug package takes care of injecting the token
81+
// into links in the index page.
82+
//
83+
// The returned handler assumed it is registered on "/" so if you wish
84+
// to register on any other route, you should strip the route prefix
85+
// before passing a request on to the handler.
86+
//
87+
// This is best done with:
88+
//
89+
// h := http.StripPrefix("/myroute/", netbug.AuthHandler("secret"))
90+
//
91+
// Unless you need to wrap or chain the handler you probably want to use
92+
// netbug.RegisterAuthHandler.
93+
func AuthHandler(token string) http.Handler {
94+
h := handler(token)
95+
ah := func(w http.ResponseWriter, r *http.Request) {
96+
if r.FormValue("token") == token {
97+
h.ServeHTTP(w, r)
98+
} else {
99+
w.WriteHeader(http.StatusUnauthorized)
100+
fmt.Fprintln(w, "Unauthorized.")
101+
}
102+
}
103+
return http.HandlerFunc(ah)
104+
}
105+
106+
// RegisterAuthHandler registers a handler requiring authentication on
107+
// the provided http.ServeMux, using the provided prefix to form the
108+
// route.
109+
//
110+
// The provided prefix needs to have a trailing slash. The full list of
111+
// routes registered can be examined by visiting the root page.
112+
func RegisterAuthHandler(token, prefix string, mux *http.ServeMux) {
113+
mux.Handle(prefix, http.StripPrefix(prefix, AuthHandler(token)))
114+
}
115+
116+
var indexTmpl = template.Must(template.New("index").Parse(`<html>
117+
<head>
118+
<title>Debug Information</title>
119+
</head>
120+
<br>
121+
<body>
122+
profiles:<br>
123+
<table>
124+
{{range .Profiles}}
125+
<tr><td align=right>{{.Count}}<td><a href="{{.Name}}?debug=1{{if $.Token}}&token={{$.Token}}{{end}}">{{.Name}}</a>
126+
{{end}}
127+
<tr><td align=right><td><a href="profile{{if .Token}}?token={{.Token}}{{end}}">CPU</a>
128+
</table>
129+
<br>
130+
debug information:<br>
131+
<table>
132+
<tr><td align=right><td><a href="cmdline{{if .Token}}?token={{.Token}}{{end}}">cmdline</a>
133+
<tr><td align=right><td><a href="symbol{{if .Token}}?token={{.Token}}{{end}}">symbol</a>
134+
<tr><td align=right><td><a href="goroutine?debug=2{{if .Token}}&token={{.Token}}{{end}}">full goroutine stack dump</a><br>
135+
<table>
136+
</body>
137+
</html>`))

0 commit comments

Comments
 (0)