From d16189c610148d8cfdf225f56dfadb83a561bc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 12 Nov 2022 15:43:32 +0100 Subject: [PATCH 1/2] feat: embed PHP thanks to FrankenPHP --- commands/local_server_start.go | 89 +++++++++++++++++++--------------- go.mod | 9 ++++ go.sum | 18 ++++++- local/php/php_server.go | 62 +++++++++++++++++++---- local/project/config.go | 5 +- local/project/project.go | 2 +- 6 files changed, 134 insertions(+), 51 deletions(-) diff --git a/commands/local_server_start.go b/commands/local_server_start.go index 9b2e0fde..2ad7ca94 100644 --- a/commands/local_server_start.go +++ b/commands/local_server_start.go @@ -31,6 +31,7 @@ import ( "path/filepath" "syscall" + "github.com/dunglas/frankenphp" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/soheilhy/cmux" @@ -68,6 +69,7 @@ var localServerStartCmd = &console.Command{ &console.StringFlag{Name: "p12", Usage: "Name of the file containing the TLS certificate to use in p12 format"}, &console.BoolFlag{Name: "no-tls", Usage: "Use HTTP instead of HTTPS"}, &console.BoolFlag{Name: "use-gzip", Usage: "Use GZIP"}, + &console.BoolFlag{Name: "frankenphp", Usage: "Use FrankenPHP (built-in) instead of PHP FPM"}, }, Action: func(c *console.Context) error { ui := terminal.SymfonyStyle(terminal.Stdout, terminal.Stdin) @@ -201,52 +203,58 @@ var localServerStartCmd = &console.Command{ return err } - // We retrieve a reader on logs as soon as possible to be able to - // display error logs in case of startup errors. We can't do it - // later as the log file will already be deleted. - logs, err := phpPidFile.LogReader() - if err != nil { - return err - } + if phpPidFile == nil { + if err := phpStartCallback(); err != nil { + return err + } + } else { + // We retrieve a reader on logs as soon as possible to be able to + // display error logs in case of startup errors. We can't do it + // later as the log file will already be deleted. + logs, err := phpPidFile.LogReader() + if err != nil { + return err + } - if !reexec.IsChild() { - tailer.WatchAdditionalPidFile(phpPidFile) - } + if !reexec.IsChild() { + tailer.WatchAdditionalPidFile(phpPidFile) + } - // we run FPM in its own goroutine to allow it to run even when - // foreground is forced - go func() { errChan <- phpStartCallback() }() + // we run FPM in its own goroutine to allow it to run even when + // foreground is forced + go func() { errChan <- phpStartCallback() }() - // Give time to PHP to fail or to be ready - select { - case err := <-errChan: - terminal.Logger.Error().Msgf("Unable to start %s", phpPidFile.CustomName) + // Give time to PHP to fail or to be ready + select { + case err := <-errChan: + terminal.Logger.Error().Msgf("Unable to start %s", phpPidFile.CustomName) - humanizer := humanlog.NewHandler(&humanlog.Options{ - SkipUnchanged: true, - WithSource: true, - }) + humanizer := humanlog.NewHandler(&humanlog.Options{ + SkipUnchanged: true, + WithSource: true, + }) - buf := bytes.Buffer{} - fmt.Fprintf(&buf, "%s failed to start:\n", phpPidFile.CustomName) + buf := bytes.Buffer{} + fmt.Fprintf(&buf, "%s failed to start:\n", phpPidFile.CustomName) - scanner := bufio.NewScanner(logs) - for scanner.Scan() { - buf.Write(humanizer.Simplify(scanner.Bytes())) - buf.WriteRune('\n') - } + scanner := bufio.NewScanner(logs) + for scanner.Scan() { + buf.Write(humanizer.Simplify(scanner.Bytes())) + buf.WriteRune('\n') + } - ui.Error(buf.String()) + ui.Error(buf.String()) - if err != nil { - return err - } - return nil - case err := <-phpPidFile.WaitForPid(): - // PHP started, we can close logs and go ahead - logs.Close() - if err != nil { - return err + if err != nil { + return err + } + return nil + case err := <-phpPidFile.WaitForPid(): + // PHP started, we can close logs and go ahead + logs.Close() + if err != nil { + return err + } } } } @@ -343,6 +351,11 @@ var localServerStartCmd = &console.Command{ case <-shutdownCh: terminal.Eprintln("") terminal.Eprintln("Shutting down!") + + if config.FrankenPHP { + frankenphp.Shutdown() + } + if err := cleanupWebServerFiles(projectDir, pidFile); err != nil { return err } diff --git a/go.mod b/go.mod index 9e36f098..a5fc1d7a 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,11 @@ module github.com/symfony-cli/symfony-cli +replace github.com/symfony-cli/phpstore => github.com/dunglas/phpstore v1.0.6-0.20221112140329-97f1078dbfc1 + require ( github.com/compose-spec/compose-go v1.6.0 github.com/docker/docker v20.10.21+incompatible + github.com/dunglas/frankenphp v0.0.0-20221112134810-6a6dda5ed924 github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 github.com/fabpot/local-php-security-checker/v2 v2.0.5 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 @@ -24,11 +27,17 @@ require ( github.com/symfony-cli/phpstore v1.0.5 github.com/symfony-cli/terminal v1.0.4 github.com/syncthing/notify v0.0.0-20210616190510-c6b7342338d2 + go.uber.org/zap v1.23.0 golang.org/x/sync v0.1.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 ) +require ( + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.8.0 // indirect +) + require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/NYTimes/gziphandler v1.1.1 diff --git a/go.sum b/go.sum index 89a7de8a..c22df12f 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,7 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -70,6 +71,12 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dunglas/frankenphp v0.0.0-20221110130350-f0b2eb74451c h1:MV2p5aj2WsUVKtPU5iWyQCqrTlPnc7h/Xcpd5Fp//Mo= +github.com/dunglas/frankenphp v0.0.0-20221110130350-f0b2eb74451c/go.mod h1:6b1QU694yYzvWx460qOVNYJGOwOLZtErhiUNLiVHpSI= +github.com/dunglas/frankenphp v0.0.0-20221112134810-6a6dda5ed924 h1:xE23E2Ch9txx7bSas/xeLa1j+GEfSSUXbwZ72LCjjnY= +github.com/dunglas/frankenphp v0.0.0-20221112134810-6a6dda5ed924/go.mod h1:6b1QU694yYzvWx460qOVNYJGOwOLZtErhiUNLiVHpSI= +github.com/dunglas/phpstore v1.0.6-0.20221112140329-97f1078dbfc1 h1:y124yxl6y4skiXW87SIisI76sWcsx0QKpyPvZwBEvLU= +github.com/dunglas/phpstore v1.0.6-0.20221112140329-97f1078dbfc1/go.mod h1:Pug4pGst4b5DcGUwYz2DB1LjltohPgvE4OusDe1z2Xg= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= @@ -269,8 +276,6 @@ github.com/symfony-cli/cert v1.0.1 h1:ETYVBshgY+SaydBJmMkU0PaoDrWm6zsGNB/799ZQYC github.com/symfony-cli/cert v1.0.1/go.mod h1:g4WrLT6EQsEPmA19xh5Jv9Jnpg5EvtFqArJf2AG2S+w= github.com/symfony-cli/console v1.0.2 h1:u8KJm9jFbfzmN0y7fcfjjal3wWOLflNomu29PLgYQjQ= github.com/symfony-cli/console v1.0.2/go.mod h1:z2dLSNdPW3rWdSxj8DlZocMtMYN5EF6OeIYjVioXVqE= -github.com/symfony-cli/phpstore v1.0.5 h1:e1J+FcztiSSAVuD4gwatPwMpeqQy3SGNdhd6Vtuncy8= -github.com/symfony-cli/phpstore v1.0.5/go.mod h1:Pug4pGst4b5DcGUwYz2DB1LjltohPgvE4OusDe1z2Xg= github.com/symfony-cli/terminal v1.0.4 h1:jam7aN7g7WQ9uOwV9UC+iVGBLTlzK8kAC5EKcxq21Z8= github.com/symfony-cli/terminal v1.0.4/go.mod h1:+OxxnU05wyRHKYyQkTzTaCSSxmmIBcvAHLcQ099odj4= github.com/syncthing/notify v0.0.0-20210616190510-c6b7342338d2 h1:F4snRP//nIuTTW9LYEzVH4HVwDG9T3M4t8y/2nqMbiY= @@ -292,6 +297,14 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -614,6 +627,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/local/php/php_server.go b/local/php/php_server.go index 676e88a0..65ad1a61 100644 --- a/local/php/php_server.go +++ b/local/php/php_server.go @@ -25,6 +25,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "net" "net/http" "net/http/httptest" @@ -37,6 +38,7 @@ import ( "strings" "time" + "github.com/dunglas/frankenphp" "github.com/pkg/errors" "github.com/rs/xid" "github.com/rs/zerolog" @@ -46,6 +48,7 @@ import ( "github.com/symfony-cli/symfony-cli/local/html" "github.com/symfony-cli/symfony-cli/local/pid" "github.com/symfony-cli/symfony-cli/local/process" + "go.uber.org/zap" ) // Server represents a PHP server process (can be php-fpm, php-cgi, or php-cli) @@ -63,16 +66,30 @@ type Server struct { var addslashes = strings.NewReplacer("\\", "\\\\", "'", "\\'") // NewServer creates a new PHP server backend -func NewServer(homeDir, projectDir, documentRoot, passthru string, logger zerolog.Logger) (*Server, error) { - logger.Debug().Str("source", "PHP").Msg("Reloading PHP versions") - phpStore := phpstore.New(homeDir, true, nil) - version, source, warning, err := phpStore.BestVersionForDir(projectDir) - if warning != "" { - logger.Warn().Str("source", "PHP").Msg(warning) - } - if err != nil { - return nil, err +func NewServer(homeDir, projectDir, documentRoot, passthru string, useFrankenPHP bool, logger zerolog.Logger) (*Server, error) { + var ( + version *phpstore.Version + source string + ) + + if useFrankenPHP { + version = &phpstore.Version{Version: frankenphp.Version().Version, FrankenPHP: true} + source = "FrankenPHP" + } else { + logger.Debug().Str("source", "PHP").Msg("Reloading PHP versions") + phpStore := phpstore.New(homeDir, true, nil) + v, s, warning, err := phpStore.BestVersionForDir(projectDir) + if warning != "" { + logger.Warn().Str("source", "PHP").Msg(warning) + } + if err != nil { + return nil, err + } + + version = v + source = s } + logger.Debug().Str("source", "PHP").Msgf("Using PHP version %s (from %s)", version.Version, source) return &Server{ Version: version, @@ -86,6 +103,25 @@ func NewServer(homeDir, projectDir, documentRoot, passthru string, logger zerolo // Start starts a PHP server func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile, func() error, error) { + if p.Version.IsFrankenPHPServer() { + return nil, func() error { + log.Print("calllll") + + // TODO: create an adapter between zerolog and zap + z, err := zap.NewProduction() + if err != nil { + return errors.Wrap(err, "unable to create FrankenPHP's logger") + } + if err = frankenphp.Init(frankenphp.WithLogger(z)); err != nil { + return errors.Wrap(err, "unable to start FrankenPHP's logger") + } + + log.Print("started") + + return nil + }, nil + } + var pathsToRemove []string port, err := process.FindAvailablePort() if err != nil { @@ -197,6 +233,14 @@ func (p *Server) Serve(w http.ResponseWriter, r *http.Request, env map[string]st for k, v := range p.generateEnv(r) { env[k] = v } + if p.Version.IsFrankenPHPServer() { + fr := frankenphp.NewRequestWithContext(r, p.documentRoot, nil) + fc, _ := frankenphp.FromContext(fr.Context()) + fc.Env = env + fc.Env["SCRIPT_FILENAME"] = p.documentRoot + string(os.PathSeparator) + p.passthru + + return frankenphp.ServeHTTP(w, fr) + } if p.Version.IsCLIServer() { rid := xid.New().String() r.Header.Add("__SYMFONY_LOCAL_REQUEST_ID__", rid) diff --git a/local/project/config.go b/local/project/config.go index 175e1451..a175c4fd 100644 --- a/local/project/config.go +++ b/local/project/config.go @@ -45,6 +45,7 @@ type Config struct { NoTLS bool `yaml:"no_tls"` Daemon bool `yaml:"daemon"` UseGzip bool `yaml:"use_gzip"` + FrankenPHP bool `yaml:"frankenphp"` } type FileConfig struct { @@ -104,10 +105,12 @@ func NewConfigFromContext(c *console.Context, projectDir string) (*Config, *File if c.IsSet("daemon") { config.Daemon = c.Bool("daemon") } - if c.IsSet("use-gzip") { config.UseGzip = c.Bool("use-gzip") } + if c.IsSet("frankenphp") { + config.FrankenPHP = c.Bool("frankenphp") + } return config, fileConfig, nil } diff --git a/local/project/project.go b/local/project/project.go index 6fcb0c88..c4b46053 100644 --- a/local/project/project.go +++ b/local/project/project.go @@ -76,7 +76,7 @@ func New(c *Config) (*Project, error) { return nil } } else { - p.PHPServer, err = php.NewServer(c.HomeDir, c.ProjectDir, documentRoot, passthru, c.Logger) + p.PHPServer, err = php.NewServer(c.HomeDir, c.ProjectDir, documentRoot, passthru, c.FrankenPHP, c.Logger) if err != nil { return nil, err } From f0b73763703177bdde59af33b67ffbbf715010aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 13 Nov 2022 11:35:08 +0100 Subject: [PATCH 2/2] cleanup --- local/php/php_server.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/local/php/php_server.go b/local/php/php_server.go index 65ad1a61..1342f09a 100644 --- a/local/php/php_server.go +++ b/local/php/php_server.go @@ -25,7 +25,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net" "net/http" "net/http/httptest" @@ -105,8 +104,6 @@ func NewServer(homeDir, projectDir, documentRoot, passthru string, useFrankenPHP func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile, func() error, error) { if p.Version.IsFrankenPHPServer() { return nil, func() error { - log.Print("calllll") - // TODO: create an adapter between zerolog and zap z, err := zap.NewProduction() if err != nil { @@ -116,8 +113,6 @@ func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile, return errors.Wrap(err, "unable to start FrankenPHP's logger") } - log.Print("started") - return nil }, nil }