diff --git a/middleware/websockets/websocket.go b/middleware/websockets/websocket.go index 085399f76..e830ce2b2 100644 --- a/middleware/websockets/websocket.go +++ b/middleware/websockets/websocket.go @@ -10,7 +10,7 @@ import ( ) // WebSocket represents a web socket server instance. A WebSocket -// struct is instantiated for each new websocket request. +// is instantiated for each new websocket request/connection. type WebSocket struct { WSConfig *http.Request @@ -21,33 +21,40 @@ type WebSocket struct { // the command's stdin and stdout. func (ws WebSocket) Handle(conn *websocket.Conn) { cmd := exec.Command(ws.Command, ws.Arguments...) + cmd.Stdin = conn cmd.Stdout = conn + cmd.Stderr = conn // TODO: Make this configurable from the Caddyfile - err := ws.buildEnv(cmd) + metavars, err := ws.buildEnv(cmd.Path) if err != nil { - // TODO + panic(err) // TODO } + cmd.Env = metavars + err = cmd.Run() if err != nil { panic(err) } } -// buildEnv sets the meta-variables for the child process according +// buildEnv creates the meta-variables for the child process according // to the CGI 1.1 specification: http://tools.ietf.org/html/rfc3875#section-4.1 -func (ws WebSocket) buildEnv(cmd *exec.Cmd) error { +// cmdPath should be the path of the command being run. +// The returned string slice can be set to the command's Env property. +func (ws WebSocket) buildEnv(cmdPath string) (metavars []string, err error) { remoteHost, remotePort, err := net.SplitHostPort(ws.RemoteAddr) if err != nil { - return err - } - serverHost, serverPort, err := net.SplitHostPort(ws.Host) - if err != nil { - return err + return } - cmd.Env = []string{ + serverHost, serverPort, err := net.SplitHostPort(ws.Host) + if err != nil { + return + } + + metavars = []string{ `AUTH_TYPE=`, // Not used `CONTENT_LENGTH=`, // Not used `CONTENT_TYPE=`, // Not used @@ -62,7 +69,7 @@ func (ws WebSocket) buildEnv(cmd *exec.Cmd) error { `REMOTE_USER=`, // Not used, `REQUEST_METHOD=` + ws.Method, `REQUEST_URI=` + ws.RequestURI, - `SCRIPT_NAME=`, // TODO - absolute path to program being executed? + `SCRIPT_NAME=` + cmdPath, // path of the program being executed `SERVER_NAME=` + serverHost, `SERVER_PORT=` + serverPort, `SERVER_PROTOCOL=` + ws.Proto, @@ -75,8 +82,8 @@ func (ws WebSocket) buildEnv(cmd *exec.Cmd) error { header = strings.ToUpper(header) header = strings.Replace(header, "-", "_", -1) value = strings.Replace(value, "\n", " ", -1) - cmd.Env = append(cmd.Env, "HTTP_"+header+"="+value) + metavars = append(metavars, "HTTP_"+header+"="+value) } - return nil + return } diff --git a/middleware/websockets/websockets.go b/middleware/websockets/websockets.go index fe71861ea..8141f7d93 100644 --- a/middleware/websockets/websockets.go +++ b/middleware/websockets/websockets.go @@ -17,16 +17,20 @@ type ( // websocket middleware generally, like a list of all the // websocket endpoints. WebSockets struct { + // Next is the next HTTP handler in the chain for when the path doesn't match + Next http.HandlerFunc + // Sockets holds all the web socket endpoint configurations Sockets []WSConfig } // WSConfig holds the configuration for a single websocket - // endpoint which may serve zero or more websocket connections. + // endpoint which may serve multiple websocket connections. WSConfig struct { Path string Command string Arguments []string + Respawn bool // TODO: Not used, but parser supports it until we decide on it } ) @@ -42,11 +46,27 @@ func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } + + // Didn't match a websocket path, so pass-thru + ws.Next(w, r) } // New constructs and configures a new websockets middleware instance. func New(c middleware.Controller) (middleware.Middleware, error) { var websocks []WSConfig + var respawn bool + + optionalBlock := func() (hadBlock bool, err error) { + for c.NextBlock() { + hadBlock = true + if c.Val() == "respawn" { + respawn = true + } else { + return true, c.Err("Expected websocket configuration parameter in block") + } + } + return + } for c.Next() { var val, path, command string @@ -57,38 +77,40 @@ func New(c middleware.Controller) (middleware.Middleware, error) { } val = c.Val() - // The rest of the arguments are the command - if c.NextArg() { - path = val - command = c.Val() - for c.NextArg() { - command += " " + c.Val() + // Extra configuration may be in a block + hadBlock, err := optionalBlock() + if err != nil { + return nil, err + } + + if !hadBlock { + // The next argument on this line will be the command or an open curly brace + if c.NextArg() { + path = val + command = c.Val() + } else { + path = "/" + command = val + } + + // Okay, check again for optional block + hadBlock, err = optionalBlock() + if err != nil { + return nil, err } - } else { - path = "/" - command = val } // Split command into the actual command and its arguments - var cmd string - var args []string - - parts, err := shlex.Split(command) + cmd, args, err := parseCommandAndArgs(command) if err != nil { - return nil, errors.New("Error parsing command for websocket use: " + err.Error()) - } else if len(parts) == 0 { - return nil, errors.New("No command found for use by websocket") - } - - cmd = parts[0] - if len(parts) > 1 { - args = parts[1:] + return nil, err } websocks = append(websocks, WSConfig{ Path: path, Command: cmd, Arguments: args, + Respawn: respawn, }) } @@ -96,12 +118,30 @@ func New(c middleware.Controller) (middleware.Middleware, error) { ServerSoftware = envServerSoftware return func(next http.HandlerFunc) http.HandlerFunc { - // We don't use next because websockets aren't HTTP, - // so we don't invoke other middleware after this. - return WebSockets{Sockets: websocks}.ServeHTTP + return WebSockets{Next: next, Sockets: websocks}.ServeHTTP }, 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 ( // See CGI spec, 4.1.4 GatewayInterface string @@ -112,5 +152,5 @@ var ( const ( envGatewayInterface = "caddy-CGI/1.1" - envServerSoftware = "caddy/0.1.0" + envServerSoftware = "caddy/?.?.?" // TODO )