Sfoglia il codice sorgente

Replaced http wrapper field in plugin Options with HTTPServer. (#229)

Replaced http wrapper field in plugin Options with HTTPServer.

HTTPServer allows somebody embedding pprof to control how the
hosting HTTP server is constructed which makes pprof work in
more environments.
Sanjay Ghemawat 7 anni fa
parent
commit
43bdac25ec
3 ha cambiato i file con 94 aggiunte e 128 eliminazioni
  1. 51
    51
      internal/driver/webui.go
  2. 21
    70
      internal/driver/webui_test.go
  3. 22
    7
      internal/plugin/plugin.go

+ 51
- 51
internal/driver/webui.go Vedi File

23
 	gourl "net/url"
23
 	gourl "net/url"
24
 	"os"
24
 	"os"
25
 	"os/exec"
25
 	"os/exec"
26
+	"strconv"
26
 	"strings"
27
 	"strings"
27
 	"time"
28
 	"time"
28
 
29
 
81
 }
82
 }
82
 
83
 
83
 func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
84
 func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
85
+	host, portStr, err := net.SplitHostPort(hostport)
86
+	if err != nil {
87
+		return fmt.Errorf("could not split http address: %v", err)
88
+	}
89
+	port, err := strconv.Atoi(portStr)
90
+	if err != nil {
91
+		return fmt.Errorf("invalid port number: %v", err)
92
+	}
93
+	if host == "" {
94
+		host = "localhost"
95
+	}
96
+
84
 	interactiveMode = true
97
 	interactiveMode = true
85
 	ui := makeWebInterface(p, o)
98
 	ui := makeWebInterface(p, o)
86
 	for n, c := range pprofCommands {
99
 	for n, c := range pprofCommands {
93
 	ui.help["graph"] = "Display profile as a directed graph"
106
 	ui.help["graph"] = "Display profile as a directed graph"
94
 	ui.help["reset"] = "Show the entire profile"
107
 	ui.help["reset"] = "Show the entire profile"
95
 
108
 
96
-	ln, url, isLocal, err := newListenerAndURL(hostport)
97
-	if err != nil {
98
-		return err
109
+	server := o.HTTPServer
110
+	if server == nil {
111
+		server = defaultWebServer
99
 	}
112
 	}
100
-
101
-	// authorization wrapper
102
-	wrap := o.HTTPWrapper
103
-	if wrap == nil {
104
-		if isLocal {
105
-			// Only allow requests from local host.
106
-			wrap = checkLocalHost
107
-		} else {
108
-			wrap = func(h http.Handler) http.Handler { return h }
109
-		}
113
+	args := &plugin.HTTPServerArgs{
114
+		Hostport: net.JoinHostPort(host, portStr),
115
+		Host:     host,
116
+		Port:     port,
117
+		Handlers: map[string]http.Handler{
118
+			"/":       http.HandlerFunc(ui.dot),
119
+			"/top":    http.HandlerFunc(ui.top),
120
+			"/disasm": http.HandlerFunc(ui.disasm),
121
+			"/source": http.HandlerFunc(ui.source),
122
+			"/peek":   http.HandlerFunc(ui.peek),
123
+		},
110
 	}
124
 	}
111
 
125
 
112
-	mux := http.NewServeMux()
113
-	mux.Handle("/", wrap(http.HandlerFunc(ui.dot)))
114
-	mux.Handle("/top", wrap(http.HandlerFunc(ui.top)))
115
-	mux.Handle("/disasm", wrap(http.HandlerFunc(ui.disasm)))
116
-	mux.Handle("/source", wrap(http.HandlerFunc(ui.source)))
117
-	mux.Handle("/peek", wrap(http.HandlerFunc(ui.peek)))
118
-
119
-	s := &http.Server{Handler: mux}
120
-	go openBrowser(url, o)
121
-	return s.Serve(ln)
126
+	go openBrowser("http://"+args.Hostport, o)
127
+	return server(args)
122
 }
128
 }
123
 
129
 
124
-func newListenerAndURL(hostport string) (ln net.Listener, url string, isLocal bool, err error) {
125
-	host, _, err := net.SplitHostPort(hostport)
130
+func defaultWebServer(args *plugin.HTTPServerArgs) error {
131
+	ln, err := net.Listen("tcp", args.Hostport)
126
 	if err != nil {
132
 	if err != nil {
127
-		return nil, "", false, err
128
-	}
129
-	if host == "" {
130
-		host = "localhost"
131
-	}
132
-	if ln, err = net.Listen("tcp", hostport); err != nil {
133
-		return nil, "", false, err
133
+		return err
134
 	}
134
 	}
135
-	url = fmt.Sprint("http://", net.JoinHostPort(host, fmt.Sprint(ln.Addr().(*net.TCPAddr).Port)))
136
-	return ln, url, isLocalhost(host), nil
135
+	isLocal := isLocalhost(args.Host)
136
+	handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
137
+		if isLocal {
138
+			// Only allow local clients
139
+			host, _, err := net.SplitHostPort(req.RemoteAddr)
140
+			if err != nil || !isLocalhost(host) {
141
+				http.Error(w, "permission denied", http.StatusForbidden)
142
+				return
143
+			}
144
+		}
145
+		h := args.Handlers[req.URL.Path]
146
+		if h == nil {
147
+			// Fall back to default behavior
148
+			h = http.DefaultServeMux
149
+		}
150
+		h.ServeHTTP(w, req)
151
+	})
152
+	s := &http.Server{Handler: handler}
153
+	return s.Serve(ln)
137
 }
154
 }
138
 
155
 
139
 func isLocalhost(host string) bool {
156
 func isLocalhost(host string) bool {
179
 	o.UI.PrintErr(u.String())
196
 	o.UI.PrintErr(u.String())
180
 }
197
 }
181
 
198
 
182
-func checkLocalHost(h http.Handler) http.Handler {
183
-	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
184
-		host, _, err := net.SplitHostPort(req.RemoteAddr)
185
-		if err != nil || !isLocalhost(host) {
186
-			http.Error(w, "permission denied", http.StatusForbidden)
187
-			return
188
-		}
189
-		h.ServeHTTP(w, req)
190
-	})
191
-}
192
-
193
 func varsFromURL(u *gourl.URL) variables {
199
 func varsFromURL(u *gourl.URL) variables {
194
 	vars := pprofVariables.makeCopy()
200
 	vars := pprofVariables.makeCopy()
195
 	vars["focus"].value = u.Query().Get("f")
201
 	vars["focus"].value = u.Query().Get("f")
241
 
247
 
242
 // dot generates a web page containing an svg diagram.
248
 // dot generates a web page containing an svg diagram.
243
 func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
249
 func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
244
-	// Disable prefix matching behavior of net/http.
245
-	if req.URL.Path != "/" {
246
-		http.NotFound(w, req)
247
-		return
248
-	}
249
-
250
 	rpt, errList := ui.makeReport(w, req, []string{"svg"})
250
 	rpt, errList := ui.makeReport(w, req, []string{"svg"})
251
 	if rpt == nil {
251
 	if rpt == nil {
252
 		return // error already reported
252
 		return // error already reported

+ 21
- 70
internal/driver/webui_test.go Vedi File

23
 	"net/url"
23
 	"net/url"
24
 	"os/exec"
24
 	"os/exec"
25
 	"regexp"
25
 	"regexp"
26
-	"runtime"
27
 	"testing"
26
 	"testing"
28
 
27
 
29
 	"github.com/google/pprof/internal/plugin"
28
 	"github.com/google/pprof/internal/plugin"
32
 
31
 
33
 func TestWebInterface(t *testing.T) {
32
 func TestWebInterface(t *testing.T) {
34
 	prof := makeFakeProfile()
33
 	prof := makeFakeProfile()
35
-	ui := makeWebInterface(prof, &plugin.Options{
36
-		Obj: fakeObjTool{},
37
-		UI:  &stdUI{},
38
-	})
39
 
34
 
40
-	// Start test server.
41
-	server := httptest.NewServer(http.HandlerFunc(
42
-		func(w http.ResponseWriter, r *http.Request) {
43
-			switch r.URL.Path {
44
-			case "/":
45
-				ui.dot(w, r)
46
-			case "/top":
47
-				ui.top(w, r)
48
-			case "/disasm":
49
-				ui.disasm(w, r)
50
-			case "/peek":
51
-				ui.peek(w, r)
52
-			case "/source":
53
-				ui.source(w, r)
54
-			}
55
-		}))
35
+	// Custom http server creator
36
+	var server *httptest.Server
37
+	serverCreated := make(chan bool)
38
+	creator := func(a *plugin.HTTPServerArgs) error {
39
+		server = httptest.NewServer(http.HandlerFunc(
40
+			func(w http.ResponseWriter, r *http.Request) {
41
+				if h := a.Handlers[r.URL.Path]; h != nil {
42
+					h.ServeHTTP(w, r)
43
+				}
44
+			}))
45
+		serverCreated <- true
46
+		return nil
47
+	}
48
+
49
+	// Start server and wait for it to be initialized
50
+	go serveWebInterface("unused:1234", prof, &plugin.Options{
51
+		Obj:        fakeObjTool{},
52
+		UI:         &stdUI{},
53
+		HTTPServer: creator,
54
+	})
55
+	<-serverCreated
56
 	defer server.Close()
56
 	defer server.Close()
57
 
57
 
58
 	haveDot := false
58
 	haveDot := false
195
 	}
195
 	}
196
 }
196
 }
197
 
197
 
198
-func TestNewListenerAndURL(t *testing.T) {
199
-	if runtime.GOOS == "nacl" {
200
-		t.Skip("test assumes tcp available")
201
-	}
202
-
203
-	tests := []struct {
204
-		hostport  string
205
-		wantErr   bool
206
-		wantURLRe *regexp.Regexp
207
-		wantLocal bool
208
-	}{
209
-		{
210
-			hostport:  ":",
211
-			wantURLRe: regexp.MustCompile(`http://localhost:\d+`),
212
-			wantLocal: true,
213
-		},
214
-		{
215
-			hostport:  "localhost:",
216
-			wantURLRe: regexp.MustCompile(`http://localhost:\d+`),
217
-			wantLocal: true,
218
-		},
219
-		{
220
-			hostport: "http://localhost:12345",
221
-			wantErr:  true,
222
-		},
223
-	}
224
-
225
-	for _, tt := range tests {
226
-		t.Run(tt.hostport, func(t *testing.T) {
227
-			_, url, isLocal, err := newListenerAndURL(tt.hostport)
228
-			if tt.wantErr {
229
-				if err == nil {
230
-					t.Fatalf("newListenerAndURL(%v) want error; got none", tt.hostport)
231
-				}
232
-				return
233
-			}
234
-			if err != nil {
235
-				t.Fatalf("newListenerAndURL(%v) = %v", tt.hostport, err)
236
-			}
237
-			if !tt.wantURLRe.MatchString(url) {
238
-				t.Errorf("newListenerAndURL(%v) URL = %v; want URL matching %v", tt.hostport, url, tt.wantURLRe)
239
-			}
240
-			if got, want := isLocal, tt.wantLocal; got != want {
241
-				t.Errorf("newListenerAndURL(%v) isLocal = %v; want %v", tt.hostport, got, want)
242
-			}
243
-		})
244
-	}
245
-}
246
-
247
 func TestIsLocalHost(t *testing.T) {
198
 func TestIsLocalHost(t *testing.T) {
248
 	for _, s := range []string{"localhost:10000", "[::1]:10000", "127.0.0.1:10000"} {
199
 	for _, s := range []string{"localhost:10000", "[::1]:10000", "127.0.0.1:10000"} {
249
 		host, _, err := net.SplitHostPort(s)
200
 		host, _, err := net.SplitHostPort(s)

+ 22
- 7
internal/plugin/plugin.go Vedi File

33
 	Obj     ObjTool
33
 	Obj     ObjTool
34
 	UI      UI
34
 	UI      UI
35
 
35
 
36
-	// HTTPWrapper takes a pprof http handler as an argument and
37
-	// returns the actual handler that should be invoked by http.
38
-	// A typical use is to add authentication before calling the
39
-	// pprof handler.
36
+	// HTTPServer is a function that should block serving http requests,
37
+	// including the handlers specfied in args.  If non-nil, pprof will
38
+	// invoke this function if necessary to provide a web interface.
40
 	//
39
 	//
41
-	// If HTTPWrapper is nil, a default wrapper will be used that
42
-	// disallows all requests except from the localhost.
43
-	HTTPWrapper func(http.Handler) http.Handler
40
+	// If HTTPServer is nil, pprof will use its own internal HTTP server.
41
+	//
42
+	// A common use for a custom HTTPServer is to provide custom
43
+	// authentication checks.
44
+	HTTPServer func(args *HTTPServerArgs) error
44
 }
45
 }
45
 
46
 
46
 // Writer provides a mechanism to write data under a certain name,
47
 // Writer provides a mechanism to write data under a certain name,
195
 	// the auto-completion of cmd, if the UI supports auto-completion at all.
196
 	// the auto-completion of cmd, if the UI supports auto-completion at all.
196
 	SetAutoComplete(complete func(string) string)
197
 	SetAutoComplete(complete func(string) string)
197
 }
198
 }
199
+
200
+// HTTPServerArgs contains arguments needed by an HTTP server that
201
+// is exporting a pprof web interface.
202
+type HTTPServerArgs struct {
203
+	// Hostport contains the http server address (derived from flags).
204
+	Hostport string
205
+
206
+	Host string // Host portion of Hostport
207
+	Port int    // Port portion of Hostport
208
+
209
+	// Handlers maps from URL paths to the handler to invoke to
210
+	// serve that path.
211
+	Handlers map[string]http.Handler
212
+}