Commit 2fbfafc4 authored by Matthew Holt's avatar Matthew Holt

New startup and shutdown directives

parent b5dc1dde
...@@ -14,6 +14,51 @@ const ( ...@@ -14,6 +14,51 @@ const (
defaultRoot = "." defaultRoot = "."
) )
// config represents a server configuration. It
// is populated by parsing a config file (via the
// Load function).
type Config struct {
// The hostname or IP to which to bind the server
Host string
// The port to listen on
Port string
// The directory from which to serve files
Root string
// HTTPS configuration
TLS TLSConfig
// Middleware stack
Middleware map[string][]middleware.Middleware
// Functions (or methods) to execute at server start; these
// are executed before any parts of the server are configured,
// and the functions are blocking
Startup []func() error
// Functions (or methods) to execute when the server quits;
// these are executed in response to SIGINT and are blocking
Shutdown []func() error
// MaxCPU is the maximum number of cores for the whole process to use
MaxCPU int
}
// Address returns the host:port of c as a string.
func (c Config) Address() string {
return c.Host + ":" + c.Port
}
// TLSConfig describes how TLS should be configured and used,
// if at all. A certificate and key are both required.
type TLSConfig struct {
Enabled bool
Certificate string
Key string
}
// Load loads a configuration file, parses it, // Load loads a configuration file, parses it,
// and returns a slice of Config structs which // and returns a slice of Config structs which
// can be used to create and configure server // can be used to create and configure server
...@@ -54,43 +99,3 @@ func Default() []Config { ...@@ -54,43 +99,3 @@ func Default() []Config {
} }
return cfg return cfg
} }
// config represents a server configuration. It
// is populated by parsing a config file (via the
// Load function).
type Config struct {
// The hostname or IP to which to bind the server
Host string
// The port to listen on
Port string
// The directory from which to serve files
Root string
// HTTPS configuration
TLS TLSConfig
// Middleware stack
Middleware map[string][]middleware.Middleware
// Functions (or methods) to execute at server start; these
// are executed before any parts of the server are configured
Startup []func() error
// MaxCPU is the maximum number of cores for the whole process to use
MaxCPU int
}
// Address returns the host:port of c as a string.
func (c Config) Address() string {
return c.Host + ":" + c.Port
}
// TLSConfig describes how TLS should be configured and used,
// if at all. At least a certificate and key are required.
type TLSConfig struct {
Enabled bool
Certificate string
Key string
}
...@@ -2,9 +2,12 @@ package config ...@@ -2,9 +2,12 @@ package config
import ( import (
"os" "os"
"os/exec"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"github.com/mholt/caddy/middleware"
) )
// dirFunc is a type of parsing function which processes // dirFunc is a type of parsing function which processes
...@@ -111,5 +114,55 @@ func init() { ...@@ -111,5 +114,55 @@ func init() {
} }
return nil return nil
}, },
"startup": func(p *parser) error {
// TODO: This code is duplicated with the shutdown directive below
if !p.nextArg() {
return p.argErr()
}
command, args, err := middleware.SplitCommandAndArgs(p.tkn())
if err != nil {
return p.err("Parse", err.Error())
}
startupfn := func() error {
cmd := exec.Command(command, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
return nil
}
p.cfg.Startup = append(p.cfg.Startup, startupfn)
return nil
},
"shutdown": func(p *parser) error {
if !p.nextArg() {
return p.argErr()
}
command, args, err := middleware.SplitCommandAndArgs(p.tkn())
if err != nil {
return p.err("Parse", err.Error())
}
shutdownfn := func() error {
cmd := exec.Command(command, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
return nil
}
p.cfg.Shutdown = append(p.cfg.Shutdown, shutdownfn)
return nil
},
} }
} }
package middleware
import (
"errors"
"github.com/flynn/go-shlex"
)
// SplitCommandAndArgs takes a command string and parses it
// shell-style into the command and its separate arguments.
func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
parts, err := shlex.Split(command)
if err != nil {
err = errors.New("Error parsing command: " + err.Error())
return
} else if len(parts) == 0 {
err = errors.New("No command contained in '" + command + "'")
return
}
cmd = parts[0]
if len(parts) > 1 {
args = parts[1:]
}
return
}
...@@ -4,10 +4,8 @@ ...@@ -4,10 +4,8 @@
package websockets package websockets
import ( import (
"errors"
"net/http" "net/http"
"github.com/flynn/go-shlex"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
...@@ -101,7 +99,7 @@ func New(c middleware.Controller) (middleware.Middleware, error) { ...@@ -101,7 +99,7 @@ func New(c middleware.Controller) (middleware.Middleware, error) {
} }
// Split command into the actual command and its arguments // Split command into the actual command and its arguments
cmd, args, err := parseCommandAndArgs(command) cmd, args, err := middleware.SplitCommandAndArgs(command)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -122,26 +120,6 @@ func New(c middleware.Controller) (middleware.Middleware, error) { ...@@ -122,26 +120,6 @@ func New(c middleware.Controller) (middleware.Middleware, error) {
}, nil }, nil
} }
// parseCommandAndArgs takes a command string and parses it
// shell-style into the command and its separate arguments.
func parseCommandAndArgs(command string) (cmd string, args []string, err error) {
parts, err := shlex.Split(command)
if err != nil {
err = errors.New("Error parsing command for websocket: " + err.Error())
return
} else if len(parts) == 0 {
err = errors.New("No command found for use by websocket")
return
}
cmd = parts[0]
if len(parts) > 1 {
args = parts[1:]
}
return
}
var ( var (
// See CGI spec, 4.1.4 // See CGI spec, 4.1.4
GatewayInterface string GatewayInterface string
......
...@@ -7,6 +7,8 @@ import ( ...@@ -7,6 +7,8 @@ import (
"errors" "errors"
"log" "log"
"net/http" "net/http"
"os"
"os/signal"
"runtime" "runtime"
"github.com/bradfitz/http2" "github.com/bradfitz/http2"
...@@ -75,6 +77,21 @@ func (s *Server) Serve() error { ...@@ -75,6 +77,21 @@ func (s *Server) Serve() error {
http2.ConfigureServer(server, nil) // TODO: This may not be necessary after HTTP/2 merged into std lib http2.ConfigureServer(server, nil) // TODO: This may not be necessary after HTTP/2 merged into std lib
// Execute shutdown commands on exit
// TODO: Is graceful net/http shutdown necessary?
go func() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
<-interrupt
for _, shutdownFunc := range s.config.Shutdown {
err := shutdownFunc()
if err != nil {
log.Fatal(err)
}
}
os.Exit(0)
}()
if s.config.TLS.Enabled { if s.config.TLS.Enabled {
return server.ListenAndServeTLS(s.config.TLS.Certificate, s.config.TLS.Key) return server.ListenAndServeTLS(s.config.TLS.Certificate, s.config.TLS.Key)
} else { } else {
...@@ -103,6 +120,7 @@ func (s *Server) Log(v ...interface{}) { ...@@ -103,6 +120,7 @@ func (s *Server) Log(v ...interface{}) {
func (s *Server) buildStack() error { func (s *Server) buildStack() error {
s.fileServer = FileServer(http.Dir(s.config.Root)) s.fileServer = FileServer(http.Dir(s.config.Root))
// Execute startup functions
for _, start := range s.config.Startup { for _, start := range s.config.Startup {
err := start() err := start()
if err != nil { if err != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment