diff --git a/middleware/websockets/websocket.go b/middleware/websockets/websocket.go index 201fa40ce..78ac54110 100644 --- a/middleware/websockets/websocket.go +++ b/middleware/websockets/websocket.go @@ -1,16 +1,19 @@ package websockets import ( + "net" + "net/http" "os/exec" + "strings" "golang.org/x/net/websocket" ) -// WebSocket represents a web socket server configuration. +// WebSocket represents a web socket server instance. A WebSocket +// struct is instantiated for each new websocket request. type WebSocket struct { - Path string - Command string - Arguments []string + WSConfig + *http.Request } // Handle handles a WebSocket connection. It launches the @@ -21,12 +24,67 @@ func (ws WebSocket) Handle(conn *websocket.Conn) { cmd.Stdin = conn cmd.Stdout = conn - // TODO: Set environment variables according to CGI 1.1 - // cf. http://tools.ietf.org/html/rfc3875#section-4.1.4 - cmd.Env = append(cmd.Env, `GATEWAY_INTERFACE="caddy-CGI/1.1"`) + err := ws.buildEnv(cmd) + if err != nil { + // TODO + } - err := cmd.Run() + err = cmd.Run() if err != nil { panic(err) } } + +// buildEnv sets 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 { + remoteHost, remotePort, err := net.SplitHostPort(ws.RemoteAddr) + if err != nil { + return err + } + serverHost, serverPort, err := net.SplitHostPort(ws.Host) + if err != nil { + return err + } + + cmd.Env = []string{ + `AUTH_TYPE=`, // Not used + `CONTENT_LENGTH=`, // Not used + `CONTENT_TYPE=`, // Not used + `GATEWAY_INTERFACE=` + gatewayInterface, + `PATH_INFO=`, // TODO + `PATH_TRANSLATED=`, // TODO + `QUERY_STRING=` + ws.URL.RawQuery, + `REMOTE_ADDR=` + remoteHost, + `REMOTE_HOST=` + remoteHost, // TODO (Host lookups are slow; make this configurable) + `REMOTE_IDENT=`, // Not used + `REMOTE_PORT=` + remotePort, + `REMOTE_USER=`, // Not used, + `REQUEST_METHOD=` + ws.Method, + `REQUEST_URI=` + ws.RequestURI, + `SCRIPT_NAME=`, // TODO - absolute path to program being executed? + `SERVER_NAME=` + serverHost, + `SERVER_PORT=` + serverPort, + `SERVER_PROTOCOL=` + ws.Proto, + `SERVER_SOFTWARE=` + serverSoftware, + } + + // Add each HTTP header to the environment as well + for header, values := range ws.Header { + value := strings.Join(values, ", ") + header = strings.ToUpper(header) + header = strings.Replace(header, "-", "_", -1) + value = strings.Replace(value, "\n", " ", -1) + cmd.Env = append(cmd.Env, "HTTP_"+header+"="+value) + } + + return nil +} + +const ( + // See CGI spec, 4.1.4 + gatewayInterface = "caddy-CGI/1.1" + + // See CGI spec, 4.1.17 + serverSoftware = "caddy/0.1.0" +) diff --git a/middleware/websockets/websockets.go b/middleware/websockets/websockets.go index eab35b389..880dd8346 100644 --- a/middleware/websockets/websockets.go +++ b/middleware/websockets/websockets.go @@ -4,7 +4,7 @@ package websockets import ( - "log" + "errors" "net/http" "github.com/flynn/go-shlex" @@ -12,16 +12,31 @@ import ( "golang.org/x/net/websocket" ) -// WebSockets is a type which holds configuration -// for the websocket middleware collectively. -type WebSockets struct { - Sockets []WebSocket -} +type ( + // WebSockets is a type that holds configuration for the + // websocket middleware generally, like a list of all the + // websocket endpoints. + WebSockets struct { + Sockets []WSConfig + } -// ServeHTTP more or less converts the HTTP request to a WebSocket connection. + // WSConfig holds the configuration for a single websocket + // endpoint which may serve zero or more websocket connections. + WSConfig struct { + Path string + Command string + Arguments []string + } +) + +// ServeHTTP converts the HTTP request to a WebSocket connection and serves it up. func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) { - for _, socket := range ws.Sockets { - if middleware.Path(r.URL.Path).Matches(socket.Path) { + for _, sockconfig := range ws.Sockets { + if middleware.Path(r.URL.Path).Matches(sockconfig.Path) { + socket := WebSocket{ + WSConfig: sockconfig, + Request: r, + } websocket.Handler(socket.Handle).ServeHTTP(w, r) return } @@ -30,7 +45,7 @@ func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) { // New constructs and configures a new websockets middleware instance. func New(c middleware.Controller) (middleware.Middleware, error) { - var websocks []WebSocket + var websocks []WSConfig var path string var command string @@ -62,9 +77,9 @@ func New(c middleware.Controller) (middleware.Middleware, error) { parts, err := shlex.Split(command) if err != nil { - log.Fatal("Error parsing command for websocket use: " + err.Error()) + return nil, errors.New("Error parsing command for websocket use: " + err.Error()) } else if len(parts) == 0 { - log.Fatal("No command found for use by websocket.") + return nil, errors.New("No command found for use by websocket") } cmd = parts[0] @@ -72,7 +87,7 @@ func New(c middleware.Controller) (middleware.Middleware, error) { args = parts[1:] } - websocks = append(websocks, WebSocket{ + websocks = append(websocks, WSConfig{ Path: path, Command: cmd, Arguments: args,