From b26f1af438b0d7982193c5ab889c1a88db081537 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Tue, 4 Jun 2024 16:21:17 -0400 Subject: [PATCH] fix: use random available port by default Instead of using a default port of 9090, this change will set the port for the various servers to a random available port. Signed-off-by: Donnie Adams --- pkg/cli/gptscript.go | 2 +- pkg/sdkserver/routes.go | 10 ++++++++-- pkg/sdkserver/server.go | 21 ++++++++++++++++----- pkg/server/server.go | 10 ++++++++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pkg/cli/gptscript.go b/pkg/cli/gptscript.go index 7256c18d..d2515673 100644 --- a/pkg/cli/gptscript.go +++ b/pkg/cli/gptscript.go @@ -59,7 +59,7 @@ type GPTScript struct { ListModels bool `usage:"List the models available and exit" local:"true"` ListTools bool `usage:"List built-in tools and exit" local:"true"` Server bool `usage:"Start server" local:"true"` - ListenAddress string `usage:"Server listen address" default:"127.0.0.1:9090" local:"true"` + ListenAddress string `usage:"Server listen address" default:"127.0.0.1:0" local:"true"` Chdir string `usage:"Change current working directory" short:"C"` Daemon bool `usage:"Run tool as a daemon" local:"true" hidden:"true"` Ports string `usage:"The port range to use for ephemeral daemon ports (ex: 11000-12000)" hidden:"true"` diff --git a/pkg/sdkserver/routes.go b/pkg/sdkserver/routes.go index c049a791..2fbc422c 100644 --- a/pkg/sdkserver/routes.go +++ b/pkg/sdkserver/routes.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "os" - "slices" "sort" "strings" "sync" @@ -165,7 +164,14 @@ func (s *server) execHandler(w http.ResponseWriter, r *http.Request) { reqObject.Env = append(os.Environ(), reqObject.Env...) // Don't overwrite the PromptURLEnvVar if it is already set in the environment. - if !slices.ContainsFunc(reqObject.Env, func(s string) bool { return strings.HasPrefix(s, types.PromptTokenEnvVar+"=") }) { + var promptTokenAlreadySet bool + for _, env := range reqObject.Env { + if strings.HasPrefix(env, types.PromptTokenEnvVar+"=") { + promptTokenAlreadySet = true + break + } + } + if !promptTokenAlreadySet { // Append a prompt URL for this run. reqObject.Env = append(reqObject.Env, fmt.Sprintf("%s=http://%s/prompt/%s", types.PromptURLEnvVar, s.address, runID), fmt.Sprintf("%s=%s", types.PromptTokenEnvVar, s.token)) } diff --git a/pkg/sdkserver/server.go b/pkg/sdkserver/server.go index 1433606a..ee111ac5 100644 --- a/pkg/sdkserver/server.go +++ b/pkg/sdkserver/server.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log/slog" + "net" "net/http" "os" "os/signal" @@ -17,6 +18,7 @@ import ( "github.com/gptscript-ai/gptscript/pkg/gptscript" "github.com/gptscript-ai/gptscript/pkg/mvl" "github.com/gptscript-ai/gptscript/pkg/runner" + "github.com/gptscript-ai/gptscript/pkg/types" "github.com/rs/cors" ) @@ -46,14 +48,24 @@ func Start(ctx context.Context, opts Options) error { opts.Options.Runner.MonitorFactory = NewSessionFactory(events) go events.Start(ctx) + token := uuid.NewString() + // Add the prompt token env var so that gptscript doesn't start its own server. We never want this client to start the + // prompt server because it is only used for fmt, parse, etc. + opts.Env = append(opts.Env, fmt.Sprintf("%s=%s", types.PromptTokenEnvVar, token)) + g, err := gptscript.New(&opts.Options) if err != nil { return err } + listener, err := net.Listen("tcp", opts.ListenAddress) + if err != nil { + return fmt.Errorf("failed to listen on %s: %w", opts.ListenAddress, err) + } + s := &server{ - address: opts.ListenAddress, - token: uuid.NewString(), + address: listener.Addr().String(), + token: token, client: g, events: events, waitingToConfirm: make(map[string]chan runner.AuthorizerResponse), @@ -64,7 +76,6 @@ func Start(ctx context.Context, opts Options) error { s.addRoutes(http.DefaultServeMux) server := http.Server{ - Addr: opts.ListenAddress, Handler: apply(http.DefaultServeMux, contentType("application/json"), addRequestID, @@ -74,7 +85,7 @@ func Start(ctx context.Context, opts Options) error { ), } - slog.Info("Starting server", "addr", server.Addr) + slog.Info("Starting server", "addr", s.address) context.AfterFunc(sigCtx, func() { ctx, cancel := context.WithTimeout(ctx, 15*time.Second) @@ -85,7 +96,7 @@ func Start(ctx context.Context, opts Options) error { slog.Info("Server stopped") }) - if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { return fmt.Errorf("server error: %w", err) } diff --git a/pkg/server/server.go b/pkg/server/server.go index 9f687783..b064b4db 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "net" "net/http" "os" "path/filepath" @@ -212,10 +213,15 @@ func (s *Server) getContext(req *http.Request) (string, context.Context) { } func (s *Server) Start(ctx context.Context) error { + listener, err := net.Listen("tcp", s.listenAddress) + if err != nil { + return fmt.Errorf("could not listen on %s: %w", s.listenAddress, err) + } + s.ctx = ctx s.melody.HandleConnect(s.Connect) go s.events.Start(ctx) - log.Infof("Listening on http://%s", s.listenAddress) + log.Infof("Listening on http://%s", listener.Addr().String()) handler := cors.Default().Handler(s) server := &http.Server{Addr: s.listenAddress, Handler: handler} context.AfterFunc(ctx, func() { @@ -224,7 +230,7 @@ func (s *Server) Start(ctx context.Context) error { _ = server.Shutdown(ctx) }) - return server.ListenAndServe() + return server.Serve(listener) } func (s *Server) Connect(session *melody.Session) {