Compare commits

...

1 Commits

Author SHA1 Message Date
Shayne Sweeney 1ceaabbc7b
cmd/tailscale/cli: allow empty text ("") serve
Current behavior is broken. `tailscale serve text / ""` returns no error
and shows up in `tailscale serve status` but requests return a 500
"empty handler".

The commit changes the handler field `Text` to type of `*string`.

Closes #6405

Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2022-11-20 12:56:12 -05:00
8 changed files with 28 additions and 14 deletions

View File

@ -53,6 +53,12 @@ func outln(a ...any) {
fmt.Fprintln(Stdout, a...) fmt.Fprintln(Stdout, a...)
} }
// ptrTo returns a pointer to the provided value.
// It's a convenience function to avoid having to write &value,
// or where the language doesn't allow it.
// Used primarily in tests.
func ptrTo[T any](v T) *T { return &v }
// ActLikeCLI reports whether a GUI application should act like the // ActLikeCLI reports whether a GUI application should act like the
// CLI based on os.Args, GOOS, the context the process is running in // CLI based on os.Args, GOOS, the context the process is running in
// (pty, parent PID), etc. // (pty, parent PID), etc.

View File

@ -285,7 +285,7 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
} }
h.Proxy = t h.Proxy = t
case "text": case "text":
h.Text = args[2] h.Text = ptrTo(args[2])
default: default:
fmt.Fprintf(os.Stderr, "error: unknown serve type %q\n\n", args[1]) fmt.Fprintf(os.Stderr, "error: unknown serve type %q\n\n", args[1])
return flag.ErrHelp return flag.ErrHelp
@ -547,8 +547,8 @@ func printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) {
return "path", h.Path return "path", h.Path
case h.Proxy != "": case h.Proxy != "":
return "proxy", h.Proxy return "proxy", h.Proxy
case h.Text != "": case h.Text != nil:
return "text", "\"" + elipticallyTruncate(h.Text, 20) + "\"" return "text", "\"" + elipticallyTruncate(*h.Text, 20) + "\""
} }
return "", "" return "", ""
} }

View File

@ -151,7 +151,7 @@ func TestServeConfigMutations(t *testing.T) {
"/abc": {Proxy: "http://127.0.0.1:3001"}, "/abc": {Proxy: "http://127.0.0.1:3001"},
}}, }},
"foo.test.ts.net:10000": {Handlers: map[string]*ipn.HTTPHandler{ "foo.test.ts.net:10000": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Text: "hi"}, "/": {Text: ptrTo("hi")},
}}, }},
}, },
}, },
@ -314,7 +314,7 @@ func TestServeConfigMutations(t *testing.T) {
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Text: "hello"}, "/": {Text: ptrTo("hello")},
}}, }},
}, },
}, },
@ -547,7 +547,7 @@ func TestServeConfigMutations(t *testing.T) {
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/tcp": {Text: "foo"}, "/tcp": {Text: ptrTo("foo")},
}}, }},
}, },
}, },

View File

@ -13,8 +13,6 @@ import (
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
) )
func ptrTo[T any](v T) *T { return &v }
func TestCalcAdvertiseRoutesForSet(t *testing.T) { func TestCalcAdvertiseRoutesForSet(t *testing.T) {
pfx := netip.MustParsePrefix pfx := netip.MustParsePrefix
tests := []struct { tests := []struct {

View File

@ -118,6 +118,10 @@ func (src *HTTPHandler) Clone() *HTTPHandler {
} }
dst := new(HTTPHandler) dst := new(HTTPHandler)
*dst = *src *dst = *src
if dst.Text != nil {
dst.Text = new(string)
*dst.Text = *src.Text
}
return dst return dst
} }
@ -125,7 +129,7 @@ func (src *HTTPHandler) Clone() *HTTPHandler {
var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct { var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
Path string Path string
Proxy string Proxy string
Text string Text *string
}{}) }{})
// Clone makes a deep copy of WebServerConfig. // Clone makes a deep copy of WebServerConfig.

View File

@ -290,13 +290,19 @@ func (v *HTTPHandlerView) UnmarshalJSON(b []byte) error {
func (v HTTPHandlerView) Path() string { return v.ж.Path } func (v HTTPHandlerView) Path() string { return v.ж.Path }
func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy } func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy }
func (v HTTPHandlerView) Text() string { return v.ж.Text } func (v HTTPHandlerView) Text() *string {
if v.ж.Text == nil {
return nil
}
x := *v.ж.Text
return &x
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file. // A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct { var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct {
Path string Path string
Proxy string Proxy string
Text string Text *string
}{}) }{})
// View returns a readonly view of WebServerConfig. // View returns a readonly view of WebServerConfig.

View File

@ -395,9 +395,9 @@ func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
if s := h.Text(); s != "" { if s := h.Text(); s != nil {
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
io.WriteString(w, s) io.WriteString(w, *s)
return return
} }
if v := h.Path(); v != "" { if v := h.Path(); v != "" {

View File

@ -65,7 +65,7 @@ type HTTPHandler struct {
Path string `json:",omitempty"` // absolute path to directory or file to serve Path string `json:",omitempty"` // absolute path to directory or file to serve
Proxy string `json:",omitempty"` // http://localhost:3000/, localhost:3030, 3030 Proxy string `json:",omitempty"` // http://localhost:3000/, localhost:3030, 3030
Text string `json:",omitempty"` // plaintext to serve (primarily for testing) Text *string `json:",omitempty"` // plaintext to serve (primarily for testing)
// TODO(bradfitz): bool to not enumerate directories? TTL on mapping for // TODO(bradfitz): bool to not enumerate directories? TTL on mapping for
// temporary ones? Error codes? Redirects? // temporary ones? Error codes? Redirects?