Kaynağa Gözat

Allow -http to accept host:port (#163)

* Allow -http to accept host:port

* Add serveWebInterface tests.

* Make sure serveWebInterface is serving on the URL.

* Avoid nil pointer dereference

* Simplify the -http flag test.

* Use the right capitalization for hostport.

* Fix the double escaping in regexps.

* Address review comments.
Jaana B. Dogan 8 yıl önce
ebeveyn
işleme
2cafe4be5a

+ 14
- 14
internal/driver/cli.go Dosyayı Görüntüle

@@ -30,10 +30,10 @@ type source struct {
30 30
 	Base      []string
31 31
 	Normalize bool
32 32
 
33
-	Seconds   int
34
-	Timeout   int
35
-	Symbolize string
36
-	HTTPPort  int
33
+	Seconds      int
34
+	Timeout      int
35
+	Symbolize    string
36
+	HTTPHostport string
37 37
 }
38 38
 
39 39
 // Parse parses the command lines through the specified flags package
@@ -60,7 +60,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
60 60
 	flagTools := flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames")
61 61
 
62 62
 	flagTimeout := flag.Int("timeout", -1, "Timeout in seconds for fetching a profile")
63
-	flagHTTPPort := flag.Int("http", 0, "Present interactive web based UI at the specified http port")
63
+	flagHTTP := flag.String("http", "", "Present interactive web based UI at the specified http host:port")
64 64
 
65 65
 	// Flags used during command processing
66 66
 	installedFlags := installFlags(flag)
@@ -109,7 +109,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
109 109
 	if err != nil {
110 110
 		return nil, nil, err
111 111
 	}
112
-	if cmd != nil && *flagHTTPPort != 0 {
112
+	if cmd != nil && *flagHTTP != "" {
113 113
 		return nil, nil, fmt.Errorf("--http is not compatible with an output format on the command line")
114 114
 	}
115 115
 
@@ -128,13 +128,13 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
128 128
 	}
129 129
 
130 130
 	source := &source{
131
-		Sources:   args,
132
-		ExecName:  execName,
133
-		BuildID:   *flagBuildID,
134
-		Seconds:   *flagSeconds,
135
-		Timeout:   *flagTimeout,
136
-		Symbolize: *flagSymbolize,
137
-		HTTPPort:  *flagHTTPPort,
131
+		Sources:      args,
132
+		ExecName:     execName,
133
+		BuildID:      *flagBuildID,
134
+		Seconds:      *flagSeconds,
135
+		Timeout:      *flagTimeout,
136
+		Symbolize:    *flagSymbolize,
137
+		HTTPHostport: *flagHTTP,
138 138
 	}
139 139
 
140 140
 	for _, s := range *flagBase {
@@ -292,7 +292,7 @@ var usageMsgSrc = "\n\n" +
292 292
 
293 293
 var usageMsgVars = "\n\n" +
294 294
 	"  Misc options:\n" +
295
-	"   -http port             Provide web based interface at port\n" +
295
+	"   -http host:port        Provide web based interface at host:port\n" +
296 296
 	"   -tools                 Search path for object tools\n" +
297 297
 	"\n" +
298 298
 	"  Environment Variables:\n" +

+ 2
- 2
internal/driver/driver.go Dosyayı Görüntüle

@@ -52,8 +52,8 @@ func PProf(eo *plugin.Options) error {
52 52
 		return generateReport(p, cmd, pprofVariables, o)
53 53
 	}
54 54
 
55
-	if src.HTTPPort > 0 {
56
-		return serveWebInterface(src.HTTPPort, p, o)
55
+	if src.HTTPHostport != "" {
56
+		return serveWebInterface(src.HTTPHostport, p, o)
57 57
 	}
58 58
 	return interactive(p, o)
59 59
 }

+ 46
- 12
internal/driver/webui.go Dosyayı Görüntüle

@@ -21,7 +21,7 @@ import (
21 21
 	"io"
22 22
 	"net"
23 23
 	"net/http"
24
-	"net/url"
24
+	gourl "net/url"
25 25
 	"os"
26 26
 	"os/exec"
27 27
 	"regexp"
@@ -51,28 +51,62 @@ func (ec *errorCatcher) PrintErr(args ...interface{}) {
51 51
 	ec.UI.PrintErr(args...)
52 52
 }
53 53
 
54
-func serveWebInterface(port int, p *profile.Profile, o *plugin.Options) error {
54
+func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
55 55
 	interactiveMode = true
56 56
 	ui := &webInterface{
57 57
 		prof:    p,
58 58
 		options: o,
59 59
 	}
60
+
61
+	ln, url, isLocal, err := newListenerAndURL(hostport)
62
+	if err != nil {
63
+		return err
64
+	}
65
+
60 66
 	// authorization wrapper
61 67
 	wrap := o.HTTPWrapper
62
-	if wrap == nil {
63
-		// only allow requests from local host
68
+	if wrap == nil && isLocal {
69
+		// Only allow requests from local host.
64 70
 		wrap = checkLocalHost
65 71
 	}
66
-	http.Handle("/", wrap(http.HandlerFunc(ui.dot)))
67
-	http.Handle("/disasm", wrap(http.HandlerFunc(ui.disasm)))
68
-	http.Handle("/weblist", wrap(http.HandlerFunc(ui.weblist)))
69
-	go openBrowser(port, o)
70
-	return http.ListenAndServe(fmt.Sprint(":", port), nil)
72
+
73
+	mux := http.NewServeMux()
74
+	mux.Handle("/", wrap(http.HandlerFunc(ui.dot)))
75
+	mux.Handle("/disasm", wrap(http.HandlerFunc(ui.disasm)))
76
+	mux.Handle("/weblist", wrap(http.HandlerFunc(ui.weblist)))
77
+
78
+	s := &http.Server{Handler: mux}
79
+	go openBrowser(url, o)
80
+	return s.Serve(ln)
81
+}
82
+
83
+func newListenerAndURL(hostport string) (ln net.Listener, url string, isLocal bool, err error) {
84
+	host, _, err := net.SplitHostPort(hostport)
85
+	if err != nil {
86
+		return nil, "", false, err
87
+	}
88
+	if host == "" {
89
+		host = "localhost"
90
+	}
91
+	if ln, err = net.Listen("tcp", hostport); err != nil {
92
+		return nil, "", false, err
93
+	}
94
+	url = fmt.Sprint("http://", host, ":", ln.Addr().(*net.TCPAddr).Port)
95
+	return ln, url, isLocalhost(host), nil
96
+}
97
+
98
+func isLocalhost(host string) bool {
99
+	for _, v := range []string{"localhost", "127.0.0.1", "[::1]"} {
100
+		if host == v {
101
+			return true
102
+		}
103
+	}
104
+	return false
71 105
 }
72 106
 
73
-func openBrowser(port int, o *plugin.Options) {
107
+func openBrowser(url string, o *plugin.Options) {
74 108
 	// Construct URL.
75
-	u, _ := url.Parse(fmt.Sprint("http://localhost:", port))
109
+	u, _ := gourl.Parse(url)
76 110
 	q := u.Query()
77 111
 	for _, p := range []struct{ param, key string }{
78 112
 		{"f", "focus"},
@@ -107,7 +141,7 @@ func openBrowser(port int, o *plugin.Options) {
107 141
 func checkLocalHost(h http.Handler) http.Handler {
108 142
 	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
109 143
 		host, _, err := net.SplitHostPort(req.RemoteAddr)
110
-		if err != nil || ((host != "127.0.0.1") && (host != "::1")) {
144
+		if err != nil || !isLocalhost(host) {
111 145
 			http.Error(w, "permission denied", http.StatusForbidden)
112 146
 			return
113 147
 		}

+ 55
- 0
internal/driver/webui_test.go Dosyayı Görüntüle

@@ -183,3 +183,58 @@ func makeFakeProfile() *profile.Profile {
183 183
 		Mapping:  mapping,
184 184
 	}
185 185
 }
186
+
187
+func TestNewListenerAndURL(t *testing.T) {
188
+	tests := []struct {
189
+		hostport  string
190
+		wantErr   bool
191
+		wantURLRe *regexp.Regexp
192
+		wantLocal bool
193
+	}{
194
+		{
195
+			hostport:  ":",
196
+			wantURLRe: regexp.MustCompile(`http://localhost:\d+`),
197
+			wantLocal: true,
198
+		},
199
+		{
200
+			hostport:  "localhost:",
201
+			wantURLRe: regexp.MustCompile(`http://localhost:\d+`),
202
+			wantLocal: true,
203
+		},
204
+		{
205
+			hostport:  "127.0.0.1:",
206
+			wantURLRe: regexp.MustCompile(`http://127\.0\.0\.1:\d+`),
207
+			wantLocal: true,
208
+		},
209
+		{
210
+			hostport:  "localhost:12344",
211
+			wantURLRe: regexp.MustCompile(`http://localhost:12344`),
212
+			wantLocal: true,
213
+		},
214
+		{
215
+			hostport: "http://localhost:12345",
216
+			wantErr:  true,
217
+		},
218
+	}
219
+
220
+	for _, tt := range tests {
221
+		t.Run(tt.hostport, func(t *testing.T) {
222
+			_, url, isLocal, err := newListenerAndURL(tt.hostport)
223
+			if tt.wantErr {
224
+				if err == nil {
225
+					t.Fatalf("newListenerAndURL(%v) want error; got none", tt.hostport)
226
+				}
227
+				return
228
+			}
229
+			if err != nil {
230
+				t.Fatalf("newListenerAndURL(%v) = %v", tt.hostport, err)
231
+			}
232
+			if !tt.wantURLRe.MatchString(url) {
233
+				t.Errorf("newListenerAndURL(%v) URL = %v; want URL matching %v", tt.hostport, url, tt.wantURLRe)
234
+			}
235
+			if got, want := isLocal, tt.wantLocal; got != want {
236
+				t.Errorf("newListenerAndURL(%v) isLocal = %v; want %v", tt.hostport, got, want)
237
+			}
238
+		})
239
+	}
240
+}