Skip to content

Instantly share code, notes, and snippets.

Created July 20, 2018 16:32
Show Gist options
  • Save ijt/dfb2e87087089380f4164ab27991af7c to your computer and use it in GitHub Desktop.
Save ijt/dfb2e87087089380f4164ab27991af7c to your computer and use it in GitHub Desktop.
Repro case for gofast issue 28
// This program spins up php-fpm in the background and a Go web server sending
// all requests to a PHP router script.
package main
import (
func main() {
phpFpm := flag.String("php-fpm", "php-fpm", "name or path of PHP fastCGI process manager")
port := flag.Int("port", 8080, "port which to listen")
logAccess := flag.Bool("emit-access-logs", false, "whether to turn on access logs")
router := "index.php"
numWorkers := 1
ctx, cancel := context.WithCancel(context.Background())
status := 0
log.Printf("Listening on port %d", *port)
if err := serve(ctx, numWorkers, router, *port, *phpFpm, *logAccess); err != nil {
fmt.Fprintf(os.Stderr, "serve failed: %v\n", err)
status = 1
// serve configures php-fpm to have the given number of workers and a web
// server to talk to php-fpm over a socket, and spawns php-fpm along with the
// web server.
func serve(ctx context.Context, numWorkers int, router string, port int, phpFpm string, logAccess bool) error {
g, ctx := errgroup.WithContext(ctx)
if _, err := os.Stat(router); err != nil {
return fmt.Errorf("router %s not found", router)
// Make a temp directory to put config files in.
confDir, err := ioutil.TempDir("/tmp", "serve-")
if err != nil {
return fmt.Errorf("creating temp dir: %v", err)
// Start php-fpm.
fifoPath := filepath.Join(confDir, "php-fpm.sock")
cmd, err := startPhpFpm(ctx, phpFpm, numWorkers, confDir, fifoPath)
if err != nil {
return fmt.Errorf("starting php-fpm: %v", err)
g.Go(func() error { return cmd.Wait() })
// Start the web server, forwarding requests to php-fpm via fastCGI.
cf := gofast.SimpleConnFactory("unix", fifoPath)
var h http.Handler = gofast.NewHandler(
gofast.SimpleClientFactory(cf, 0),
if logAccess {
h = handlers.LoggingHandler(os.Stdout, h)
s := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: handlers.ProxyHeaders(h),
g.Go(func() error { return s.ListenAndServe() })
defer s.Shutdown(ctx)
return g.Wait()
// startPhpFpm starts php-fpm listening to the unix socket at fifoPath.
func startPhpFpm(ctx context.Context, phpFpm string, numWorkers int, confDir string, fifoPath string) (*exec.Cmd, error) {
pidPath := filepath.Join(confDir, "")
phpFpmConfPath := filepath.Join(confDir, "php-fpm.conf")
phpFpmConf, err := os.Create(phpFpmConfPath)
if err != nil {
return nil, fmt.Errorf("creating php-fpm config file: %v", err)
args := map[string]string{
"fifo": fifoPath,
"workers": fmt.Sprintf("%d", numWorkers),
"pidpath": pidPath,
if err := phpFpmTemplate.Execute(phpFpmConf, args); err != nil {
return nil, fmt.Errorf("writing php-fpm config file: %v", err)
if err := phpFpmConf.Close(); err != nil {
return nil, fmt.Errorf("closing php-fpm config file: %v", err)
cmd := exec.CommandContext(ctx, phpFpm, "-R", "--nodaemonize", "--fpm-config", phpFpmConfPath)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// Start php-fpm in the background.
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return nil, err
return cmd, nil
var phpFpmTemplate = template.Must(template.New("phpfpm").Parse(`
; Send errors to stderr.
error_log = /proc/self/fd/2
log_level = warning
pid = {{.pidpath}}
; Pool configuration
; The address on which to accept FastCGI requests
listen = {{.fifo}}
; Create child processes with a static policy.
pm = static
; The number of child processes to be created
pm.max_children = {{.workers}}
; Keep the environment variables of the parent.
clear_env = no
catch_workers_output = yes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment