Skip to content

Commit

Permalink
CGI support
Browse files Browse the repository at this point in the history
  • Loading branch information
jwalnes committed Nov 26, 2013
1 parent b4aa0f6 commit 3a72ef8
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 42 deletions.
6 changes: 4 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func parseCommandLine() Config {
reverseLookupFlag := flag.Bool("reverselookup", true, "Perform reverse DNS lookups on remote clients")
scriptDirFlag := flag.String("dir", "", "Base directory for WebSocket scripts")
staticDirFlag := flag.String("staticdir", "", "Serve static content from this directory over HTTP")
cgiDirFlag := flag.String("cgidir", "", "Serve CGI scripts from this directory over HTTP")
devConsoleFlag := flag.Bool("devconsole", false, "Enable development console (cannot be used in conjuction with --staticdir)")

flag.Parse()
Expand Down Expand Up @@ -75,6 +76,7 @@ func parseCommandLine() Config {
config.ReverseLookup = *reverseLookupFlag
config.ScriptDir = *scriptDirFlag
config.StaticDir = *staticDirFlag
config.CgiDir = *cgiDirFlag
config.DevConsole = *devConsoleFlag
config.StartupTime = time.Now()
config.ServerSoftware = fmt.Sprintf("websocketd/%s", Version())
Expand All @@ -96,8 +98,8 @@ func parseCommandLine() Config {
}

args := flag.Args()
if len(args) < 1 && config.ScriptDir == "" {
fmt.Fprintf(os.Stderr, "Please specify COMMAND or provide --dir argument.\n")
if len(args) < 1 && config.ScriptDir == "" && config.StaticDir == "" && config.CgiDir == "" {
fmt.Fprintf(os.Stderr, "Please specify COMMAND or provide --dir, --staticdir or --cgidir argument.\n")
os.Exit(1)
}

Expand Down
8 changes: 8 additions & 0 deletions examples/cgi-bin/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
This examples directory shows how websocketd can also serve CGI scripts via HTTP.

$ websocketd --port=1234 --cgidir=examples/cgi-bin
# Then access http://localhost:1234/dump-env.sh


You can also test the command files by running from the command line.

42 changes: 42 additions & 0 deletions examples/cgi-bin/dump-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash

# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

# Standard CGI(ish) environment variables, as defined in
# http://tools.ietf.org/html/rfc3875
NAMES="""
AUTH_TYPE
CONTENT_LENGTH
CONTENT_TYPE
GATEWAY_INTERFACE
PATH_INFO
PATH_TRANSLATED
QUERY_STRING
REMOTE_ADDR
REMOTE_HOST
REMOTE_IDENT
REMOTE_PORT
REMOTE_USER
REQUEST_METHOD
REQUEST_URI
SCRIPT_NAME
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
UNIQUE_ID
"""

echo "Content-type: text/plain"
echo

for NAME in ${NAMES}
do
echo ${NAME}=${!NAME:-<unset>}
done

# Additional HTTP headers
env | grep '^HTTP_'
9 changes: 3 additions & 6 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,8 @@ Options:
options should not be specified.
--staticdir=DIR Serve static files in this directory over HTTP.
If a static file has the same filename as a
WebSocket handler, the WebSocket handler will
always take priority.
This flag cannot be used in conjunction
with --devconsole.
--cgidir=DIR Serve CGI scripts in this directory over HTTP.
--help Print help and exit.
Expand All @@ -69,7 +66,7 @@ Options:
endpoint at ws://[host]/foo, you can
visit http://[host]/foo in your browser.
This flag cannot be used in conjunction
with --staticdir.
with --staticdir or --cgidir.
--loglevel={debug, Log level to use (from most to least
trace, verbose).
Expand Down
3 changes: 2 additions & 1 deletion libwebsocketd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type Config struct {
UsingScriptDir bool // Are we running with a script dir.
StartupTime time.Time // Server startup time (used for dev console caching).
StaticDir string // If set, static files will be served from this dir over HTTP.
DevConsole bool // Enable dev console. This disables StaticDir.
CgiDir string // If set, CGI scripts will be served from this dir over HTTP.
DevConsole bool // Enable dev console. This disables StaticDir and CgiDir.
ServerSoftware string // Value to pass to SERVER_SOFTWARE environment variable (e.g. websocketd/1.2.3).
Env []string // Additional environment variables to pass to process ("key=value").
}
7 changes: 4 additions & 3 deletions libwebsocketd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package libwebsocketd
import (
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"
Expand All @@ -27,8 +28,8 @@ func generateId() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}

func remoteDetails(ws *websocket.Conn, config *Config) (string, string, string, error) {
remoteAddr, remotePort, err := net.SplitHostPort(ws.Request().RemoteAddr)
func remoteDetails(req *http.Request, config *Config) (string, string, string, error) {
remoteAddr, remotePort, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return "", "", "", err
}
Expand All @@ -53,7 +54,7 @@ func createEnv(ws *websocket.Conn, config *Config, urlInfo *URLInfo, id string)
headers := req.Header
url := req.URL

remoteAddr, remoteHost, remotePort, err := remoteDetails(ws, config)
remoteAddr, remoteHost, remotePort, err := remoteDetails(ws.Request(), config)
if err != nil {
return nil, err
}
Expand Down
77 changes: 55 additions & 22 deletions libwebsocketd/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
package libwebsocketd

import (
"code.google.com/p/go.net/websocket"
"fmt"
"net/http"
"net/http/cgi"
"os"
"path"
"path/filepath"
"strconv"
"strings"

"code.google.com/p/go.net/websocket"
)

type HttpWsMuxHandler struct {
Expand All @@ -22,8 +25,20 @@ type HttpWsMuxHandler struct {
// Main HTTP handler. Muxes between WebSocket handler, DevConsole or 404.
func (h HttpWsMuxHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
hdrs := req.Header

log := h.Log.NewLevel(h.Log.LogFunc)
log.Associate("url", fmt.Sprintf("http://%s%s", req.RemoteAddr, req.URL.RequestURI()))

_, remoteHost, _, err := remoteDetails(req, h.Config)
if err != nil {
log.Error("session", "Could not understand remote address '%s': %s", req.RemoteAddr, err)
return
}

log.Associate("remote", remoteHost)

// WebSocket
if strings.ToLower(hdrs.Get("Upgrade")) == "websocket" && strings.ToLower(hdrs.Get("Connection")) == "upgrade" {
// WebSocket

if hdrs.Get("Origin") == "null" {
// Fix up mismatch between how Chrome reports Origin
Expand All @@ -32,40 +47,58 @@ func (h HttpWsMuxHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
hdrs.Set("Origin", "file:")
}

wsHandler := websocket.Handler(func(ws *websocket.Conn) {
acceptWebSocket(ws, h.Config, h.Log)
})
wsHandler.ServeHTTP(w, req)
} else if h.Config.DevConsole {
// Dev console (if enabled)
if h.Config.CommandName != "" || h.Config.UsingScriptDir {
wsHandler := websocket.Handler(func(ws *websocket.Conn) {
acceptWebSocket(ws, h.Config, log)
})
wsHandler.ServeHTTP(w, req)
return
}
}

// Dev console (if enabled)
if h.Config.DevConsole {
content := strings.Replace(ConsoleContent, "{{license}}", License, -1)
http.ServeContent(w, req, ".html", h.Config.StartupTime, strings.NewReader(content))
} else if h.Config.StaticDir != "" {
// Serve static files
return
}

// CGI scripts
if h.Config.CgiDir != "" {
filePath := path.Join(h.Config.CgiDir, fmt.Sprintf(".%s", filepath.FromSlash(req.URL.Path)))

if fi, err := os.Stat(filePath); err == nil && !fi.IsDir() {
cgiHandler := &cgi.Handler{
Path: filePath,
}
log.Associate("cgiscript", filePath)
log.Access("http", "CGI")
cgiHandler.ServeHTTP(w, req)
return
}
}

// Static files
if h.Config.StaticDir != "" {
handler := http.FileServer(http.Dir(h.Config.StaticDir))
log.Access("http", "STATIC")
handler.ServeHTTP(w, req)
} else {
// 404
http.NotFound(w, req)
return
}

// 404
log.Access("http", "NOT FOUND")
http.NotFound(w, req)
}

func acceptWebSocket(ws *websocket.Conn, config *Config, log *LogScope) {
defer ws.Close()

req := ws.Request()
id := generateId()
_, remoteHost, _, err := remoteDetails(ws, config)
if err != nil {
log.Error("session", "Could not understand remote address '%s': %s", req.RemoteAddr, err)
return
}

log = log.NewLevel(log.LogFunc)
log.Associate("id", id)
log.Associate("url", fmt.Sprintf("http://%s%s", req.RemoteAddr, req.URL.RequestURI()))
log.Associate("origin", req.Header.Get("Origin"))
log.Associate("remote", remoteHost)

log.Access("session", "CONNECT")
defer log.Access("session", "DISCONNECT")
Expand Down
25 changes: 17 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,36 @@ func main() {

log := libwebsocketd.RootLogScope(config.LogLevel, log)

if config.DevConsole && config.StaticDir != "" {
log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --staticdir. Pick one.")
os.Exit(4)
if config.DevConsole {
if config.StaticDir != "" {
log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --staticdir. Pick one.")
os.Exit(4)
}
if config.CgiDir != "" {
log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --cgidir. Pick one.")
os.Exit(4)
}
}

http.Handle(config.BasePath, libwebsocketd.HttpWsMuxHandler{
Config: config.Config,
Log: log})

log.Info("server", "Starting WebSocket server : ws://%s%s", config.Addr, config.BasePath)
log.Info("server", "Starting WebSocket server : ws://%s%s", config.Addr, config.BasePath)
if config.DevConsole {
log.Info("server", "Developer console enabled : http://%s/", config.Addr)
log.Info("server", "Developer console enable d : http://%s/", config.Addr)
}
if config.UsingScriptDir {
log.Info("server", "Serving from directory : %s", config.ScriptDir)
} else {
log.Info("server", "Serving using application : %s %s", config.CommandName, strings.Join(config.CommandArgs, " "))
log.Info("server", "Serving from directory : %s", config.ScriptDir)
} else if config.CommandName != "" {
log.Info("server", "Serving using application : %s %s", config.CommandName, strings.Join(config.CommandArgs, " "))
}
if config.StaticDir != "" {
log.Info("server", "Serving static content from : %s", config.StaticDir)
}
if config.CgiDir != "" {
log.Info("server", "Serving CGI scripts from : %s", config.CgiDir)
}

err := http.ListenAndServe(config.Addr, nil)
if err != nil {
Expand Down

0 comments on commit 3a72ef8

Please sign in to comment.