Skip to content

Commit e7d0a14

Browse files
committed
Add experimental "enable Pushpin" mode
1 parent 3580a2e commit e7d0a14

3 files changed

Lines changed: 182 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Breaking:
66

77
### Enhancements:
8+
- Add experimental "enable Pushpin" mode
89

910
### Bug fixes:
1011

pkg/commands/compute/compute_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,11 @@ func TestFlagDivergenceServe(t *testing.T) {
101101
ignoreServeFlags := []string{
102102
"addr",
103103
"debug",
104+
"experimental-enable-pushpin",
104105
"file",
105106
"profile-guest",
107+
"pushpin-path",
108+
"pushpin-port",
106109
"profile-guest-dir",
107110
"skip-build",
108111
"viceroy-args",

pkg/commands/compute/serve.go

Lines changed: 178 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,19 @@ type ServeCommand struct {
6161
ViceroyVersioner github.AssetVersioner
6262

6363
// Serve private fields
64-
addr string
65-
debug bool
66-
env argparser.OptionalString
67-
file argparser.OptionalString
68-
profileGuest bool
69-
profileGuestDir argparser.OptionalString
70-
projectDir string
71-
skipBuild bool
72-
watch bool
73-
watchDir argparser.OptionalString
64+
addr string
65+
debug bool
66+
enablePushpin bool
67+
pushpinRunnerBinPath string
68+
pushpinProxyPort string
69+
env argparser.OptionalString
70+
file argparser.OptionalString
71+
profileGuest bool
72+
profileGuestDir argparser.OptionalString
73+
projectDir string
74+
skipBuild bool
75+
watch bool
76+
watchDir argparser.OptionalString
7477
}
7578

7679
// NewServeCommand returns a usable command registered under the parent.
@@ -92,6 +95,9 @@ func NewServeCommand(parent argparser.Registerer, g *global.Data, build *BuildCo
9295
c.CmdClause.Flag("metadata-filter-envvars", "Redact specified environment variables from [scripts.env_vars] using comma-separated list").Action(c.metadataFilterEnvVars.Set).StringVar(&c.metadataFilterEnvVars.Value)
9396
c.CmdClause.Flag("metadata-show", "Inspect the Wasm binary metadata").Action(c.metadataShow.Set).BoolVar(&c.metadataShow.Value)
9497
c.CmdClause.Flag("package-name", "Package name").Action(c.packageName.Set).StringVar(&c.packageName.Value)
98+
c.CmdClause.Flag("experimental-enable-pushpin", "Enable experimental Pushpin support for local testing of Fanout and WebSockets").BoolVar(&c.enablePushpin)
99+
c.CmdClause.Flag("pushpin-path", "The path to a user installed version of the Pushpin runner binary").StringVar(&c.pushpinRunnerBinPath)
100+
c.CmdClause.Flag("pushpin-port", "The port to run the Pushpin runner on. Defaults to 7677.").Default("7677").StringVar(&c.pushpinProxyPort)
95101
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)
96102
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)
97103
c.CmdClause.Flag("skip-build", "Skip the build step").BoolVar(&c.skipBuild)
@@ -197,6 +203,51 @@ func (c *ServeCommand) Exec(in io.Reader, out io.Writer) (err error) {
197203
return err
198204
}
199205

206+
if c.enablePushpin {
207+
pushpinRunnerBin, err := c.GetPushpinRunner(out)
208+
if err != nil {
209+
return err
210+
}
211+
212+
var pushpinCmd *exec.Cmd
213+
if pushpinRunnerBin != "" {
214+
// The port should match what we tell Viceroy.
215+
args := []string{"--port=" + c.pushpinProxyPort}
216+
217+
// Iterate backends and add them
218+
routes := c.BuildPushpinRoutes()
219+
args = append(args, routes...)
220+
221+
args = append(args, "--verbose")
222+
pushpinCmd = exec.Command(pushpinRunnerBin, args...)
223+
224+
// If verbose, show the user what's happening and pipe Pushpin's output.
225+
if c.Globals.Verbose() {
226+
text.Info(out, "Starting Pushpin runner in the background...")
227+
text.Output(out, "%s: %s", text.BoldYellow("Pushpin command"), strings.Join(pushpinCmd.Args, " "))
228+
pushpinCmd.Stdout = out
229+
pushpinCmd.Stderr = out
230+
}
231+
232+
if err := pushpinCmd.Start(); err != nil {
233+
return fmt.Errorf("failed to start Pushpin runner: %w", err)
234+
}
235+
236+
// Ensure the Pushpin process is terminated when the serve command exits.
237+
defer func() {
238+
if pushpinCmd != nil && pushpinCmd.Process != nil {
239+
if c.Globals.Verbose() {
240+
text.Info(out, "Stopping Pushpin runner...")
241+
}
242+
// Attempt to kill the process. We don't block on waiting for it to exit.
243+
if err := pushpinCmd.Process.Kill(); err != nil {
244+
c.Globals.ErrLog.Add(fmt.Errorf("failed to kill Pushpin process: %w", err))
245+
}
246+
}
247+
}()
248+
}
249+
}
250+
200251
err = spinner.Start()
201252
if err != nil {
202253
return err
@@ -217,20 +268,22 @@ func (c *ServeCommand) Exec(in io.Reader, out io.Writer) (err error) {
217268
var restart bool
218269
for {
219270
err = local(localOpts{
220-
addr: c.addr,
221-
bin: bin,
222-
debug: c.debug,
223-
errLog: c.Globals.ErrLog,
224-
extraArgs: c.ViceroyBinExtraArgs,
225-
manifestPath: manifestPath,
226-
out: out,
227-
profileGuest: c.profileGuest,
228-
profileGuestDir: c.profileGuestDir,
229-
restarted: restart,
230-
verbose: c.Globals.Verbose(),
231-
wasmBinPath: wasmBinaryToRun,
232-
watch: c.watch,
233-
watchDir: c.watchDir,
271+
addr: c.addr,
272+
bin: bin,
273+
debug: c.debug,
274+
errLog: c.Globals.ErrLog,
275+
extraArgs: c.ViceroyBinExtraArgs,
276+
manifestPath: manifestPath,
277+
out: out,
278+
profileGuest: c.profileGuest,
279+
profileGuestDir: c.profileGuestDir,
280+
enablePushpin: c.enablePushpin,
281+
pushpinProxyPort: c.pushpinProxyPort,
282+
restarted: restart,
283+
verbose: c.Globals.Verbose(),
284+
wasmBinPath: wasmBinaryToRun,
285+
watch: c.watch,
286+
watchDir: c.watchDir,
234287
})
235288
if err != nil {
236289
if err != fsterr.ErrViceroyRestart {
@@ -552,22 +605,105 @@ func (c *ServeCommand) InstallViceroy(
552605
return spinner.Stop()
553606
}
554607

608+
// GetPushpinRunner returns the path to the installed binary.
609+
//
610+
// This value comes from searching the system path for `pushpin`
611+
// It can be overridden by providing the --pushpin-path command-line parameter.
612+
func (c *ServeCommand) GetPushpinRunner(out io.Writer) (bin string, err error) {
613+
if c.pushpinRunnerBinPath != "" {
614+
if c.Globals.Verbose() {
615+
text.Info(out, "Using user provided install of Pushpin runner via --pushpin-path flag: %s\n\n", c.pushpinRunnerBinPath)
616+
}
617+
return filepath.Abs(c.pushpinRunnerBinPath)
618+
}
619+
620+
if c.Globals.Verbose() {
621+
text.Info(out, "No --pushpin-path provided, attempting to find 'pushpin' in your PATH...")
622+
}
623+
path, err := exec.LookPath("pushpin")
624+
if err != nil {
625+
return "", fsterr.RemediationError{
626+
Inner: fmt.Errorf("failed to find 'pushpin' in your $PATH"),
627+
Remediation: "Pushpin support was enabled via --enable-experimental-pushpin, but the 'pushpin' binary could not be found in your $PATH. Please install Pushpin (see: https://pushpin.org/docs/install/) or provide a path to the binary using the --pushpin-path flag.",
628+
}
629+
}
630+
631+
if c.Globals.Verbose() {
632+
text.Info(out, "Found Pushpin runner via $PATH lookup: %s\n\n", path)
633+
}
634+
return filepath.Abs(path)
635+
}
636+
637+
// BuildPushpinRoutes builds a slice of strings based on the backends
638+
// defined in the manifest's backend section.
639+
func (c *ServeCommand) BuildPushpinRoutes() []string {
640+
var routes []string
641+
for name, backend := range c.Globals.Manifest.File.LocalServer.Backends {
642+
643+
// e.g. a backend named "origin" will be mapped from "/origin".
644+
pathPrefix := "/" + name
645+
646+
// The target should be a URL
647+
u, err := url.Parse(backend.URL)
648+
if err != nil {
649+
// This is unlikely as we parse it elsewhere, but good to be safe.
650+
// We'll just skip this backend if the URL is invalid.
651+
continue
652+
}
653+
654+
// A backend may have a path component. If it does, then
655+
// it will be prepended during forwarding.
656+
forwardPrefix := strings.TrimSuffix(u.Path, "/")
657+
658+
// Route Rule:
659+
// 1. `*`: Match any Host header from the incoming request.
660+
// 2. `path_beg`: Match requests whose path begins with the backend name prefix.
661+
// 3. `replace_beg`: Replace the matched prefix with the forward prefix.
662+
rules := fmt.Sprintf(
663+
"*,path_beg=%s,replace_beg=%s",
664+
pathPrefix,
665+
forwardPrefix,
666+
)
667+
// 4. `as_host`: If the backend has an override_host.
668+
if backend.OverrideHost != "" {
669+
rules += fmt.Sprintf(",as_host=%s", backend.OverrideHost)
670+
}
671+
672+
// Target:
673+
// 1. `over_http`: Enable WebSocket-over-HTTP
674+
target := u.Host + ",over_http"
675+
// 2. `ssl`: If backend is https
676+
if u.Scheme == "https" {
677+
target += ",ssl"
678+
}
679+
680+
// The final argument format is "--route=<rules> <target_url>"
681+
routeArg := fmt.Sprintf("--route=%s %s", rules, target)
682+
routes = append(routes, routeArg)
683+
684+
}
685+
686+
return routes
687+
}
688+
555689
// localOpts represents the inputs for `local()`.
556690
type localOpts struct {
557-
addr string
558-
bin string
559-
debug bool
560-
errLog fsterr.LogInterface
561-
extraArgs string
562-
manifestPath string
563-
out io.Writer
564-
profileGuest bool
565-
profileGuestDir argparser.OptionalString
566-
restarted bool
567-
verbose bool
568-
wasmBinPath string
569-
watch bool
570-
watchDir argparser.OptionalString
691+
addr string
692+
bin string
693+
debug bool
694+
errLog fsterr.LogInterface
695+
extraArgs string
696+
manifestPath string
697+
out io.Writer
698+
profileGuest bool
699+
profileGuestDir argparser.OptionalString
700+
enablePushpin bool
701+
pushpinProxyPort string
702+
restarted bool
703+
verbose bool
704+
wasmBinPath string
705+
watch bool
706+
watchDir argparser.OptionalString
571707
}
572708

573709
// local spawns a subprocess that runs the compiled binary.
@@ -592,6 +728,10 @@ func local(opts localOpts) error {
592728
}
593729
}
594730

731+
if opts.enablePushpin {
732+
args = append(args, "--local-pushpin-proxy-port="+opts.pushpinProxyPort)
733+
}
734+
595735
if opts.extraArgs != "" {
596736
extraArgs := strings.Split(opts.extraArgs, " ")
597737
args = append(args, extraArgs...)

0 commit comments

Comments
 (0)