Skip to content

Commit

Permalink
refactored to be a library as well as a server.
Browse files Browse the repository at this point in the history
websocketd can now be referenced as a library in your existing golang
web project by adding a handler. add simple documentation for using as
an embedded library.
  • Loading branch information
Gareth Jones authored and Gareth Jones committed Mar 15, 2013
1 parent f28396f commit c35601a
Show file tree
Hide file tree
Showing 17 changed files with 366 additions and 248 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,51 @@ __count.html__:
Open this page in your web-browser. It will even work if you open it directly
from disk using a `file://` URL.

Embedding Websocketd in your application
----------------------------------------

It is possible to embed websocketd directly inside an existing go
project by registering a handler. Here is an example:

package main

import (
"fmt"
wsd "github.com/joewalnes/websocketd/libwebsocketd"
"net/http"
)

func main() {
// A log scope allows you to customize the logging that websocketd performs. You can provide your own
// log scope with a log func.
logScope := wsd.RootLogScope(wsd.LogAccess, func(l *wsd.LogScope, level wsd.LogLevel, levelName string,
category string, msg string, args ...interface{}) {
fmt.Println(args...)
})
// Configuration options tell websocketd where to look for programs to run as WebSockets.
config := &wsd.Config{
ScriptDir: "./ws-bin",
UsingScriptDir: true,
StartupTime: time.Now(),
DevConsole: true,
}
// Register your route and handler.
http.HandleFunc("/ws-bin/", func(rw http.ResponseWriter, req *http.Request) {
handler := http.StripPrefix("/ws-bin", wsd.HttpWsMuxHandler{
Config: config,
Log: logScope,
})
handler.ServeHTTP(rw, req)
})
if err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil); err != nil {
fmt.Println("could not start server!", err)
os.Exit(1)
}
}

User Manual
-----------

**[More documentation in the user manual](https://github.com/joewalnes/websocketd/wiki)**

=======
71 changes: 50 additions & 21 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,69 @@ import (
"os"
"path/filepath"
"time"

"github.com/joewalnes/websocketd/libwebsocketd"
)

type Config struct {
Addr string // TCP address to listen on. e.g. ":1234", "1.2.3.4:1234"
Verbose bool // Verbose logging.
BasePath string // Base URL path. e.g. "/"
CommandName string // Command to execute.
CommandArgs []string // Additional args to pass to command.
ReverseLookup bool // Perform reverse DNS lookups on hostnames (useful, but slower).
ScriptDir string // Base directory for websocket scripts
UsingScriptDir bool // Are we running with a script dir
DevConsole bool // Enable dev console
StartupTime time.Time // Server startup time (used for dev console caching)
BasePath string // Base URL path. e.g. "/"
Addr string // TCP address to listen on. e.g. ":1234", "1.2.3.4:1234"
Verbose bool // Verbose logging.
LogLevel libwebsocketd.LogLevel
*libwebsocketd.Config
}

func parseCommandLine() Config {
var config Config
var mainConfig Config
var config libwebsocketd.Config

// If adding new command line options, also update the help text in help.go.
// The flag library's auto-generate help message isn't pretty enough.

// server config options
portFlag := flag.Int("port", 80, "HTTP port to listen on")
addressFlag := flag.String("address", "0.0.0.0", "Interface to bind to (e.g. 127.0.0.1)")
basePathFlag := flag.String("basepath", "/", "Base URL path (e.g /)")
verboseFlag := flag.Bool("verbose", false, "Enable verbose logging")
reverseLookupFlag := flag.Bool("reverselookup", true, "Perform reverse DNS lookups on remote clients")
scriptDirFlag := flag.String("dir", "", "Base directory for WebSocket scripts")
versionFlag := flag.Bool("version", false, "Print version and exit")
licenseFlag := flag.Bool("license", false, "Print license and exit")
logLevelFlag := flag.String("loglevel", "access", "Log level, one of: debug, trace, access, info, error, fatal")

// lib config options
basePathFlag := flag.String("basepath", "/", "Base URL path (e.g /)")
reverseLookupFlag := flag.Bool("reverselookup", true, "Perform reverse DNS lookups on remote clients")
scriptDirFlag := flag.String("dir", "", "Base directory for WebSocket scripts")
devConsoleFlag := flag.Bool("devconsole", false, "Enable development console")

flag.Parse()

config.Addr = fmt.Sprintf("%s:%d", *addressFlag, *portFlag)
config.Verbose = *verboseFlag
config.BasePath = *basePathFlag
mainConfig.Addr = fmt.Sprintf("%s:%d", *addressFlag, *portFlag)
mainConfig.Verbose = *verboseFlag
mainConfig.BasePath = *basePathFlag

switch *logLevelFlag {
case "debug":
mainConfig.LogLevel = libwebsocketd.LogDebug
break
case "trace":
mainConfig.LogLevel = libwebsocketd.LogTrace
break
case "access":
mainConfig.LogLevel = libwebsocketd.LogAccess
break
case "info":
mainConfig.LogLevel = libwebsocketd.LogInfo
break
case "error":
mainConfig.LogLevel = libwebsocketd.LogError
break
case "fatal":
mainConfig.LogLevel = libwebsocketd.LogFatal
break
default:
PrintHelp()
os.Exit(1)
}

config.ReverseLookup = *reverseLookupFlag
config.ScriptDir = *scriptDirFlag
config.DevConsole = *devConsoleFlag
Expand All @@ -58,13 +85,13 @@ func parseCommandLine() Config {
}

if *versionFlag {
fmt.Printf("%s %s\n", filepath.Base(os.Args[0]), Version())
fmt.Printf("%s %s\n", filepath.Base(os.Args[0]), libwebsocketd.Version())
os.Exit(2)
}

if *licenseFlag {
fmt.Printf("%s %s\n", filepath.Base(os.Args[0]), Version())
fmt.Printf("%s\n", License)
fmt.Printf("%s %s\n", filepath.Base(os.Args[0]), libwebsocketd.Version())
fmt.Printf("%s\n", libwebsocketd.License)
os.Exit(2)
}

Expand Down Expand Up @@ -94,5 +121,7 @@ func parseCommandLine() Config {
config.UsingScriptDir = true
}

return config
mainConfig.Config = &config

return mainConfig
}
11 changes: 10 additions & 1 deletion help.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"os"
"path/filepath"
"strings"

"github.com/joewalnes/websocketd/libwebsocketd"
)

const (
Expand Down Expand Up @@ -64,6 +66,13 @@ Options:
endpoint at ws://[host]/foo, you can
visit http://[host]/foo in your browser.
--loglevel={debug, Log level to use (from most to least
trace, verbose).
access,
info,
error,
fatal}
Full documentation at http://websocketd.com/
Copyright 2013 Joe Walnes and the websocketd team. All rights reserved.
Expand All @@ -74,6 +83,6 @@ BSD license: Run '{{binary}} --license' for details.
func PrintHelp() {
msg := strings.Trim(help, " \n")
msg = strings.Replace(msg, "{{binary}}", filepath.Base(os.Args[0]), -1)
msg = strings.Replace(msg, "{{version}}", Version(), -1)
msg = strings.Replace(msg, "{{version}}", libwebsocketd.Version(), -1)
fmt.Fprintf(os.Stderr, "%s\n", msg)
}
20 changes: 20 additions & 0 deletions libwebsocketd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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.

package libwebsocketd

import (
"time"
)

type Config struct {
CommandName string // Command to execute.
CommandArgs []string // Additional args to pass to command.
ReverseLookup bool // Perform reverse DNS lookups on hostnames (useful, but slower).
ScriptDir string // Base directory for websocket scripts
UsingScriptDir bool // Are we running with a script dir
StartupTime time.Time // Server startup time (used for dev console caching)
DevConsole bool // Enable dev console
}
2 changes: 1 addition & 1 deletion console.go → libwebsocketd/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main
package libwebsocketd

// Although this isn't particularly elegant, it's the simplest
// way to embed the console content into the binary.
Expand Down
2 changes: 1 addition & 1 deletion endpoint.go → libwebsocketd/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main
package libwebsocketd

type Endpoint interface {
Terminate()
Expand Down
2 changes: 1 addition & 1 deletion env.go → libwebsocketd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main
package libwebsocketd

import (
"fmt"
Expand Down
124 changes: 124 additions & 0 deletions libwebsocketd/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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.

package libwebsocketd

import (
"fmt"
"net/http"
"strconv"
"strings"

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

type HttpWsMuxHandler struct {
Config *Config
Log *LogScope
}

// Main HTTP handler. Muxes between WebSocket handler, DevConsole or 404.
func (h HttpWsMuxHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
hdrs := req.Header
if strings.ToLower(hdrs.Get("Upgrade")) == "websocket" && strings.ToLower(hdrs.Get("Connection")) == "upgrade" {
// WebSocket
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)
content := strings.Replace(ConsoleContent, "{{license}}", License, -1)
http.ServeContent(w, req, ".html", h.Config.StartupTime, strings.NewReader(content))
} else {
// 404
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")

urlInfo, err := parsePath(ws.Request().URL.Path, config)
if err != nil {
log.Access("session", "NOT FOUND: %s", err)
return
}
log.Debug("session", "URLInfo: %s", urlInfo)

env, err := createEnv(ws, config, urlInfo, id)
if err != nil {
log.Error("process", "Could not create ENV: %s", err)
return
}

commandName := config.CommandName
if config.UsingScriptDir {
commandName = urlInfo.FilePath
}
log.Associate("command", commandName)

launched, err := launchCmd(commandName, config.CommandArgs, env)
if err != nil {
log.Error("process", "Could not launch process %s %s (%s)", commandName, strings.Join(config.CommandArgs, " "), err)
return
}

log.Associate("pid", strconv.Itoa(launched.cmd.Process.Pid))

process := NewProcessEndpoint(launched, log)
wsEndpoint := NewWebSocketEndpoint(ws, log)

defer process.Terminate()

go process.ReadOutput(launched.stdout, config)
go wsEndpoint.ReadOutput(config)
go process.pipeStdErr(config)

pipeEndpoints(process, wsEndpoint, log)
}

func pipeEndpoints(process Endpoint, wsEndpoint *WebSocketEndpoint, log *LogScope) {
for {
select {
case msgFromProcess, ok := <-process.Output():
if ok {
log.Trace("send<-", "%s", msgFromProcess)
if !wsEndpoint.Send(msgFromProcess) {
return
}
} else {
// TODO: Log exit code. Mechanism differs on different platforms.
log.Trace("process", "Process terminated")
return
}
case msgFromSocket, ok := <-wsEndpoint.Output():
if ok {
log.Trace("recv->", "%s", msgFromSocket)
process.Send(msgFromSocket)
} else {
log.Trace("websocket", "WebSocket connection closed")
return
}
}
}
}
2 changes: 1 addition & 1 deletion launcher.go → libwebsocketd/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main
package libwebsocketd

import (
"errors"
Expand Down
2 changes: 1 addition & 1 deletion launcher_test.go → libwebsocketd/launcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main
package libwebsocketd

import (
"io/ioutil"
Expand Down
2 changes: 1 addition & 1 deletion license.go → libwebsocketd/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main
package libwebsocketd

const (
License = `
Expand Down
Loading

0 comments on commit c35601a

Please sign in to comment.