mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-24 23:57:05 -05:00
Improvements to websocket middleware
This commit is contained in:
parent
d7ae9fb4a2
commit
abdadf1ee1
2 changed files with 87 additions and 40 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue