diff --git a/config.go b/config.go index beee4d8e..df22a85b 100644 --- a/config.go +++ b/config.go @@ -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() @@ -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()) @@ -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) } diff --git a/examples/cgi-bin/README.txt b/examples/cgi-bin/README.txt new file mode 100644 index 00000000..5c0a6244 --- /dev/null +++ b/examples/cgi-bin/README.txt @@ -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. + diff --git a/examples/cgi-bin/dump-env.sh b/examples/cgi-bin/dump-env.sh new file mode 100755 index 00000000..2c7350cb --- /dev/null +++ b/examples/cgi-bin/dump-env.sh @@ -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:-} +done + +# Additional HTTP headers +env | grep '^HTTP_' diff --git a/help.go b/help.go index 81a60127..0f7b59be 100644 --- a/help.go +++ b/help.go @@ -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. @@ -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). diff --git a/libwebsocketd/config.go b/libwebsocketd/config.go index f6e84223..7ccc0896 100644 --- a/libwebsocketd/config.go +++ b/libwebsocketd/config.go @@ -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"). } diff --git a/libwebsocketd/env.go b/libwebsocketd/env.go index 22d633cc..79da392b 100644 --- a/libwebsocketd/env.go +++ b/libwebsocketd/env.go @@ -8,6 +8,7 @@ package libwebsocketd import ( "fmt" "net" + "net/http" "os" "strconv" "strings" @@ -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 } @@ -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 } diff --git a/libwebsocketd/http.go b/libwebsocketd/http.go index bc2f822d..79838796 100644 --- a/libwebsocketd/http.go +++ b/libwebsocketd/http.go @@ -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 { @@ -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 @@ -32,22 +47,48 @@ 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) { @@ -55,17 +96,9 @@ func acceptWebSocket(ws *websocket.Conn, config *Config, log *LogScope) { 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") diff --git a/main.go b/main.go index ea7916c9..8545f907 100644 --- a/main.go +++ b/main.go @@ -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 {