diff --git a/CHANGELOG.md b/CHANGELOG.md index 50dfd9b58..e2575dc28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Enhancements: - feat(audit-log): add event-mapping command group ([#1875](https://github.com/fastly/cli/pull/1875)) +- feat(compute/serve): add `--experimental-websockets-passthrough` flag and `[local_server.websockets_passthrough]` section in `fastly.toml`, allowing local WebSocket passthrough to be disabled (it remains enabled by default) ### Dependencies: diff --git a/pkg/commands/compute/compute_test.go b/pkg/commands/compute/compute_test.go index 1e9ea7883..60ea4d9f6 100644 --- a/pkg/commands/compute/compute_test.go +++ b/pkg/commands/compute/compute_test.go @@ -103,6 +103,7 @@ func TestFlagDivergenceServe(t *testing.T) { "addr", "debug", "experimental-enable-pushpin", + "experimental-websockets-passthrough", "file", "profile-guest", "pushpin-path", diff --git a/pkg/commands/compute/serve.go b/pkg/commands/compute/serve.go index 02fd8f73f..26b4dc49a 100644 --- a/pkg/commands/compute/serve.go +++ b/pkg/commands/compute/serve.go @@ -63,20 +63,21 @@ type ServeCommand struct { ViceroyVersioner github.AssetVersioner // Serve private fields - addr string - debug bool - enablePushpin bool - pushpinRunnerBinPath string - pushpinProxyPort string - pushpinPublishPort string - env argparser.OptionalString - file argparser.OptionalString - profileGuest bool - profileGuestDir argparser.OptionalString - projectDir string - skipBuild bool - watch bool - watchDir argparser.OptionalString + addr string + debug bool + enablePushpin bool + enableWebsocketsPassthrough argparser.OptionalBool + pushpinRunnerBinPath string + pushpinProxyPort string + pushpinPublishPort string + env argparser.OptionalString + file argparser.OptionalString + profileGuest bool + profileGuestDir argparser.OptionalString + projectDir string + skipBuild bool + watch bool + watchDir argparser.OptionalString } // NewServeCommand returns a usable command registered under the parent. @@ -102,6 +103,7 @@ func NewServeCommand(parent argparser.Registerer, g *global.Data, build *BuildCo c.CmdClause.Flag("pushpin-path", "The path to a user installed version of the Pushpin runner binary").StringVar(&c.pushpinRunnerBinPath) c.CmdClause.Flag("pushpin-proxy-port", "The port to run the Pushpin runner on. Overrides 'local_server.pushpin.proxy_port' from 'fastly.toml', and if not specified there, defaults to 7677.").StringVar(&c.pushpinProxyPort) c.CmdClause.Flag("pushpin-publish-port", "The port to run the Pushpin publish handler on. Overrides 'local_server.pushpin.publish_port' from 'fastly.toml', and if not specified there, defaults to 5561.").StringVar(&c.pushpinPublishPort) + c.CmdClause.Flag("experimental-websockets-passthrough", "Enable WebSocket passthrough support for local testing of WebSockets. Overrides 'local_server.websockets_passthrough.enable' from 'fastly.toml', and if not specified there, defaults to true.").Action(c.enableWebsocketsPassthrough.Set).BoolVar(&c.enableWebsocketsPassthrough.Value) c.CmdClause.Flag("profile-guest", "Profile the Wasm guest under Viceroy (requires Viceroy 0.9.1 or higher). View profiles at https://profiler.firefox.com/.").BoolVar(&c.profileGuest) c.CmdClause.Flag("profile-guest-dir", "The directory where the per-request profiles are saved to. Defaults to guest-profiles.").Action(c.profileGuestDir.Set).StringVar(&c.profileGuestDir.Value) c.CmdClause.Flag("skip-build", "Skip the build step").BoolVar(&c.skipBuild) @@ -223,6 +225,19 @@ func (c *ServeCommand) Exec(in io.Reader, out io.Writer) (err error) { defer pushpinCtx.Close() } + // WebSocket passthrough is enabled unless it's explicitly disabled via the + // --experimental-websockets-passthrough flag, or via + // `local_server.websockets_passthrough.enable` in fastly.toml. The flag + // takes precedence over the manifest. + enableWebsocketsPassthrough := true + switch { + case c.enableWebsocketsPassthrough.WasSet: + enableWebsocketsPassthrough = c.enableWebsocketsPassthrough.Value + case c.Globals.Manifest.File.LocalServer.WebsocketsPassthrough != nil && + c.Globals.Manifest.File.LocalServer.WebsocketsPassthrough.EnableWebsocketsPassthrough != nil: + enableWebsocketsPassthrough = *c.Globals.Manifest.File.LocalServer.WebsocketsPassthrough.EnableWebsocketsPassthrough + } + err = spinner.Start() if err != nil { return err @@ -243,21 +258,22 @@ func (c *ServeCommand) Exec(in io.Reader, out io.Writer) (err error) { var restart bool for { err = local(localOpts{ - addr: c.addr, - bin: bin, - debug: c.debug, - errLog: c.Globals.ErrLog, - extraArgs: c.ViceroyBinExtraArgs, - manifestPath: manifestPath, - out: out, - profileGuest: c.profileGuest, - profileGuestDir: c.profileGuestDir, - pushpinProxyPort: pushpinCtx.proxyPort, - restarted: restart, - verbose: c.Globals.Verbose(), - wasmBinPath: wasmBinaryToRun, - watch: c.watch, - watchDir: c.watchDir, + addr: c.addr, + bin: bin, + debug: c.debug, + enableWebsocketsPassthrough: enableWebsocketsPassthrough, + errLog: c.Globals.ErrLog, + extraArgs: c.ViceroyBinExtraArgs, + manifestPath: manifestPath, + out: out, + profileGuest: c.profileGuest, + profileGuestDir: c.profileGuestDir, + pushpinProxyPort: pushpinCtx.proxyPort, + restarted: restart, + verbose: c.Globals.Verbose(), + wasmBinPath: wasmBinaryToRun, + watch: c.watch, + watchDir: c.watchDir, }) if err != nil { if err != fsterr.ErrViceroyRestart { @@ -820,21 +836,22 @@ func (c *ServeCommand) startPushpin(spinner text.Spinner, out io.Writer) (pushpi // localOpts represents the inputs for `local()`. type localOpts struct { - addr string - bin string - debug bool - errLog fsterr.LogInterface - extraArgs string - manifestPath string - out io.Writer - profileGuest bool - profileGuestDir argparser.OptionalString - pushpinProxyPort uint16 - restarted bool - verbose bool - wasmBinPath string - watch bool - watchDir argparser.OptionalString + addr string + bin string + debug bool + enableWebsocketsPassthrough bool + errLog fsterr.LogInterface + extraArgs string + manifestPath string + out io.Writer + profileGuest bool + profileGuestDir argparser.OptionalString + pushpinProxyPort uint16 + restarted bool + verbose bool + wasmBinPath string + watch bool + watchDir argparser.OptionalString } // local spawns a subprocess that runs the compiled binary. @@ -863,6 +880,12 @@ func local(opts localOpts) error { args = append(args, fmt.Sprintf("--local-pushpin-proxy-port=%d", opts.pushpinProxyPort)) } + // Viceroy enables WebSocket passthrough by default, so we only need to pass + // the flag when it has been explicitly disabled in the manifest. + if !opts.enableWebsocketsPassthrough { + args = append(args, "--enable-local-websocket-passthrough=false") + } + if opts.extraArgs != "" { extraArgs := strings.Split(opts.extraArgs, " ") args = append(args, extraArgs...) diff --git a/pkg/manifest/file.go b/pkg/manifest/file.go index c0804f7cc..d0c6aeeb1 100644 --- a/pkg/manifest/file.go +++ b/pkg/manifest/file.go @@ -109,6 +109,14 @@ func (f *File) MarshalTOML() ([]byte, error) { localServer["pushpin"] = pushpin } + if f.LocalServer.WebsocketsPassthrough != nil { + websocketsPassthrough := make(map[string]any) + if f.LocalServer.WebsocketsPassthrough.EnableWebsocketsPassthrough != nil { + websocketsPassthrough["enable"] = *f.LocalServer.WebsocketsPassthrough.EnableWebsocketsPassthrough + } + localServer["websockets_passthrough"] = websocketsPassthrough + } + if f.LocalServer.SecretStores != nil { secretStores := make(map[string]any) for key, entry := range f.LocalServer.SecretStores { diff --git a/pkg/manifest/local_server.go b/pkg/manifest/local_server.go index 61c3377fe..f44b8b877 100644 --- a/pkg/manifest/local_server.go +++ b/pkg/manifest/local_server.go @@ -9,12 +9,13 @@ import ( // LocalServer represents a list of mocked Viceroy resources. type LocalServer struct { - Backends map[string]LocalBackend `toml:"backends"` - ConfigStores map[string]LocalConfigStore `toml:"config_stores,omitempty"` - KVStores LocalKVStoreMap `toml:"kv_stores,omitempty"` - SecretStores LocalSecretStoreMap `toml:"secret_stores,omitempty"` - Pushpin *LocalPushpinMap `toml:"pushpin,omitempty"` - ViceroyVersion string `toml:"viceroy_version,omitempty"` + Backends map[string]LocalBackend `toml:"backends"` + ConfigStores map[string]LocalConfigStore `toml:"config_stores,omitempty"` + KVStores LocalKVStoreMap `toml:"kv_stores,omitempty"` + SecretStores LocalSecretStoreMap `toml:"secret_stores,omitempty"` + Pushpin *LocalPushpinMap `toml:"pushpin,omitempty"` + WebsocketsPassthrough *LocalWebsocketsPassthroughMap `toml:"websockets_passthrough,omitempty"` + ViceroyVersion string `toml:"viceroy_version,omitempty"` } // LocalBackend represents a backend to be mocked by the local testing server. @@ -203,6 +204,12 @@ type LocalPushpinMap struct { PushpinPublishPort *uint16 `toml:"publish_port,omitempty"` } +// LocalWebsocketsPassthroughMap represents configuration of local WebSocket +// passthrough support, used for local testing of handoff_websocket. +type LocalWebsocketsPassthroughMap struct { + EnableWebsocketsPassthrough *bool `toml:"enable,omitempty"` +} + func decodeTOMLMap(m map[string]any, out any) error { buf := new(bytes.Buffer) enc := toml.NewEncoder(buf) diff --git a/pkg/manifest/testdata/fastly-viceroy-update.toml b/pkg/manifest/testdata/fastly-viceroy-update.toml index 405fdc807..15606301b 100644 --- a/pkg/manifest/testdata/fastly-viceroy-update.toml +++ b/pkg/manifest/testdata/fastly-viceroy-update.toml @@ -57,6 +57,9 @@ pushpin_path = "path/to/pushpin" proxy_port = 7777 publish_port = 6666 +[local_server.websockets_passthrough] +enable = false + [local_server.secret_stores] store_one = [ { key = "first", data = "This is some secret data" },