Commit 37f0a37e authored by Matthew Holt's avatar Matthew Holt

Filled out more web socket stuff

parent 411f3256
package websockets package websockets
import ( import (
"net"
"net/http"
"os/exec" "os/exec"
"strings"
"golang.org/x/net/websocket" "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 { type WebSocket struct {
Path string WSConfig
Command string *http.Request
Arguments []string
} }
// Handle handles a WebSocket connection. It launches the // Handle handles a WebSocket connection. It launches the
...@@ -21,12 +24,67 @@ func (ws WebSocket) Handle(conn *websocket.Conn) { ...@@ -21,12 +24,67 @@ func (ws WebSocket) Handle(conn *websocket.Conn) {
cmd.Stdin = conn cmd.Stdin = conn
cmd.Stdout = conn cmd.Stdout = conn
// TODO: Set environment variables according to CGI 1.1 err := ws.buildEnv(cmd)
// cf. http://tools.ietf.org/html/rfc3875#section-4.1.4 if err != nil {
cmd.Env = append(cmd.Env, `GATEWAY_INTERFACE="caddy-CGI/1.1"`) // TODO
}
err := cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
panic(err) 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"
)
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
package websockets package websockets
import ( import (
"log" "errors"
"net/http" "net/http"
"github.com/flynn/go-shlex" "github.com/flynn/go-shlex"
...@@ -12,16 +12,31 @@ import ( ...@@ -12,16 +12,31 @@ import (
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
// WebSockets is a type which holds configuration type (
// for the websocket middleware collectively. // WebSockets is a type that holds configuration for the
type WebSockets struct { // websocket middleware generally, like a list of all the
Sockets []WebSocket // websocket endpoints.
} WebSockets struct {
Sockets []WSConfig
}
// 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 more or less converts the HTTP request to a WebSocket connection. // ServeHTTP converts the HTTP request to a WebSocket connection and serves it up.
func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, socket := range ws.Sockets { for _, sockconfig := range ws.Sockets {
if middleware.Path(r.URL.Path).Matches(socket.Path) { if middleware.Path(r.URL.Path).Matches(sockconfig.Path) {
socket := WebSocket{
WSConfig: sockconfig,
Request: r,
}
websocket.Handler(socket.Handle).ServeHTTP(w, r) websocket.Handler(socket.Handle).ServeHTTP(w, r)
return return
} }
...@@ -30,7 +45,7 @@ func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -30,7 +45,7 @@ func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// New constructs and configures a new websockets middleware instance. // New constructs and configures a new websockets middleware instance.
func New(c middleware.Controller) (middleware.Middleware, error) { func New(c middleware.Controller) (middleware.Middleware, error) {
var websocks []WebSocket var websocks []WSConfig
var path string var path string
var command string var command string
...@@ -62,9 +77,9 @@ func New(c middleware.Controller) (middleware.Middleware, error) { ...@@ -62,9 +77,9 @@ func New(c middleware.Controller) (middleware.Middleware, error) {
parts, err := shlex.Split(command) parts, err := shlex.Split(command)
if err != nil { 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 { } 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] cmd = parts[0]
...@@ -72,7 +87,7 @@ func New(c middleware.Controller) (middleware.Middleware, error) { ...@@ -72,7 +87,7 @@ func New(c middleware.Controller) (middleware.Middleware, error) {
args = parts[1:] args = parts[1:]
} }
websocks = append(websocks, WebSocket{ websocks = append(websocks, WSConfig{
Path: path, Path: path,
Command: cmd, Command: cmd,
Arguments: args, Arguments: args,
......
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