pax_global_header00006660000000000000000000000064134236020430014507gustar00rootroot0000000000000052 comment=0440211d7862dc76b5f2499d7dfcd4ef6a9c2fa2 websocketd-0.3.1/000077500000000000000000000000001342360204300136425ustar00rootroot00000000000000websocketd-0.3.1/.gitignore000066400000000000000000000000201342360204300156220ustar00rootroot00000000000000websocketd go-* websocketd-0.3.1/AUTHORS000066400000000000000000000002621342360204300147120ustar00rootroot00000000000000websocketd authors ================== Joe Walnes Gareth Jones Ajit George Alex Sergeyev websocketd-0.3.1/CHANGES000066400000000000000000000032071342360204300146370ustar00rootroot00000000000000Version 0.3.1 (Jan 28, 2019) * Minor improvements to websocketd itself * Use of go modules, gorilla websockets set to 1.4.0 * Binaries build code switched to 1.11.5 (improving underlying protocol handlers) Version 0.3.0 (??, 2017) * Migration of underlying websocket server to Gorilla Websocket lib. * Binaries build code switched to 1.9.2 Version 0.2.12 (Feb 17, 2016) * Update of underlying go standard libraries change how SSL works. SSL3 is no longer supported. * Support of commands that do not provide text IO (using them as binary websocket frames) * Minor changes in examples and --help output Version 0.2.11 (Jul 1, 2015) * PATH env variable is now passed to process by default * new --header* flags could generate custom HTTP headers for all websocketd-generated answers * fixed bug causing process to hang when WebSockets client disconnect is detected * minor changes for console app (default url building logic and tab char printing) * multiple changes of examples. Version 0.2.10 (Feb 16, 2015) * fixes for null-origin situations (#75, #96) * better bash examples (#103) * changelog and checksums for released files (#101, #105) Version 0.2.9 (May 19, 2014) * ability to listen multiple IP addresses (#40, #43) * proper support for TLS (#17) * resource limits enforcement (a.k.a. maxforks feature, #46) * passenv option to limit environment variables visible by running commands (#4) * fix for problem of closing upgraded websocket connection when script is not found (#29) * websocket origin restrictions via command line option (#20) * minor update for help flag behavior * minor fix for devconsole Version 0.2.8 (Jan 11, 2014) * ...websocketd-0.3.1/LICENSE000066400000000000000000000024571342360204300146570ustar00rootroot00000000000000Copyright (c) 2014, Joe Walnes and the websocketd authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. websocketd-0.3.1/Makefile000066400000000000000000000024321342360204300153030ustar00rootroot00000000000000# 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. # Self contained Go build file that will download and install (locally) the correct # version of Go, and build our programs. Go does not need to be installed on the # system (and if it already is, it will be ignored). # To manually invoke the locally installed Go, use ./go # Go installation config. GO_VER=1.11.5 SYSTEM_NAME:=$(shell uname -s | tr '[:upper:]' '[:lower:]') SYSTEM_ARCH:=$(shell uname -m) GO_ARCH:=$(if $(filter x86_64, $(SYSTEM_ARCH)),amd64,386) GO_VERSION:=$(GO_VER).$(SYSTEM_NAME)-$(GO_ARCH) GO_DOWNLOAD_URL:=https://dl.google.com/go/go$(GO_VERSION).tar.gz GO_DIR:=go-$(GO_VER) # Build websocketd binary websocketd: $(GO_DIR)/bin/go $(wildcard *.go) $(wildcard libwebsocketd/*.go) $(GO_DIR)/bin/go build localgo: $(GO_DIR)/bin/go # Download and unpack Go distribution. $(GO_DIR)/bin/go: mkdir -p $(GO_DIR) rm -f $@ @echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR) curl -s $(GO_DOWNLOAD_URL) | tar xf - --strip-components=1 -C $(GO_DIR) # Clean up binary clean: rm -rf websocketd .PHONY: clean # Also clean up downloaded Go clobber: clean rm -rf $(wildcard go-v*) .PHONY: clobber websocketd-0.3.1/README.md000066400000000000000000000116231342360204300151240ustar00rootroot00000000000000websocketd ========== `websocketd` is a small command-line tool that will wrap an existing command-line interface program, and allow it to be accessed via a WebSocket. WebSocket-capable applications can now be built very easily. As long as you can write an executable program that reads `STDIN` and writes to `STDOUT`, you can build a WebSocket server. Do it in Python, Ruby, Perl, Bash, .NET, C, Go, PHP, Java, Clojure, Scala, Groovy, Expect, Awk, VBScript, Haskell, Lua, R, whatever! No networking libraries necessary. -[@joewalnes](https://twitter.com/joewalnes) Details ------- Upon startup, `websocketd` will start a WebSocket server on a specified port, and listen for connections. Upon a connection, it will fork the appropriate process, and disconnect the process when the WebSocket connection closes (and vice-versa). Any message sent from the WebSocket client will be piped to the process's `STDIN` stream, followed by a `\n` newline. Any text printed by the process to `STDOUT` shall be sent as a WebSocket message whenever a `\n` newline is encountered. Download -------- If you're on a Mac, you can install `websocketd` using [Homebrew](http://brew.sh/). Just run `brew install websocketd`. For other operating systems, or if you don't want to use Homebrew, check out the link below. **[Download for Linux, OS X and Windows](https://github.com/joewalnes/websocketd/wiki/Download-and-install)** Quickstart ---------- To get started, we'll create a WebSocket endpoint that will accept connections, then send back messages, counting to 10 with 1 second pause between each one, before disconnecting. To show how simple it is, let's do it in Bash! __count.sh__: ```sh #!/bin/bash for ((COUNT = 1; COUNT <= 10; COUNT++)); do echo $COUNT sleep 1 done ``` Before turning it into a WebSocket server, let's test it from the command line. The beauty of `websocketd` is that servers work equally well in the command line, or in shell scripts, as they do in the server - with no modifications required. ```sh $ chmod +x count.sh $ ./count.sh 1 2 3 4 5 6 7 8 9 10 ``` Now let's turn it into a WebSocket server: ```sh $ websocketd --port=8080 ./count.sh ``` Finally, let's create a web-page that to test it. __count.html__: ```html


```
Open this page in your web-browser. It will even work if you open it directly
from disk using a `file://` URL.

More Features
-------------

*   Very simple install. Just [download](https://github.com/joewalnes/websocketd/wiki/Download-and-install) the single executable for Linux, Mac or Windows and run it. Minimal dependencies, no installers, no package managers, no external libraries. Suitable for development and production servers.
*   Server side scripts can access details about the WebSocket HTTP request (e.g. remote host, query parameters, cookies, path, etc) via standard [CGI environment variables](https://github.com/joewalnes/websocketd/wiki/Environment-variables).
*   As well as serving websocket daemons it also includes a static file server and classic CGI server for convenience.
*   Command line help available via `websocketd --help`.
*   Includes [WebSocket developer console](https://github.com/joewalnes/websocketd/wiki/Developer-console) to make it easy to test your scripts before you've built a JavaScript frontend.
*   [Examples in many programming languages](https://github.com/joewalnes/websocketd/tree/master/examples) are available to help you getting started.

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

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

Example Projects
----------------

*   [Plot real time Linux CPU/IO/Mem stats to a HTML5 dashboard using websocketd and vmstat](https://github.com/joewalnes/web-vmstats) _(for Linux)_
*   [Remote JScript & VBScript code execution tool based on websocketd](https://github.com/dab00/ws-console) _(for Windows)_
*   [Arbitrary REPL in the browser using websocketd](https://github.com/rowanthorpe/ws-repl)
*   [Retrieve SQL data from server with LiveCode and webSocketd](https://github.com/samansjukur/wslc)

Got more examples? Open a pull request.

My Other Projects
-----------------

*   [ReconnectingWebSocket](https://github.com/joewalnes/reconnecting-websocket) - Simplest way to add some robustness to your WebSocket connections.
*   [Smoothie Charts](http://smoothiecharts.org/) - JavaScript charts for streaming data.
*   Visit [The Igloo Lab](http://theigloolab.com/) to see and subscribe to other thingies I make.

And [follow @joewalnes](https://twitter.com/joewalnes)!
websocketd-0.3.1/config.go000066400000000000000000000215011342360204300154350ustar00rootroot00000000000000// 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 main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

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

type Config struct {
	Addr              []string // TCP addresses to listen on. e.g. ":1234", "1.2.3.4:1234" or "[::1]:1234"
	MaxForks          int      // Number of allowable concurrent forks
	LogLevel          libwebsocketd.LogLevel
	RedirPort         int
	CertFile, KeyFile string
	*libwebsocketd.Config
}

type Arglist []string

func (al *Arglist) String() string {
	return fmt.Sprintf("%v", []string(*al))
}

func (al *Arglist) Set(value string) error {
	*al = append(*al, value)
	return nil
}

// Borrowed from net/http/cgi
var defaultPassEnv = map[string]string{
	"darwin":  "PATH,DYLD_LIBRARY_PATH",
	"freebsd": "PATH,LD_LIBRARY_PATH",
	"hpux":    "PATH,LD_LIBRARY_PATH,SHLIB_PATH",
	"irix":    "PATH,LD_LIBRARY_PATH,LD_LIBRARYN32_PATH,LD_LIBRARY64_PATH",
	"linux":   "PATH,LD_LIBRARY_PATH",
	"openbsd": "PATH,LD_LIBRARY_PATH",
	"solaris": "PATH,LD_LIBRARY_PATH,LD_LIBRARY_PATH_32,LD_LIBRARY_PATH_64",
	"windows": "PATH,SystemRoot,COMSPEC,PATHEXT,WINDIR",
}

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

	flag.Usage = func() {}
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)

	// 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.

	addrlist := Arglist(make([]string, 0, 1)) // pre-reserve for 1 address
	flag.Var(&addrlist, "address", "Interfaces to bind to (e.g. 127.0.0.1 or [::1]).")

	// server config options
	portFlag := flag.Int("port", 0, "HTTP port to listen on")
	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")
	sslFlag := flag.Bool("ssl", false, "Use TLS on listening socket (see also --sslcert and --sslkey)")
	sslCert := flag.String("sslcert", "", "Should point to certificate PEM file when --ssl is used")
	sslKey := flag.String("sslkey", "", "Should point to certificate private key file when --ssl is used")
	maxForksFlag := flag.Int("maxforks", 0, "Max forks, zero means unlimited")
	closeMsFlag := flag.Uint("closems", 0, "Time to start sending signals (0 never)")
	redirPortFlag := flag.Int("redirport", 0, "HTTP port to redirect to canonical --port address")

	// lib config options
	binaryFlag := flag.Bool("binary", false, "Set websocketd to experimental binary mode (default is line by line)")
	reverseLookupFlag := flag.Bool("reverselookup", false, "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 conjunction with --staticdir)")
	passEnvFlag := flag.String("passenv", defaultPassEnv[runtime.GOOS], "List of envvars to pass to subprocesses (others will be cleaned out)")
	sameOriginFlag := flag.Bool("sameorigin", false, "Restrict upgrades if origin and host headers differ")
	allowOriginsFlag := flag.String("origin", "", "Restrict upgrades if origin does not match the list")

	headers := Arglist(make([]string, 0))
	headersWs := Arglist(make([]string, 0))
	headersHttp := Arglist(make([]string, 0))
	flag.Var(&headers, "header", "Custom headers for any response.")
	flag.Var(&headersWs, "header-ws", "Custom headers for successful WebSocket upgrade responses.")
	flag.Var(&headersHttp, "header-http", "Custom headers for all but WebSocket upgrade HTTP responses.")

	err := flag.CommandLine.Parse(os.Args[1:])
	if err != nil {
		if err == flag.ErrHelp {
			PrintHelp()
			os.Exit(0)
		} else {
			ShortHelp()
			os.Exit(2)
		}
	}

	port := *portFlag
	if port == 0 {
		if *sslFlag {
			port = 443
		} else {
			port = 80
		}
	}

	if socknum := len(addrlist); socknum != 0 {
		mainConfig.Addr = make([]string, socknum)
		for i, addrSingle := range addrlist {
			mainConfig.Addr[i] = fmt.Sprintf("%s:%d", addrSingle, port)
		}
	} else {
		mainConfig.Addr = []string{fmt.Sprintf(":%d", port)}
	}
	mainConfig.MaxForks = *maxForksFlag
	mainConfig.RedirPort = *redirPortFlag
	mainConfig.LogLevel = libwebsocketd.LevelFromString(*logLevelFlag)
	if mainConfig.LogLevel == libwebsocketd.LogUnknown {
		fmt.Printf("Incorrect loglevel flag '%s'. Use --help to see allowed values.\n", *logLevelFlag)
		ShortHelp()
		os.Exit(1)
	}

	config.Headers = []string(headers)
	config.HeadersWs = []string(headersWs)
	config.HeadersHTTP = []string(headersHttp)

	config.CloseMs = *closeMsFlag
	config.Binary = *binaryFlag
	config.ReverseLookup = *reverseLookupFlag
	config.Ssl = *sslFlag
	config.ScriptDir = *scriptDirFlag
	config.StaticDir = *staticDirFlag
	config.CgiDir = *cgiDirFlag
	config.DevConsole = *devConsoleFlag
	config.StartupTime = time.Now()
	config.ServerSoftware = fmt.Sprintf("websocketd/%s", Version())
	config.HandshakeTimeout = time.Millisecond * 1500 // only default for now

	if len(os.Args) == 1 {
		fmt.Printf("Command line arguments are missing.\n")
		ShortHelp()
		os.Exit(1)
	}

	if *versionFlag {
		fmt.Printf("%s %s\n", HelpProcessName(), Version())
		os.Exit(0)
	}

	if *licenseFlag {
		fmt.Printf("%s %s\n", HelpProcessName(), Version())
		fmt.Printf("%s\n", libwebsocketd.License)
		os.Exit(0)
	}

	// Reading SSL options
	if config.Ssl {
		if *sslCert == "" || *sslKey == "" {
			fmt.Fprintf(os.Stderr, "Please specify both --sslcert and --sslkey when requesting --ssl.\n")
			os.Exit(1)
		}
	} else {
		if *sslCert != "" || *sslKey != "" {
			fmt.Fprintf(os.Stderr, "You should not be using --ssl* flags when there is no --ssl option.\n")
			os.Exit(1)
		}
	}

	mainConfig.CertFile = *sslCert
	mainConfig.KeyFile = *sslKey

	// Building config.ParentEnv to avoid calling Environ all the time in the scripts
	// (caller is responsible for wiping environment if desired)
	config.ParentEnv = make([]string, 0)
	newlineCleaner := strings.NewReplacer("\n", " ", "\r", " ")
	for _, key := range strings.Split(*passEnvFlag, ",") {
		if key != "HTTPS" {
			if v := os.Getenv(key); v != "" {
				// inevitably adding flavor of libwebsocketd appendEnv func.
				// it's slightly nicer than in net/http/cgi implementation
				if clean := strings.TrimSpace(newlineCleaner.Replace(v)); clean != "" {
					config.ParentEnv = append(config.ParentEnv, fmt.Sprintf("%s=%s", key, clean))
				}
			}
		}
	}

	if *allowOriginsFlag != "" {
		config.AllowOrigins = strings.Split(*allowOriginsFlag, ",")
	}
	config.SameOrigin = *sameOriginFlag

	args := flag.Args()
	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")
		ShortHelp()
		os.Exit(1)
	}

	if len(args) > 0 {
		if config.ScriptDir != "" {
			fmt.Fprintf(os.Stderr, "Ambiguous. Provided COMMAND and --dir argument. Please only specify just one.\n")
			ShortHelp()
			os.Exit(1)
		}
		if path, err := exec.LookPath(args[0]); err == nil {
			config.CommandName = path // This can be command in PATH that we are able to execute
			config.CommandArgs = flag.Args()[1:]
			config.UsingScriptDir = false
		} else {
			fmt.Fprintf(os.Stderr, "Unable to locate specified COMMAND '%s' in OS path.\n", args[0])
			ShortHelp()
			os.Exit(1)
		}
	}

	if config.ScriptDir != "" {
		scriptDir, err := filepath.Abs(config.ScriptDir)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Could not resolve absolute path to dir '%s'.\n", config.ScriptDir)
			ShortHelp()
			os.Exit(1)
		}
		inf, err := os.Stat(scriptDir)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Could not find your script dir '%s'.\n", config.ScriptDir)
			ShortHelp()
			os.Exit(1)
		}
		if !inf.IsDir() {
			fmt.Fprintf(os.Stderr, "Did you mean to specify COMMAND instead of --dir '%s'?\n", config.ScriptDir)
			ShortHelp()
			os.Exit(1)
		} else {
			config.ScriptDir = scriptDir
			config.UsingScriptDir = true
		}
	}

	if config.CgiDir != "" {
		if inf, err := os.Stat(config.CgiDir); err != nil || !inf.IsDir() {
			fmt.Fprintf(os.Stderr, "Your CGI dir '%s' is not pointing to an accessible directory.\n", config.CgiDir)
			ShortHelp()
			os.Exit(1)
		}
	}

	if config.StaticDir != "" {
		if inf, err := os.Stat(config.StaticDir); err != nil || !inf.IsDir() {
			fmt.Fprintf(os.Stderr, "Your static dir '%s' is not pointing to an accessible directory.\n", config.StaticDir)
			ShortHelp()
			os.Exit(1)
		}
	}

	mainConfig.Config = &config

	return &mainConfig
}
websocketd-0.3.1/examples/000077500000000000000000000000001342360204300154605ustar00rootroot00000000000000websocketd-0.3.1/examples/bash/000077500000000000000000000000001342360204300163755ustar00rootroot00000000000000websocketd-0.3.1/examples/bash/README.txt000066400000000000000000000002031342360204300200660ustar00rootroot00000000000000This examples directory shows some examples written in Bash.

You can also test the command files by running from the command line.websocketd-0.3.1/examples/bash/chat.sh000077500000000000000000000014541342360204300176570ustar00rootroot00000000000000#!/bin/bash

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

# Run a simple chat server: websocketd --devconsole --port 8080 ./chat.sh
#
# Please note that this example requires GNU tail, which is not the default
# tail on OS X. Even though this script properly escapes the variables,
# please keep in mind that it is in general a bad idea to read
# untrusted data into variables and pass this onto the command line.

echo "Please enter your name:"; read USER
echo "[$(date)] ${USER} joined the chat" >> chat.log
echo "[$(date)] Welcome to the chat ${USER}!"
tail -n 0 -f chat.log --pid=$$ | grep --line-buffered -v "] ${USER}>" &
while read MSG; do echo "[$(date)] ${USER}> ${MSG}" >> chat.log; done
websocketd-0.3.1/examples/bash/count.sh000077500000000000000000000005111342360204300200610ustar00rootroot00000000000000#!/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.

# Simple example script that counts to 10 at ~2Hz, then stops.
for ((COUNT = 1; COUNT <= 10; COUNT++))
do
  echo $COUNT
  sleep 0.5
done
websocketd-0.3.1/examples/bash/dump-env.sh000077500000000000000000000013201342360204300204630ustar00rootroot00000000000000#!/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
  HTTPS
"""

for NAME in ${NAMES}
do
	echo "${NAME}=${!NAME:-}"
done

# Additional HTTP headers
env | grep '^HTTP_'
websocketd-0.3.1/examples/bash/greeter.sh000077500000000000000000000004561342360204300203760ustar00rootroot00000000000000#!/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.

# For each line FOO received on STDIN, respond with "Hello FOO!".
while read LINE
do
	echo "Hello $LINE!"
donewebsocketd-0.3.1/examples/bash/send-receive.sh000077500000000000000000000002431342360204300213040ustar00rootroot00000000000000#!/bin/bash


while true; do
	cnt=0
	while read -t 0.01 _; do
		((cnt++))
	done

	echo "$(date)" "($cnt line(s) received)"
	sleep $((RANDOM % 10 + 1)) & wait
done
websocketd-0.3.1/examples/c#/000077500000000000000000000000001342360204300157455ustar00rootroot00000000000000websocketd-0.3.1/examples/c#/.gitignore000066400000000000000000000026541342360204300177440ustar00rootroot00000000000000# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/

# mstest test results
TestResults

## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.sln.docstates

# Build results
[Dd]ebug/
[Rr]elease/
build/
test/
deploy/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
*.vssscc
.builds

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf

# Visual Studio profiler
*.psess
*.vsp

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*

# NCrunch
*ncrunch*/*
*.ncrunch*
.*crunch*.local.xml

# Installshield output folder 
[Ee]xpress

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish

# Publish Web Output
*.Publish.xml

# Windows Azure Build Output
csx
*.build.csdef

# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects

# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XMLwebsocketd-0.3.1/examples/c#/Count/000077500000000000000000000000001342360204300170355ustar00rootroot00000000000000websocketd-0.3.1/examples/c#/Count/App.config000066400000000000000000000002661342360204300207500ustar00rootroot00000000000000

     
        
    
websocketd-0.3.1/examples/c#/Count/Count.csproj000066400000000000000000000046661342360204300213630ustar00rootroot00000000000000

  
  
    Debug
    AnyCPU
    {FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}
    Exe
    Properties
    Count
    Count
    v4.5
    512
  
  
    AnyCPU
    true
    full
    false
    ..\bin\
    DEBUG;TRACE
    prompt
    4
  
  
    AnyCPU
    pdbonly
    true
    ..\bin\
    TRACE
    prompt
    4
  
  
    
    
    
    
    
    
    
  
  
    
    
  
  
    
  
  
  
websocketd-0.3.1/examples/c#/Count/Program.cs000066400000000000000000000005071342360204300207750ustar00rootroot00000000000000using System;
using System.Linq;
using System.Threading;

namespace Count
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var i in Enumerable.Range(1, 10))
            {
                Console.WriteLine(i);
                Thread.Sleep(1000);
            }
        }
    }
}websocketd-0.3.1/examples/c#/Count/Properties/000077500000000000000000000000001342360204300211715ustar00rootroot00000000000000websocketd-0.3.1/examples/c#/Count/Properties/AssemblyInfo.cs000066400000000000000000000024261342360204300241170ustar00rootroot00000000000000using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Count")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Count")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("38cb6838-4839-498f-b09f-d0e67a2e9974")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
websocketd-0.3.1/examples/c#/Echo/000077500000000000000000000000001342360204300166235ustar00rootroot00000000000000websocketd-0.3.1/examples/c#/Echo/App.config000066400000000000000000000002661342360204300205360ustar00rootroot00000000000000

     
        
    
websocketd-0.3.1/examples/c#/Echo/Echo.csproj000066400000000000000000000046641342360204300207350ustar00rootroot00000000000000

  
  
    Debug
    AnyCPU
    {65414388-1058-414C-910F-CBD58E2B064A}
    Exe
    Properties
    Echo
    Echo
    v4.5
    512
  
  
    AnyCPU
    true
    full
    false
    ..\bin\
    DEBUG;TRACE
    prompt
    4
  
  
    AnyCPU
    pdbonly
    true
    ..\bin\
    TRACE
    prompt
    4
  
  
    
    
    
    
    
    
    
  
  
    
    
  
  
    
  
  
  
websocketd-0.3.1/examples/c#/Echo/Program.cs000066400000000000000000000004111342360204300205550ustar00rootroot00000000000000using System;

namespace Echo
{
    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                var msg = Console.ReadLine();
                Console.WriteLine(msg);
            }
        }
    }
}websocketd-0.3.1/examples/c#/Echo/Properties/000077500000000000000000000000001342360204300207575ustar00rootroot00000000000000websocketd-0.3.1/examples/c#/Echo/Properties/AssemblyInfo.cs000066400000000000000000000024241342360204300237030ustar00rootroot00000000000000using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Echo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Echo")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6199e327-f4cd-438c-a6c5-87861f837fb1")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
websocketd-0.3.1/examples/c#/Examples.sln000066400000000000000000000025011342360204300202370ustar00rootroot00000000000000
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Echo", "Echo\Echo.csproj", "{65414388-1058-414C-910F-CBD58E2B064A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Count", "Count\Count.csproj", "{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.Build.0 = Release|Any CPU
		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal
websocketd-0.3.1/examples/c#/README.md000066400000000000000000000010501342360204300172200ustar00rootroot00000000000000## Running the examples on Windows

1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH)
2. open and build the **Examples.sln** solution
3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it
4. double click the **run_count.cmd** to start the count example, go to the [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browserwebsocketd-0.3.1/examples/c#/run_count.cmd000066400000000000000000000000611342360204300204430ustar00rootroot00000000000000websocketd --port=8080 --devconsole bin\Count.exewebsocketd-0.3.1/examples/c#/run_echo.cmd000066400000000000000000000000601342360204300202300ustar00rootroot00000000000000websocketd --port=8080 --devconsole bin\Echo.exewebsocketd-0.3.1/examples/cgi-bin/000077500000000000000000000000001342360204300167705ustar00rootroot00000000000000websocketd-0.3.1/examples/cgi-bin/README.txt000066400000000000000000000003771342360204300204750ustar00rootroot00000000000000This 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.

websocketd-0.3.1/examples/cgi-bin/dump-env.sh000077500000000000000000000014271342360204300210660ustar00rootroot00000000000000#!/bin/sh

# 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
  HTTPS
"""

echo "Content-type: text/plain"
echo

for NAME in ${NAMES}
do
	eval "value=\${${NAME}}"
	env -i "${NAME}=${value:-}"
done

# Additional HTTP headers
env | egrep '^(HTTP|SSL)_'
websocketd-0.3.1/examples/f#/000077500000000000000000000000001342360204300157505ustar00rootroot00000000000000websocketd-0.3.1/examples/f#/.gitignore000066400000000000000000000026541342360204300177470ustar00rootroot00000000000000# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/

# mstest test results
TestResults

## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.sln.docstates

# Build results
[Dd]ebug/
[Rr]elease/
build/
test/
deploy/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
*.vssscc
.builds

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf

# Visual Studio profiler
*.psess
*.vsp

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*

# NCrunch
*ncrunch*/*
*.ncrunch*
.*crunch*.local.xml

# Installshield output folder 
[Ee]xpress

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish

# Publish Web Output
*.Publish.xml

# Windows Azure Build Output
csx
*.build.csdef

# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects

# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XMLwebsocketd-0.3.1/examples/f#/Count/000077500000000000000000000000001342360204300170405ustar00rootroot00000000000000websocketd-0.3.1/examples/f#/Count/App.config000066400000000000000000000012541342360204300207510ustar00rootroot00000000000000

     
        
    
    
      
        
          
          
          
          
          
        
      
    	
websocketd-0.3.1/examples/f#/Count/Count.fsproj000066400000000000000000000054701342360204300213630ustar00rootroot00000000000000

  
  
    Debug
    AnyCPU
    2.0
    367c65fd-485b-45c0-9c0e-7ce455951eae
    Exe
    Count
    Count
    v4.5
    Count
  
  
    true
    full
    false
    false
    ..\bin\
    DEBUG;TRACE
    3
    AnyCPU
    ..\bin\Count.XML
    true
  
  
    pdbonly
    true
    true
    ..\bin\
    TRACE
    3
    AnyCPU
    ..\bin\Count.XML
    true
  
  
    
    
      True
    
    
    
    
  
  
    
    
  
  
    11
  
  
  
websocketd-0.3.1/examples/f#/Count/Program.fs000066400000000000000000000002771342360204300210070ustar00rootroot00000000000000open System
open System.Threading

[]
let main argv = 
    [| 1..10 |] |> Array.iter (Console.WriteLine >> (fun _ -> Thread.Sleep(1000)))

    0 // return an integer exit code
websocketd-0.3.1/examples/f#/Echo/000077500000000000000000000000001342360204300166265ustar00rootroot00000000000000websocketd-0.3.1/examples/f#/Echo/App.config000066400000000000000000000012541342360204300205370ustar00rootroot00000000000000

     
        
    
    
      
        
          
          
          
          
          
        
      
    	
websocketd-0.3.1/examples/f#/Echo/Echo.fsproj000066400000000000000000000054661342360204300207440ustar00rootroot00000000000000

  
  
    Debug
    AnyCPU
    2.0
    6f680332-caa0-447b-a87e-af272ded5701
    Exe
    Echo
    Echo
    v4.5
    Echo
  
  
    true
    full
    false
    false
    ..\bin
    DEBUG;TRACE
    3
    AnyCPU
    ..\bin\Echo.XML
    true
  
  
    pdbonly
    true
    true
    ..\bin
    TRACE
    3
    AnyCPU
    bin\Release\Echo.XML
    true
  
  
    
    
      True
    
    
    
    
  
  
    
    
  
  
    11
  
  
  
websocketd-0.3.1/examples/f#/Echo/Program.fs000066400000000000000000000003041342360204300205640ustar00rootroot00000000000000open System

[]
let main argv = 
    let rec recLoop () =
        Console.ReadLine() |> Console.WriteLine
        recLoop()

    recLoop()
    
    0 // return an integer exit code
websocketd-0.3.1/examples/f#/Examples.sln000066400000000000000000000025011342360204300202420ustar00rootroot00000000000000
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Echo", "Echo\Echo.fsproj", "{6F680332-CAA0-447B-A87E-AF272DED5701}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Count", "Count\Count.fsproj", "{367C65FD-485B-45C0-9C0E-7CE455951EAE}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.Build.0 = Release|Any CPU
		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal
websocketd-0.3.1/examples/f#/README.md000066400000000000000000000010501342360204300172230ustar00rootroot00000000000000## Running the examples on Windows

1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH)
2. open and build the **Examples.sln** solution
3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it
4. double click the **run_count.cmd** to start the count example, go to the [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browserwebsocketd-0.3.1/examples/f#/run_count.cmd000066400000000000000000000000611342360204300204460ustar00rootroot00000000000000websocketd --port=8080 --devconsole bin\Count.exewebsocketd-0.3.1/examples/f#/run_echo.cmd000066400000000000000000000000601342360204300202330ustar00rootroot00000000000000websocketd --port=8080 --devconsole bin\Echo.exewebsocketd-0.3.1/examples/haskell/000077500000000000000000000000001342360204300171035ustar00rootroot00000000000000websocketd-0.3.1/examples/haskell/README.md000066400000000000000000000010041342360204300203550ustar00rootroot00000000000000## Haskell examples

### Count

Start the server with

```
$ websocketd --port=8080 --devconsole --passenv PATH ./count.hs
```

The passing of `PATH` was required for me because a typical Haskell installation of `runhaskell` does not go into `/usr/bin` but more like `/usr/local/bin`.

### Greeter

The greeter server waits for a line of text to be sent, then sends back a greeting in response, and continues to wait for more lines to come.

```
$ websocketd --port=8080 --devconsole --passenv PATH ./greeter.hs
```
websocketd-0.3.1/examples/haskell/count.hs000077500000000000000000000004261342360204300205740ustar00rootroot00000000000000#!/usr/bin/env runhaskell

import Control.Monad (forM_)
import Control.Concurrent (threadDelay)
import System.IO (hFlush, stdout)

-- | Count from 1 to 10 with a sleep
main :: IO ()
main = forM_ [1 :: Int .. 10] $ \count -> do
  print count
  hFlush stdout
  threadDelay 500000
websocketd-0.3.1/examples/haskell/greeter.hs000077500000000000000000000005061342360204300211000ustar00rootroot00000000000000#!/usr/bin/env runhaskell

import Control.Monad (unless)
import System.IO (hFlush, stdout, stdin, hIsEOF)

-- | For each line FOO received on STDIN, respond with "Hello FOO!".
main :: IO ()
main = do
  eof <- hIsEOF stdin
  unless eof $ do
    line <- getLine
    putStrLn $ "Hello " ++ line ++ "!"
    hFlush stdout
    main
websocketd-0.3.1/examples/html/000077500000000000000000000000001342360204300164245ustar00rootroot00000000000000websocketd-0.3.1/examples/html/count.html000066400000000000000000000013231342360204300204410ustar00rootroot00000000000000

  
    websocketd count example
    
  
  

    
websocketd-0.3.1/examples/java/000077500000000000000000000000001342360204300164015ustar00rootroot00000000000000websocketd-0.3.1/examples/java/Count/000077500000000000000000000000001342360204300174715ustar00rootroot00000000000000websocketd-0.3.1/examples/java/Count/Count.java000066400000000000000000000004651342360204300214310ustar00rootroot00000000000000public class Count { public static void main(String[] args) { for(int i = 1; i <= 10; i++) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }websocketd-0.3.1/examples/java/Count/count.sh000077500000000000000000000000451342360204300211570ustar00rootroot00000000000000#!/bin/sh javac Count.java java Countwebsocketd-0.3.1/examples/java/Echo/000077500000000000000000000000001342360204300172575ustar00rootroot00000000000000websocketd-0.3.1/examples/java/Echo/Echo.java000066400000000000000000000006341342360204300210030ustar00rootroot00000000000000import java.io.*; public class Echo { public static void main(String[] args) { while(true) { try { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String message = in.readLine(); System.out.println(message); } catch (IOException e) { e.printStackTrace(); } } } }websocketd-0.3.1/examples/java/Echo/echo.sh000077500000000000000000000000431342360204300205310ustar00rootroot00000000000000#!/bin/sh javac Echo.java java Echowebsocketd-0.3.1/examples/java/README.md000066400000000000000000000010041342360204300176530ustar00rootroot00000000000000## Running the examples on Mac 1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your PATH 2. Echo Server: Run `websocketd --port=8080 --devconsole ./echo.sh` and then go to http://localhost:8080 to interact with it 3. Count Server: Run `websocketd --port=8080 ./count.sh` to start the server, then go to [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browserwebsocketd-0.3.1/examples/lua/000077500000000000000000000000001342360204300162415ustar00rootroot00000000000000websocketd-0.3.1/examples/lua/README.md000066400000000000000000000025141342360204300175220ustar00rootroot00000000000000The examples demonstrate the use of websocketd with lua. There are two examples in the directory both very basic. 1. Greeter.lua simply echos back any input made from the client 2. json_ws.lua echos back any input from the client *after* converting it into a json string It is pretty simple to extend these examples into full fledged applications. All you need is an stdin input loop ``` local input = io.stdin:read() while input do -- do anything here -- update the input input = io.stdin:read() end ``` any thing you `print` goes out to the websocket client Libraries and third party modules can be used by the standard `require` statement in lua. ## Running the examples ##### 1. Download [Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`. ##### 2. Start a server: greeter Run `websocketd --port=8080 --devconsole lua ./greeter.lua` and then go to `http://localhost:8080` to interact with it ##### 3. Start a server: json_ws Run `websocketd --port=8080 --devconsole lua ./json_ws.lua` and then go to `http://localhost:8080` to interact with it If you are using luajit instead of lua you may run the examples like this (this assumes that you've got luajit in your path) `websocketd --port=8080 --devconsole luajit ./json_ws.lua` and then go to `http://localhost:8080`websocketd-0.3.1/examples/lua/greeter.lua000066400000000000000000000001621342360204300204000ustar00rootroot00000000000000local input = io.stdin:read() while input do print(input) io.stdout:flush() input = io.stdin:read() end websocketd-0.3.1/examples/lua/json.lua000066400000000000000000000213671342360204300177260ustar00rootroot00000000000000-- -- json.lua -- -- Copyright (c) 2015 rxi -- -- This library is free software; you can redistribute it and/or modify it -- under the terms of the MIT license. See LICENSE for details. -- local json = { _version = "0.1.0" } ------------------------------------------------------------------------------- -- Encode ------------------------------------------------------------------------------- local encode local escape_char_map = { [ "\\" ] = "\\\\", [ "\"" ] = "\\\"", [ "\b" ] = "\\b", [ "\f" ] = "\\f", [ "\n" ] = "\\n", [ "\r" ] = "\\r", [ "\t" ] = "\\t", } local escape_char_map_inv = { [ "\\/" ] = "/" } for k, v in pairs(escape_char_map) do escape_char_map_inv[v] = k end local function escape_char(c) return escape_char_map[c] or string.format("\\u%04x", c:byte()) end local function encode_nil(val) return "null" end local function encode_table(val, stack) local res = {} stack = stack or {} -- Circular reference? if stack[val] then error("circular reference") end stack[val] = true if val[1] ~= nil or next(val) == nil then -- Treat as array -- check keys are valid and it is not sparse local n = 0 for k in pairs(val) do if type(k) ~= "number" then error("invalid table: mixed or invalid key types") end n = n + 1 end if n ~= #val then error("invalid table: sparse array") end -- Encode for i, v in ipairs(val) do table.insert(res, encode(v, stack)) end stack[val] = nil return "[" .. table.concat(res, ",") .. "]" else -- Treat as an object for k, v in pairs(val) do if type(k) ~= "string" then error("invalid table: mixed or invalid key types") end table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) end stack[val] = nil return "{" .. table.concat(res, ",") .. "}" end end local function encode_string(val) return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' end local function encode_number(val) -- Check for NaN, -inf and inf if val ~= val or val <= -math.huge or val >= math.huge then error("unexpected number value '" .. tostring(val) .. "'") end return string.format("%.14g", val) end local type_func_map = { [ "nil" ] = encode_nil, [ "table" ] = encode_table, [ "string" ] = encode_string, [ "number" ] = encode_number, [ "boolean" ] = tostring, } encode = function(val, stack) local t = type(val) local f = type_func_map[t] if f then return f(val, stack) end error("unexpected type '" .. t .. "'") end function json.encode(val) return ( encode(val) ) end ------------------------------------------------------------------------------- -- Decode ------------------------------------------------------------------------------- local parse local function create_set(...) local res = {} for i = 1, select("#", ...) do res[ select(i, ...) ] = true end return res end local space_chars = create_set(" ", "\t", "\r", "\n") local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") local literals = create_set("true", "false", "null") local literal_map = { [ "true" ] = true, [ "false" ] = false, [ "null" ] = nil, } local function next_char(str, idx, set, negate) for i = idx, #str do if set[str:sub(i, i)] ~= negate then return i end end return #str + 1 end local function decode_error(str, idx, msg) local line_count = 1 local col_count = 1 for i = 1, idx - 1 do col_count = col_count + 1 if str:sub(i, i) == "\n" then line_count = line_count + 1 col_count = 1 end end error( string.format("%s at line %d col %d", msg, line_count, col_count) ) end local function codepoint_to_utf8(n) -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa local f = math.floor if n <= 0x7f then return string.char(n) elseif n <= 0x7ff then return string.char(f(n / 64) + 192, n % 64 + 128) elseif n <= 0xffff then return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) elseif n <= 0x10ffff then return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, f(n % 4096 / 64) + 128, n % 64 + 128) end error( string.format("invalid unicode codepoint '%x'", n) ) end local function parse_unicode_escape(s) local n1 = tonumber( s:sub(3, 6), 16 ) local n2 = tonumber( s:sub(9, 12), 16 ) -- Surrogate pair? if n2 then return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) else return codepoint_to_utf8(n1) end end local function parse_string(str, i) local has_unicode_escape = false local has_surrogate_escape = false local has_escape = false local last for j = i + 1, #str do local x = str:byte(j) if x < 32 then decode_error(str, j, "control character in string") end if last == 92 then -- "\\" (escape char) if x == 117 then -- "u" (unicode escape sequence) local hex = str:sub(j + 1, j + 5) if not hex:find("%x%x%x%x") then decode_error(str, j, "invalid unicode escape in string") end if hex:find("^[dD][89aAbB]") then has_surrogate_escape = true else has_unicode_escape = true end else local c = string.char(x) if not escape_chars[c] then decode_error(str, j, "invalid escape char '" .. c .. "' in string") end has_escape = true end last = nil elseif x == 34 then -- '"' (end of string) local s = str:sub(i + 1, j - 1) if has_surrogate_escape then s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) end if has_unicode_escape then s = s:gsub("\\u....", parse_unicode_escape) end if has_escape then s = s:gsub("\\.", escape_char_map_inv) end return s, j + 1 else last = x end end decode_error(str, i, "expected closing quote for string") end local function parse_number(str, i) local x = next_char(str, i, delim_chars) local s = str:sub(i, x - 1) local n = tonumber(s) if not n then decode_error(str, i, "invalid number '" .. s .. "'") end return n, x end local function parse_literal(str, i) local x = next_char(str, i, delim_chars) local word = str:sub(i, x - 1) if not literals[word] then decode_error(str, i, "invalid literal '" .. word .. "'") end return literal_map[word], x end local function parse_array(str, i) local res = {} local n = 1 i = i + 1 while 1 do local x i = next_char(str, i, space_chars, true) -- Empty / end of array? if str:sub(i, i) == "]" then i = i + 1 break end -- Read token x, i = parse(str, i) res[n] = x n = n + 1 -- Next token i = next_char(str, i, space_chars, true) local chr = str:sub(i, i) i = i + 1 if chr == "]" then break end if chr ~= "," then decode_error(str, i, "expected ']' or ','") end end return res, i end local function parse_object(str, i) local res = {} i = i + 1 while 1 do local key, val i = next_char(str, i, space_chars, true) -- Empty / end of object? if str:sub(i, i) == "}" then i = i + 1 break end -- Read key if str:sub(i, i) ~= '"' then decode_error(str, i, "expected string for key") end key, i = parse(str, i) -- Read ':' delimiter i = next_char(str, i, space_chars, true) if str:sub(i, i) ~= ":" then decode_error(str, i, "expected ':' after key") end i = next_char(str, i + 1, space_chars, true) -- Read value val, i = parse(str, i) -- Set res[key] = val -- Next token i = next_char(str, i, space_chars, true) local chr = str:sub(i, i) i = i + 1 if chr == "}" then break end if chr ~= "," then decode_error(str, i, "expected '}' or ','") end end return res, i end local char_func_map = { [ '"' ] = parse_string, [ "0" ] = parse_number, [ "1" ] = parse_number, [ "2" ] = parse_number, [ "3" ] = parse_number, [ "4" ] = parse_number, [ "5" ] = parse_number, [ "6" ] = parse_number, [ "7" ] = parse_number, [ "8" ] = parse_number, [ "9" ] = parse_number, [ "-" ] = parse_number, [ "t" ] = parse_literal, [ "f" ] = parse_literal, [ "n" ] = parse_literal, [ "[" ] = parse_array, [ "{" ] = parse_object, } parse = function(str, idx) local chr = str:sub(idx, idx) local f = char_func_map[chr] if f then return f(str, idx) end decode_error(str, idx, "unexpected character '" .. chr .. "'") end function json.decode(str) if type(str) ~= "string" then error("expected argument of type string, got " .. type(str)) end return ( parse(str, next_char(str, 1, space_chars, true)) ) end return json websocketd-0.3.1/examples/lua/json_ws.lua000066400000000000000000000002551342360204300204300ustar00rootroot00000000000000local input = io.stdin:read() local json = require("json") while input do print(json.encode({res="json",mess=input})) io.stdout:flush() input = io.stdin:read() end websocketd-0.3.1/examples/nodejs/000077500000000000000000000000001342360204300167425ustar00rootroot00000000000000websocketd-0.3.1/examples/nodejs/README.md000066400000000000000000000010701342360204300202170ustar00rootroot00000000000000## Running the examples on Mac ##### 1. Download [Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`. ##### 2. Start a server: getter Run `websocketd --port=8080 --devconsole ./getter.js` and then go to `http://localhost:8080` to interact with it ##### 3. Start a server: counter Run `websocketd --port=8080 ./counter.js` to start the server, then go to [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) directory and double click **count.html** to open in a browser websocketd-0.3.1/examples/nodejs/count.js000066400000000000000000000003421342360204300204270ustar00rootroot00000000000000(function(){ var counter = 0; var echo = function(){ if (counter === 10){ return; } setTimeout(function(){ counter++; echo(); process.stdout.write(counter.toString() + "\n"); }, 500); } echo(); })(); websocketd-0.3.1/examples/nodejs/greeter.js000066400000000000000000000004211342360204300207320ustar00rootroot00000000000000// from node.js sample // https://nodejs.org/api/process.html#process_process_stdin process.stdin.setEncoding('utf8'); process.stdin.on('readable', function() { var chunk = process.stdin.read(); if (chunk !== null) { process.stdout.write('data: ' + chunk); } });websocketd-0.3.1/examples/perl/000077500000000000000000000000001342360204300164225ustar00rootroot00000000000000websocketd-0.3.1/examples/perl/README.txt000066400000000000000000000002031342360204300201130ustar00rootroot00000000000000This examples directory shows some examples written in Perl. You can also test the command files by running from the command line.websocketd-0.3.1/examples/perl/count.pl000077500000000000000000000006411342360204300201130ustar00rootroot00000000000000#!/usr/bin/perl # 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. use strict; # Autoflush output use IO::Handle; STDOUT->autoflush(1); use Time::HiRes qw(sleep); # Simple example script that counts to 10 at ~2Hz, then stops. for my $count (1 .. 10) { print "$count\n"; sleep 0.5; } websocketd-0.3.1/examples/perl/dump-env.pl000077500000000000000000000016271342360204300205230ustar00rootroot00000000000000#!/usr/bin/perl # 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. use strict; # Autoflush output use IO::Handle; STDOUT->autoflush(1); # Standard CGI(ish) environment variables, as defined in # http://tools.ietf.org/html/rfc3875 my @names = qw( 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 HTTPS ); for my $name (@names) { my $value = $ENV{$name} || ''; print "$name=$value\n"; } # Additional HTTP headers for my $name (keys(%ENV)) { if ($name =~ /^HTTP_/) { my $value = $ENV{$name}; print "$name=$value\n"; } } websocketd-0.3.1/examples/perl/greeter.pl000077500000000000000000000006101342360204300204140ustar00rootroot00000000000000#!/usr/bin/perl # 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. use strict; # Autoflush output use IO::Handle; STDOUT->autoflush(1); # For each line FOO received on STDIN, respond with "Hello FOO!". while (<>) { chomp; # remove \n print "Hello $_!\n"; } websocketd-0.3.1/examples/php/000077500000000000000000000000001342360204300162475ustar00rootroot00000000000000websocketd-0.3.1/examples/php/README.txt000066400000000000000000000004201342360204300177410ustar00rootroot00000000000000This examples directory shows some examples written in PHP. This relies on the CLI verson of PHP being installed and in the path. See http://www.php.net/manual/en/features.commandline.introduction.php You can also test the command files by running from the command line.websocketd-0.3.1/examples/php/count.php000077500000000000000000000005441342360204300201160ustar00rootroot00000000000000#!/usr/bin/php websocketd-0.3.1/examples/php/dump-env.php000077500000000000000000000017141342360204300205210ustar00rootroot00000000000000#!/usr/bin/php '; echo $name . '=' . $value . "\n"; } // Additional HTTP headers foreach ($_SERVER as $name => $value) { if (strpos($name, 'HTTP_') === 0) { echo $name . '=' . $value . "\n"; } } websocketd-0.3.1/examples/php/greeter.php000077500000000000000000000005771342360204300204310ustar00rootroot00000000000000#!/usr/bin/php websocketd-0.3.1/examples/python/000077500000000000000000000000001342360204300170015ustar00rootroot00000000000000websocketd-0.3.1/examples/python/README.txt000066400000000000000000000002051342360204300204740ustar00rootroot00000000000000This examples directory shows some examples written in Python. You can also test the command files by running from the command line.websocketd-0.3.1/examples/python/count.py000077500000000000000000000006241342360204300205100ustar00rootroot00000000000000#!/usr/bin/python # 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. from sys import stdout from time import sleep # Simple example script that counts to 10 at ~2Hz, then stops. for count in range(0, 10): print(count + 1) stdout.flush() # Remember to flush sleep(0.5) websocketd-0.3.1/examples/python/dump-env.py000077500000000000000000000020031342360204300211040ustar00rootroot00000000000000#!/usr/bin/python # 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. import os from sys import stdout # Standard CGI(ish) environment variables, as defined in # http://tools.ietf.org/html/rfc3875 var_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', 'HTTPS' ] for var_name in var_names: print('%s=%s' % (var_name, os.environ.get(var_name, ''))) stdout.flush() # Remember to flush # Additional HTTP headers for var_name in os.environ: if var_name.startswith('HTTP_'): print('%s=%s' % (var_name, os.environ[var_name])) stdout.flush() # Remember to flush websocketd-0.3.1/examples/python/greeter.py000077500000000000000000000006261342360204300210170ustar00rootroot00000000000000#!/usr/bin/python # 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. from sys import stdin, stdout # For each line FOO received on STDIN, respond with "Hello FOO!". while True: line = stdin.readline().strip() print('Hello %s!' % line) stdout.flush() # Remember to flush websocketd-0.3.1/examples/ruby/000077500000000000000000000000001342360204300164415ustar00rootroot00000000000000websocketd-0.3.1/examples/ruby/README.txt000066400000000000000000000002031342360204300201320ustar00rootroot00000000000000This examples directory shows some examples written in Ruby. You can also test the command files by running from the command line.websocketd-0.3.1/examples/ruby/count.rb000077500000000000000000000005371342360204300201260ustar00rootroot00000000000000#!/usr/bin/ruby # 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. # Autoflush output STDOUT.sync = true # Simple example script that counts to 10 at ~2Hz, then stops. (1..10).each do |count| puts count sleep(0.5) end websocketd-0.3.1/examples/ruby/dump-env.rb000077500000000000000000000016151342360204300205270ustar00rootroot00000000000000#!/usr/bin/ruby # 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. # Autoflush output STDOUT.sync = true # 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', 'HTTPS', ] names.each do |name| value = ENV[name] || '' puts "#{name}=#{value}" end # Additional HTTP headers ENV.each do |name,value| puts "#{name}=#{value}" if name.start_with?('HTTP_') end websocketd-0.3.1/examples/ruby/greeter.rb000077500000000000000000000005571342360204300204350ustar00rootroot00000000000000#!/usr/bin/ruby # 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. # Autoflush output STDOUT.sync = true # For each line FOO received on STDIN, respond with "Hello FOO!". while 1 line = STDIN.readline.strip puts "Hello #{line}!" end websocketd-0.3.1/examples/swift/000077500000000000000000000000001342360204300166145ustar00rootroot00000000000000websocketd-0.3.1/examples/swift/README.md000066400000000000000000000005371342360204300201000ustar00rootroot00000000000000## Swift examples ### Count Run the following line and open "html/count.html" from the websocketd examples directory. ``` $ websocketd --port=8080 count.swift ``` ### Greeter Run the following line and open "http://localhost:8080" in your browser to interact with the greeter server. ``` $ websocketd --port=8080 --devconsole greeter.swift ``` websocketd-0.3.1/examples/swift/count.swift000077500000000000000000000002561342360204300210300ustar00rootroot00000000000000#!/usr/bin/env xcrun -sdk macosx swift import AppKit for index in 1...10 { print(index) // Flush output fflush(__stdoutp) NSThread.sleepForTimeInterval(0.5) }websocketd-0.3.1/examples/swift/greeter.swift000077500000000000000000000005351342360204300213350ustar00rootroot00000000000000#!/usr/bin/env xcrun -sdk macosx swift import Foundation while(true){ var stdin = NSFileHandle.fileHandleWithStandardInput().availableData var line = NSString(data: stdin, encoding: NSUTF8StringEncoding)! var name = line.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) print("Hello \(name)!") fflush(__stdoutp) } websocketd-0.3.1/examples/windows-jscript/000077500000000000000000000000001342360204300206265ustar00rootroot00000000000000websocketd-0.3.1/examples/windows-jscript/README.txt000066400000000000000000000005501342360204300223240ustar00rootroot00000000000000This examples directory shows some examples written in JScript that can be run using Windows Script Hosting. Note that each .js file, also requires a .cmd file to launch it. The WebSocket should connect to ws://..../[example].cmd. http://en.wikipedia.org/wiki/Windows_Script_Host You can also test the command files by running from the command line.websocketd-0.3.1/examples/windows-jscript/count.cmd000066400000000000000000000000531342360204300224410ustar00rootroot00000000000000@echo off cscript /nologo %0\..\count.js websocketd-0.3.1/examples/windows-jscript/count.js000066400000000000000000000005141342360204300223140ustar00rootroot00000000000000// 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. // Simple example script that counts to 10 at ~2Hz, then stops. for (var i = 1; i <= 10; i++) { WScript.echo(i); WScript.sleep(500); } websocketd-0.3.1/examples/windows-jscript/dump-env.cmd000066400000000000000000000000561342360204300230470ustar00rootroot00000000000000@echo off cscript /nologo %0\..\dump-env.js websocketd-0.3.1/examples/windows-jscript/dump-env.js000066400000000000000000000021151342360204300227160ustar00rootroot00000000000000// 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 var 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', 'HTTPS' ]; var shell = WScript.CreateObject("WScript.Shell"); var env = shell.Environment('PROCESS'); for (var i = 0; i < names.length; i++) { var name = names[i]; var value = env(name) || ''; WScript.echo(name + '=' + value); } for(var en = new Enumerator(env); !en.atEnd(); en.moveNext()) { var item = en.item(); if (item.indexOf('HTTP_') == 0) { WScript.Echo(item); } }websocketd-0.3.1/examples/windows-jscript/greeter.cmd000066400000000000000000000000551342360204300227500ustar00rootroot00000000000000@echo off cscript /nologo %0\..\greeter.js websocketd-0.3.1/examples/windows-jscript/greeter.js000066400000000000000000000005451342360204300226250ustar00rootroot00000000000000// 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. // For each line FOO received on STDIN, respond with "Hello FOO!". while (true) { var input= WScript.stdIn.readLine(); WScript.echo('Hello ' + input+ '!'); } websocketd-0.3.1/examples/windows-vbscript/000077500000000000000000000000001342360204300210045ustar00rootroot00000000000000websocketd-0.3.1/examples/windows-vbscript/README.txt000066400000000000000000000005521342360204300225040ustar00rootroot00000000000000This examples directory shows some examples written in VBScript that can be run using Windows Script Hosting. Note that each .vbs file, also requires a .cmd file to launch it. The WebSocket should connect to ws://..../[example].cmd. http://en.wikipedia.org/wiki/Windows_Script_Host You can also test the command files by running from the command line.websocketd-0.3.1/examples/windows-vbscript/count.cmd000066400000000000000000000000541342360204300226200ustar00rootroot00000000000000@echo off cscript /nologo %0\..\count.vbs websocketd-0.3.1/examples/windows-vbscript/count.vbs000066400000000000000000000004701342360204300226510ustar00rootroot00000000000000' 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. ' Simple example script that counts to 10 at ~2Hz, then stops. for i = 1 to 10 WScript.echo i WScript.sleep 500 next websocketd-0.3.1/examples/windows-vbscript/dump-env.cmd000066400000000000000000000000571342360204300232260ustar00rootroot00000000000000@echo off cscript /nologo %0\..\dump-env.vbs websocketd-0.3.1/examples/windows-vbscript/dump-env.vbs000066400000000000000000000020641342360204300232550ustar00rootroot00000000000000' 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 = Array(_ "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", _ "HTTPS"_ ) set shell = WScript.CreateObject("WScript.Shell") set env = shell.Environment("PROCESS") for each name in names value = env(name) if value = "" then value = "" end if WScript.echo name & "=" & value next for each item in env if instr(1, item, "HTTP_", 1) = 1 then WScript.Echo item end if next websocketd-0.3.1/examples/windows-vbscript/greeter.cmd000066400000000000000000000000561342360204300231270ustar00rootroot00000000000000@echo off cscript /nologo %0\..\greeter.vbs websocketd-0.3.1/examples/windows-vbscript/greeter.vbs000066400000000000000000000005261342360204300231600ustar00rootroot00000000000000' 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. ' For each line FOO received on STDIN, respond with "Hello FOO!". while true line = WScript.stdIn.readLine WScript.echo "Hello " & line & "!" wend websocketd-0.3.1/go.mod000066400000000000000000000001241342360204300147450ustar00rootroot00000000000000module github.com/joewalnes/websocketd require github.com/gorilla/websocket v1.4.0 websocketd-0.3.1/go.sum000066400000000000000000000002571342360204300150010ustar00rootroot00000000000000github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= websocketd-0.3.1/help.go000066400000000000000000000147671342360204300151400ustar00rootroot00000000000000// 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 main import ( "fmt" "os" "path/filepath" "strings" ) const ( help = ` {{binary}} ({{version}}) {{binary}} is a command line tool that will allow any executable program that accepts input on stdin and produces output on stdout to be turned into a WebSocket server. Usage: Export a single executable program a WebSocket server: {{binary}} [options] COMMAND [command args] Or, export an entire directory of executables as WebSocket endpoints: {{binary}} [options] --dir=SOMEDIR Options: --port=PORT HTTP port to listen on. --address=ADDRESS Address to bind to (multiple options allowed) Use square brackets to specify IPv6 address. Default: "" (all) --sameorigin={true,false} Restrict (HTTP 403) protocol upgrades if the Origin header does not match to requested HTTP Host. Default: false. --origin=host[:port][,host[:port]...] Restrict (HTTP 403) protocol upgrades if the Origin header does not match to one of the host and port combinations listed. If the port is not specified, any port number will match. Default: "" (allow any origin) --ssl Listen for HTTPS socket instead of HTTP. --sslcert=FILE All three options must be used or all of --sslkey=FILE them should be omitted. --redirport=PORT Open alternative port and redirect HTTP traffic from it to canonical address (mostly useful for HTTPS-only configurations to redirect HTTP traffic) --passenv VAR[,VAR...] Lists environment variables allowed to be passed to executed scripts. Does not work for Windows since all the variables are kept there. --binary={true,false} Switches communication to binary, process reads send to browser as blobs and all reads from the browser are immediately flushed to the process. Default: false --reverselookup={true,false} Perform DNS reverse lookups on remote clients. Default: false --dir=DIR Allow all scripts in the local directory to be accessed as WebSockets. If using this, option, then the standard program and args options should not be specified. --staticdir=DIR Serve static files in this directory over HTTP. --cgidir=DIR Serve CGI scripts in this directory over HTTP. --maxforks=N Limit number of processes that websocketd is able to execute with WS and CGI handlers. When maxforks reached the server will be rejecting requests that require executing another process (unlimited when 0 or negative). Default: 0 --closems=milliseconds Specifies additional time process needs to gracefully finish before websocketd will send termination signals to it. Default: 0 (signals sent after 100ms, 250ms, and 500ms of waiting) --header="..." Set custom HTTP header to each answer. For example: --header="Server: someserver/0.0.1" --header-ws="...." Same as --header, just applies to only those responses that indicate upgrade of TCP connection to a WebSockets protocol. --header-http="...." Same as --header, just applies to only to plain HTTP responses that do not indicate WebSockets upgrade --help Print help and exit. --version Print version and exit. --license Print license and exit. --devconsole Enable interactive development console. This enables you to access the websocketd server with a web-browser and use a user interface to quickly test WebSocket endpoints. For example, to test an endpoint at ws://[host]/foo, you can visit http://[host]/foo in your browser. This flag cannot be used in conjunction with --staticdir or --cgidir. --loglevel=LEVEL Log level to use (default access). From most to least verbose: debug, trace, access, info, error, fatal Full documentation at http://websocketd.com/ Copyright 2013 Joe Walnes and the websocketd team. All rights reserved. BSD license: Run '{{binary}} --license' for details. ` short = ` Usage: Export a single executable program a WebSocket server: {{binary}} [options] COMMAND [command args] Or, export an entire directory of executables as WebSocket endpoints: {{binary}} [options] --dir=SOMEDIR Or, show extended help message using: {{binary}} --help ` ) func get_help_message(content string) string { msg := strings.Trim(content, " \n") msg = strings.Replace(msg, "{{binary}}", HelpProcessName(), -1) return strings.Replace(msg, "{{version}}", Version(), -1) } func HelpProcessName() string { binary := os.Args[0] if strings.Contains(binary, "/go-build") { // this was run using "go run", let's use something appropriate binary = "websocketd" } else { binary = filepath.Base(binary) } return binary } func PrintHelp() { fmt.Fprintf(os.Stderr, "%s\n", get_help_message(help)) } func ShortHelp() { // Shown after some error fmt.Fprintf(os.Stderr, "\n%s\n", get_help_message(short)) } websocketd-0.3.1/libwebsocketd/000077500000000000000000000000001342360204300164635ustar00rootroot00000000000000websocketd-0.3.1/libwebsocketd/config.go000066400000000000000000000035231342360204300202620ustar00rootroot00000000000000// 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 { // base initiaization fields StartupTime time.Time // Server startup time (used for dev console caching). CommandName string // Command to execute. CommandArgs []string // Additional args to pass to command. ServerSoftware string // Value to pass to SERVER_SOFTWARE environment variable (e.g. websocketd/1.2.3). CloseMs uint // Milliseconds to start sending signals HandshakeTimeout time.Duration // time to finish handshake (default 1500ms) // settings Binary bool // Use binary communication (send data in chunks they are read from process) ReverseLookup bool // Perform reverse DNS lookups on hostnames (useful, but slower). Ssl bool // websocketd works with --ssl which means TLS is in use ScriptDir string // Base directory for websocket scripts. UsingScriptDir bool // Are we running with a script dir. StaticDir string // If set, static files will be served from this dir over HTTP. CgiDir string // If set, CGI scripts will be served from this dir over HTTP. DevConsole bool // Enable dev console. This disables StaticDir and CgiDir. AllowOrigins []string // List of allowed origin addresses for websocket upgrade. SameOrigin bool // If set, requires websocket upgrades to be performed from same origin only. Headers []string HeadersWs []string HeadersHTTP []string // created environment Env []string // Additional environment variables to pass to process ("key=value"). ParentEnv []string // Variables kept from os.Environ() before sanitizing it for subprocess. } websocketd-0.3.1/libwebsocketd/console.go000066400000000000000000000177031342360204300204640ustar00rootroot00000000000000// 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 // Although this isn't particularly elegant, it's the simplest // way to embed the console content into the binary. // Note that the console is served by a single HTML file containing // all CSS and JS inline. // We can get by without jQuery or Bootstrap for this one ;). const ( defaultConsoleContent = ` websocketd console
send »
` ) var ConsoleContent = defaultConsoleContent websocketd-0.3.1/libwebsocketd/endpoint.go000066400000000000000000000011551342360204300206340ustar00rootroot00000000000000// 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 type Endpoint interface { StartReading() Terminate() Output() chan []byte Send([]byte) bool } func PipeEndpoints(e1, e2 Endpoint) { e1.StartReading() e2.StartReading() defer e1.Terminate() defer e2.Terminate() for { select { case msgOne, ok := <-e1.Output(): if !ok || !e2.Send(msgOne) { return } case msgTwo, ok := <-e2.Output(): if !ok || !e1.Send(msgTwo) { return } } } } websocketd-0.3.1/libwebsocketd/endpoint_test.go000066400000000000000000000033251342360204300216740ustar00rootroot00000000000000package libwebsocketd import ( "strconv" "testing" "time" ) var eol_tests = []string{ "", "\n", "\r\n", "ok\n", "ok\n", "quite long string for our test\n", "quite long string for our test\r\n", } var eol_answers = []string{ "", "", "", "ok", "ok", "quite long string for our test", "quite long string for our test", } func TestTrimEOL(t *testing.T) { for n := 0; n < len(eol_tests); n++ { answ := trimEOL([]byte(eol_tests[n])) if string(answ) != eol_answers[n] { t.Errorf("Answer '%s' did not match predicted '%s'", answ, eol_answers[n]) } } } func BenchmarkTrimEOL(b *testing.B) { for n := 0; n < b.N; n++ { trimEOL([]byte(eol_tests[n%len(eol_tests)])) } } type TestEndpoint struct { limit int prefix string c chan []byte result []string } func (e *TestEndpoint) StartReading() { go func() { for i := 0; i < e.limit; i++ { e.c <- []byte(e.prefix + strconv.Itoa(i)) } time.Sleep(time.Millisecond) // should be enough for smaller channel to catch up with long one close(e.c) }() } func (e *TestEndpoint) Terminate() { } func (e *TestEndpoint) Output() chan []byte { return e.c } func (e *TestEndpoint) Send(msg []byte) bool { e.result = append(e.result, string(msg)) return true } func TestEndpointPipe(t *testing.T) { one := &TestEndpoint{2, "one:", make(chan []byte), make([]string, 0)} two := &TestEndpoint{4, "two:", make(chan []byte), make([]string, 0)} PipeEndpoints(one, two) if len(one.result) != 4 || len(two.result) != 2 { t.Errorf("Invalid lengths, should be 4 and 2: %v %v", one.result, two.result) } else if one.result[0] != "two:0" || two.result[0] != "one:0" { t.Errorf("Invalid first results, should be two:0 and one:0: %#v %#v", one.result[0], two.result[0]) } } websocketd-0.3.1/libwebsocketd/env.go000066400000000000000000000100361342360204300176020ustar00rootroot00000000000000// 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" "strings" ) const ( gatewayInterface = "websocketd-CGI/0.1" ) var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") var headerDashToUnderscore = strings.NewReplacer("-", "_") func createEnv(handler *WebsocketdHandler, req *http.Request, log *LogScope) []string { headers := req.Header url := req.URL serverName, serverPort, err := tellHostPort(req.Host, handler.server.Config.Ssl) if err != nil { // This does mean that we cannot detect port from Host: header... Just keep going with "", guessing is bad. log.Debug("env", "Host port detection error: %s", err) serverPort = "" } standardEnvCount := 20 if handler.server.Config.Ssl { standardEnvCount += 1 } parentLen := len(handler.server.Config.ParentEnv) env := make([]string, 0, len(headers)+standardEnvCount+parentLen+len(handler.server.Config.Env)) // This variable could be rewritten from outside env = appendEnv(env, "SERVER_SOFTWARE", handler.server.Config.ServerSoftware) parentStarts := len(env) for _, v := range handler.server.Config.ParentEnv { env = append(env, v) } // IMPORTANT ---> Adding a header? Make sure standardEnvCount (above) is up to date. // Standard CGI specification headers. // As defined in http://tools.ietf.org/html/rfc3875 env = appendEnv(env, "REMOTE_ADDR", handler.RemoteInfo.Addr) env = appendEnv(env, "REMOTE_HOST", handler.RemoteInfo.Host) env = appendEnv(env, "SERVER_NAME", serverName) env = appendEnv(env, "SERVER_PORT", serverPort) env = appendEnv(env, "SERVER_PROTOCOL", req.Proto) env = appendEnv(env, "GATEWAY_INTERFACE", gatewayInterface) env = appendEnv(env, "REQUEST_METHOD", req.Method) env = appendEnv(env, "SCRIPT_NAME", handler.URLInfo.ScriptPath) env = appendEnv(env, "PATH_INFO", handler.URLInfo.PathInfo) env = appendEnv(env, "PATH_TRANSLATED", url.Path) env = appendEnv(env, "QUERY_STRING", url.RawQuery) // Not supported, but we explicitly clear them so we don't get leaks from parent environment. env = appendEnv(env, "AUTH_TYPE", "") env = appendEnv(env, "CONTENT_LENGTH", "") env = appendEnv(env, "CONTENT_TYPE", "") env = appendEnv(env, "REMOTE_IDENT", "") env = appendEnv(env, "REMOTE_USER", "") // Non standard, but commonly used headers. env = appendEnv(env, "UNIQUE_ID", handler.Id) // Based on Apache mod_unique_id. env = appendEnv(env, "REMOTE_PORT", handler.RemoteInfo.Port) env = appendEnv(env, "REQUEST_URI", url.RequestURI()) // e.g. /foo/blah?a=b // The following variables are part of the CGI specification, but are optional // and not set by websocketd: // // AUTH_TYPE, REMOTE_USER, REMOTE_IDENT // -- Authentication left to the underlying programs. // // CONTENT_LENGTH, CONTENT_TYPE // -- makes no sense for WebSocket connections. // // SSL_* // -- SSL variables are not supported, HTTPS=on added for websocketd running with --ssl if handler.server.Config.Ssl { env = appendEnv(env, "HTTPS", "on") } if log.MinLevel == LogDebug { for i, v := range env { if i >= parentStarts && i < parentLen+parentStarts { log.Debug("env", "Parent envvar: %v", v) } else { log.Debug("env", "Std. variable: %v", v) } } } for k, hdrs := range headers { header := fmt.Sprintf("HTTP_%s", headerDashToUnderscore.Replace(k)) env = appendEnv(env, header, hdrs...) log.Debug("env", "Header variable %s", env[len(env)-1]) } for _, v := range handler.server.Config.Env { env = append(env, v) log.Debug("env", "External variable: %s", v) } return env } // Adapted from net/http/header.go func appendEnv(env []string, k string, v ...string) []string { if len(v) == 0 { return env } vCleaned := make([]string, 0, len(v)) for _, val := range v { vCleaned = append(vCleaned, strings.TrimSpace(headerNewlineToSpace.Replace(val))) } return append(env, fmt.Sprintf("%s=%s", strings.ToUpper(k), strings.Join(vCleaned, ", "))) } websocketd-0.3.1/libwebsocketd/handler.go000066400000000000000000000101601342360204300204250ustar00rootroot00000000000000package libwebsocketd import ( "errors" "fmt" "net" "net/http" "os" "path/filepath" "strconv" "strings" "time" "github.com/gorilla/websocket" ) var ScriptNotFoundError = errors.New("script not found") // WebsocketdHandler is a single request information and processing structure, it handles WS requests out of all that daemon can handle (static, cgi, devconsole) type WebsocketdHandler struct { server *WebsocketdServer Id string *RemoteInfo *URLInfo // TODO: I cannot find where it's used except in one single place as URLInfo.FilePath Env []string command string } // NewWebsocketdHandler constructs the struct and parses all required things in it... func NewWebsocketdHandler(s *WebsocketdServer, req *http.Request, log *LogScope) (wsh *WebsocketdHandler, err error) { wsh = &WebsocketdHandler{server: s, Id: generateId()} log.Associate("id", wsh.Id) wsh.RemoteInfo, err = GetRemoteInfo(req.RemoteAddr, s.Config.ReverseLookup) if err != nil { log.Error("session", "Could not understand remote address '%s': %s", req.RemoteAddr, err) return nil, err } log.Associate("remote", wsh.RemoteInfo.Host) wsh.URLInfo, err = GetURLInfo(req.URL.Path, s.Config) if err != nil { log.Access("session", "NOT FOUND: %s", err) return nil, err } wsh.command = s.Config.CommandName if s.Config.UsingScriptDir { wsh.command = wsh.URLInfo.FilePath } log.Associate("command", wsh.command) wsh.Env = createEnv(wsh, req, log) return wsh, nil } func (wsh *WebsocketdHandler) accept(ws *websocket.Conn, log *LogScope) { defer ws.Close() log.Access("session", "CONNECT") defer log.Access("session", "DISCONNECT") launched, err := launchCmd(wsh.command, wsh.server.Config.CommandArgs, wsh.Env) if err != nil { log.Error("process", "Could not launch process %s %s (%s)", wsh.command, strings.Join(wsh.server.Config.CommandArgs, " "), err) return } log.Associate("pid", strconv.Itoa(launched.cmd.Process.Pid)) binary := wsh.server.Config.Binary process := NewProcessEndpoint(launched, binary, log) if cms := wsh.server.Config.CloseMs; cms != 0 { process.closetime += time.Duration(cms) * time.Millisecond } wsEndpoint := NewWebSocketEndpoint(ws, binary, log) PipeEndpoints(process, wsEndpoint) } // RemoteInfo holds information about remote http client type RemoteInfo struct { Addr, Host, Port string } // GetRemoteInfo creates RemoteInfo structure and fills its fields appropriately func GetRemoteInfo(remote string, doLookup bool) (*RemoteInfo, error) { addr, port, err := net.SplitHostPort(remote) if err != nil { return nil, err } var host string if doLookup { hosts, err := net.LookupAddr(addr) if err != nil || len(hosts) == 0 { host = addr } else { host = hosts[0] } } else { host = addr } return &RemoteInfo{Addr: addr, Host: host, Port: port}, nil } // URLInfo - structure carrying information about current request and it's mapping to filesystem type URLInfo struct { ScriptPath string PathInfo string FilePath string } // GetURLInfo is a function that parses path and provides URL info according to libwebsocketd.Config fields func GetURLInfo(path string, config *Config) (*URLInfo, error) { if !config.UsingScriptDir { return &URLInfo{"/", path, ""}, nil } parts := strings.Split(path[1:], "/") urlInfo := &URLInfo{} for i, part := range parts { urlInfo.ScriptPath = strings.Join([]string{urlInfo.ScriptPath, part}, "/") urlInfo.FilePath = filepath.Join(config.ScriptDir, urlInfo.ScriptPath) isLastPart := i == len(parts)-1 statInfo, err := os.Stat(urlInfo.FilePath) // not a valid path if err != nil { return nil, ScriptNotFoundError } // at the end of url but is a dir if isLastPart && statInfo.IsDir() { return nil, ScriptNotFoundError } // we've hit a dir, carry on looking if statInfo.IsDir() { continue } // no extra args if isLastPart { return urlInfo, nil } // build path info from extra parts of url urlInfo.PathInfo = "/" + strings.Join(parts[i+1:], "/") return urlInfo, nil } panic(fmt.Sprintf("GetURLInfo cannot parse path %#v", path)) } func generateId() string { return strconv.FormatInt(time.Now().UnixNano(), 10) } websocketd-0.3.1/libwebsocketd/handler_test.go000066400000000000000000000042411342360204300214670ustar00rootroot00000000000000// 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 ( "io/ioutil" "os" "path/filepath" "testing" ) func TestParsePathWithScriptDir(t *testing.T) { baseDir, _ := ioutil.TempDir("", "websockets") scriptDir := filepath.Join(baseDir, "foo", "bar") scriptPath := filepath.Join(scriptDir, "baz.sh") defer os.RemoveAll(baseDir) if err := os.MkdirAll(scriptDir, os.ModePerm); err != nil { t.Error("could not create ", scriptDir) } if _, err := os.Create(scriptPath); err != nil { t.Error("could not create ", scriptPath) } config := new(Config) config.UsingScriptDir = true config.ScriptDir = baseDir var res *URLInfo var err error // simple url res, err = GetURLInfo("/foo/bar/baz.sh", config) if err != nil { t.Error(err) } if res.ScriptPath != "/foo/bar/baz.sh" { t.Error("scriptPath") } if res.PathInfo != "" { t.Error("GetURLInfo") } if res.FilePath != scriptPath { t.Error("filePath") } // url with extra path info res, err = GetURLInfo("/foo/bar/baz.sh/some/extra/stuff", config) if err != nil { t.Error(err) } if res.ScriptPath != "/foo/bar/baz.sh" { t.Error("scriptPath") } if res.PathInfo != "/some/extra/stuff" { t.Error("GetURLInfo") } if res.FilePath != scriptPath { t.Error("filePath") } // non-existing file _, err = GetURLInfo("/foo/bar/bang.sh", config) if err == nil { t.Error("non-existing file should fail") } if err != ScriptNotFoundError { t.Error("should fail with script not found") } // non-existing dir _, err = GetURLInfo("/hoohar/bang.sh", config) if err == nil { t.Error("non-existing dir should fail") } if err != ScriptNotFoundError { t.Error("should fail with script not found") } } func TestParsePathExplicitScript(t *testing.T) { config := new(Config) config.UsingScriptDir = false res, err := GetURLInfo("/some/path", config) if err != nil { t.Error(err) } if res.ScriptPath != "/" { t.Error("scriptPath") } if res.PathInfo != "/some/path" { t.Error("GetURLInfo") } if res.FilePath != "" { t.Error("filePath") } } websocketd-0.3.1/libwebsocketd/http.go000066400000000000000000000225261342360204300200000ustar00rootroot00000000000000// 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 ( "errors" "fmt" "net" "net/http" "net/http/cgi" "net/textproto" "net/url" "os" "path" "path/filepath" "regexp" "strings" "github.com/gorilla/websocket" ) var ForkNotAllowedError = errors.New("too many forks active") // WebsocketdServer presents http.Handler interface for requests libwebsocketd is handling. type WebsocketdServer struct { Config *Config Log *LogScope forks chan byte } // NewWebsocketdServer creates WebsocketdServer struct with pre-determined config, logscope and maxforks limit func NewWebsocketdServer(config *Config, log *LogScope, maxforks int) *WebsocketdServer { mux := &WebsocketdServer{ Config: config, Log: log, } if maxforks > 0 { mux.forks = make(chan byte, maxforks) } return mux } func splitMimeHeader(s string) (string, string) { p := strings.IndexByte(s, ':') if p < 0 { return s, "" } key := textproto.CanonicalMIMEHeaderKey(s[:p]) for p = p + 1; p < len(s); p++ { if s[p] != ' ' { break } } return key, s[p:] } func pushHeaders(h http.Header, hdrs []string) { for _, hstr := range hdrs { h.Add(splitMimeHeader(hstr)) } } // ServeHTTP muxes between WebSocket handler, CGI handler, DevConsole, Static HTML or 404. func (h *WebsocketdServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { log := h.Log.NewLevel(h.Log.LogFunc) log.Associate("url", h.TellURL("http", req.Host, req.RequestURI)) if h.Config.CommandName != "" || h.Config.UsingScriptDir { hdrs := req.Header upgradeRe := regexp.MustCompile("(?i)(^|[,\\s])Upgrade($|[,\\s])") // WebSocket, limited to size of h.forks if strings.ToLower(hdrs.Get("Upgrade")) == "websocket" && upgradeRe.MatchString(hdrs.Get("Connection")) { if h.noteForkCreated() == nil { defer h.noteForkCompled() // start figuring out if we even need to upgrade handler, err := NewWebsocketdHandler(h, req, log) if err != nil { if err == ScriptNotFoundError { log.Access("session", "NOT FOUND: %s", err) http.Error(w, "404 Not Found", 404) } else { log.Access("session", "INTERNAL ERROR: %s", err) http.Error(w, "500 Internal Server Error", 500) } return } var headers http.Header if len(h.Config.Headers)+len(h.Config.HeadersWs) > 0 { headers = http.Header(make(map[string][]string)) pushHeaders(headers, h.Config.Headers) pushHeaders(headers, h.Config.HeadersWs) } upgrader := &websocket.Upgrader{ HandshakeTimeout: h.Config.HandshakeTimeout, CheckOrigin: func(r *http.Request) bool { // backporting previous checkorigin for use in gorilla/websocket for now err := checkOrigin(req, h.Config, log) return err == nil }, } conn, err := upgrader.Upgrade(w, req, headers) if err != nil { log.Access("session", "Unable to Upgrade: %s", err) http.Error(w, "500 Internal Error", 500) return } // old func was used in x/net/websocket style, we reuse it here for gorilla/websocket handler.accept(conn, log) return } else { log.Error("http", "Max of possible forks already active, upgrade rejected") http.Error(w, "429 Too Many Requests", 429) } return } } pushHeaders(w.Header(), h.Config.HeadersHTTP) // Dev console (if enabled) if h.Config.DevConsole { log.Access("http", "DEVCONSOLE") content := ConsoleContent content = strings.Replace(content, "{{license}}", License, -1) content = strings.Replace(content, "{{addr}}", h.TellURL("ws", req.Host, req.RequestURI), -1) http.ServeContent(w, req, ".html", h.Config.StartupTime, strings.NewReader(content)) return } // CGI scripts, limited to size of h.forks 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() { log.Associate("cgiscript", filePath) if h.noteForkCreated() == nil { defer h.noteForkCompled() // Make variables to supplement cgi... Environ it uses will show empty list. envlen := len(h.Config.ParentEnv) cgienv := make([]string, envlen+1) if envlen > 0 { copy(cgienv, h.Config.ParentEnv) } cgienv[envlen] = "SERVER_SOFTWARE=" + h.Config.ServerSoftware cgiHandler := &cgi.Handler{ Path: filePath, Env: []string{ "SERVER_SOFTWARE=" + h.Config.ServerSoftware, }, } log.Access("http", "CGI") cgiHandler.ServeHTTP(w, req) } else { log.Error("http", "Fork not allowed since maxforks amount has been reached. CGI was not run.") http.Error(w, "429 Too Many Requests", 429) } return } } // Static files if h.Config.StaticDir != "" { handler := http.FileServer(http.Dir(h.Config.StaticDir)) log.Access("http", "STATIC") handler.ServeHTTP(w, req) return } // 404 log.Access("http", "NOT FOUND") http.NotFound(w, req) } var canonicalHostname string // TellURL is a helper function that changes http to https or ws to wss in case if SSL is used func (h *WebsocketdServer) TellURL(scheme, host, path string) string { if len(host) > 0 && host[0] == ':' { if canonicalHostname == "" { var err error canonicalHostname, err = os.Hostname() if err != nil { canonicalHostname = "UNKNOWN" } } host = canonicalHostname + host } if h.Config.Ssl { return scheme + "s://" + host + path } return scheme + "://" + host + path } func (h *WebsocketdServer) noteForkCreated() error { // note that forks can be nil since the construct could've been created by // someone who is not using NewWebsocketdServer if h.forks != nil { select { case h.forks <- 1: return nil default: return ForkNotAllowedError } } else { return nil } } func (h *WebsocketdServer) noteForkCompled() { if h.forks != nil { // see comment in noteForkCreated select { case <-h.forks: return default: // This could only happen if the completion handler called more times than creation handler above // Code should be audited to not allow this to happen, it's desired to have test that would // make sure this is impossible but it is not exist yet. panic("Cannot deplet number of allowed forks, something is not right in code!") } } return } func checkOrigin(req *http.Request, config *Config, log *LogScope) (err error) { // CONVERT GORILLA: // this is origin checking function, it's called from wshandshake which is from ServeHTTP main handler // should be trivial to reuse in gorilla's upgrader.CheckOrigin function. // Only difference is to parse request and fetching passed Origin header out of it instead of using // pre-parsed wsconf.Origin // check for origin to be correct in future // handshaker triggers answering with 403 if error was returned // We keep behavior of original handshaker that populates this field origin := req.Header.Get("Origin") if origin == "" || (origin == "null" && config.AllowOrigins == nil) { // we don't want to trust string "null" if there is any // enforcements are active origin = "file:" } originParsed, err := url.ParseRequestURI(origin) if err != nil { log.Access("session", "Origin parsing error: %s", err) return err } log.Associate("origin", originParsed.String()) // If some origin restrictions are present: if config.SameOrigin || config.AllowOrigins != nil { originServer, originPort, err := tellHostPort(originParsed.Host, originParsed.Scheme == "https") if err != nil { log.Access("session", "Origin hostname parsing error: %s", err) return err } if config.SameOrigin { localServer, localPort, err := tellHostPort(req.Host, req.TLS != nil) if err != nil { log.Access("session", "Request hostname parsing error: %s", err) return err } if originServer != localServer || originPort != localPort { log.Access("session", "Same origin policy mismatch") return fmt.Errorf("same origin policy violated") } } if config.AllowOrigins != nil { matchFound := false for _, allowed := range config.AllowOrigins { if pos := strings.Index(allowed, "://"); pos > 0 { // allowed schema has to match allowedURL, err := url.Parse(allowed) if err != nil { continue // pass bad URLs in origin list } if allowedURL.Scheme != originParsed.Scheme { continue // mismatch } allowed = allowed[pos+3:] } allowServer, allowPort, err := tellHostPort(allowed, false) if err != nil { continue // unparseable } if allowPort == "80" && allowed[len(allowed)-3:] != ":80" { // any port is allowed, host names need to match matchFound = allowServer == originServer } else { // exact match of host names and ports matchFound = allowServer == originServer && allowPort == originPort } if matchFound { break } } if !matchFound { log.Access("session", "Origin is not listed in allowed list") return fmt.Errorf("origin list matches were not found") } } } return nil } func tellHostPort(host string, ssl bool) (server, port string, err error) { server, port, err = net.SplitHostPort(host) if err != nil { if addrerr, ok := err.(*net.AddrError); ok && strings.Contains(addrerr.Err, "missing port") { server = host if ssl { port = "443" } else { port = "80" } err = nil } } return server, port, err } websocketd-0.3.1/libwebsocketd/http_test.go000066400000000000000000000117471342360204300210420ustar00rootroot00000000000000package libwebsocketd import ( "bufio" "crypto/tls" "fmt" "net/http" "strings" "testing" ) var tellHostPortTests = []struct { src string ssl bool server, port string }{ {"localhost", false, "localhost", "80"}, {"localhost:8080", false, "localhost", "8080"}, {"localhost", true, "localhost", "443"}, {"localhost:8080", true, "localhost", "8080"}, } func TestTellHostPort(t *testing.T) { for _, testcase := range tellHostPortTests { s, p, e := tellHostPort(testcase.src, testcase.ssl) if testcase.server == "" { if e == nil { t.Errorf("test case for %#v failed, error was not returned", testcase.src) } } else if e != nil { t.Errorf("test case for %#v failed, error should not happen", testcase.src) } if testcase.server != s || testcase.port != p { t.Errorf("test case for %#v failed, server or port mismatch to expected values (%s:%s)", testcase.src, s, p) } } } var NoOriginsAllowed = []string{} var NoOriginList []string = nil const ( ReqHTTPS = iota ReqHTTP OriginMustBeSame OriginCouldDiffer ReturnsPass ReturnsError ) var CheckOriginTests = []struct { host string reqtls int origin string same int allowed []string getsErr int name string }{ {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed"}, {"server.example.com", ReqHTTP, "http://example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin mismatch"}, {"server.example.com", ReqHTTP, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsPass, "same origin match"}, {"server.example.com", ReqHTTP, "https://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 1"}, {"server.example.com", ReqHTTPS, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 2"}, {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginsAllowed, ReturnsError, "no origins allowed"}, {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"server.example.com"}, ReturnsError, "no origin allowed matches (junk prefix)"}, {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com.t"}, ReturnsError, "no origin allowed matches (junk suffix)"}, {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed clean match"}, {"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed any port match"}, {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:80"}, ReturnsPass, "origin allowed port match"}, {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"}, {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"}, {"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com:81"}, ReturnsPass, "origin allowed port 81 match"}, {"server.example.com", ReqHTTP, "null", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even null"}, {"server.example.com", ReqHTTP, "", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even empty"}, } // CONVERT GORILLA // as method for origin checking changes to handle things without websocket.Config the test // should be altered too func TestCheckOrigin(t *testing.T) { for _, testcase := range CheckOriginTests { br := bufio.NewReader(strings.NewReader(fmt.Sprintf(`GET /chat HTTP/1.1 Host: %s Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: %s Sec-WebSocket-Version: 13 `, testcase.host, testcase.origin))) req, err := http.ReadRequest(br) if err != nil { t.Fatal("request", err) } log := new(LogScope) log.LogFunc = func(*LogScope, LogLevel, string, string, string, ...interface{}) {} config := new(Config) if testcase.reqtls == ReqHTTPS { // Fake TLS config.Ssl = true req.TLS = &tls.ConnectionState{} } if testcase.same == OriginMustBeSame { config.SameOrigin = true } if testcase.allowed != nil { config.AllowOrigins = testcase.allowed } err = checkOrigin(req, config, log) if testcase.getsErr == ReturnsError && err == nil { t.Errorf("Test case %#v did not get an error", testcase.name) } else if testcase.getsErr == ReturnsPass && err != nil { t.Errorf("Test case %#v got error while expected to pass", testcase.name) } } } var mimetest = [][3]string{ {"Content-Type: text/plain", "Content-Type", "text/plain"}, {"Content-Type: ", "Content-Type", ""}, } func TestSplitMimeHeader(t *testing.T) { for _, tst := range mimetest { s, v := splitMimeHeader(tst[0]) if tst[1] != s || tst[2] != v { t.Errorf("%v and %v are not same as expexted %v and %v", s, v, tst[1], tst[2]) } } } websocketd-0.3.1/libwebsocketd/launcher.go000066400000000000000000000015221342360204300206130ustar00rootroot00000000000000// 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 ( "io" "os/exec" ) type LaunchedProcess struct { cmd *exec.Cmd stdin io.WriteCloser stdout io.ReadCloser stderr io.ReadCloser } func launchCmd(commandName string, commandArgs []string, env []string) (*LaunchedProcess, error) { cmd := exec.Command(commandName, commandArgs...) cmd.Env = env stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } stderr, err := cmd.StderrPipe() if err != nil { return nil, err } stdin, err := cmd.StdinPipe() if err != nil { return nil, err } err = cmd.Start() if err != nil { return nil, err } return &LaunchedProcess{cmd, stdin, stdout, stderr}, err } websocketd-0.3.1/libwebsocketd/license.go000066400000000000000000000030461342360204300204370ustar00rootroot00000000000000// 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 const ( license = ` Copyright (c) 2013, Joe Walnes and the websocketd authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ` License = license ) websocketd-0.3.1/libwebsocketd/logscope.go000066400000000000000000000051061342360204300206270ustar00rootroot00000000000000// 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 ( "sync" "time" ) type LogLevel int const ( LogDebug = iota LogTrace LogAccess LogInfo LogError LogFatal LogNone = 126 LogUnknown = 127 ) type LogFunc func(logScope *LogScope, level LogLevel, levelName string, category string, msg string, args ...interface{}) type LogScope struct { Parent *LogScope // Parent scope MinLevel LogLevel // Minimum log level to write out. Mutex *sync.Mutex // Should be shared across all LogScopes that write to the same destination. Associated []AssocPair // Additional data associated with scope LogFunc LogFunc } type AssocPair struct { Key string Value string } func (l *LogScope) Associate(key string, value string) { l.Associated = append(l.Associated, AssocPair{key, value}) } func (l *LogScope) Debug(category string, msg string, args ...interface{}) { l.LogFunc(l, LogDebug, "DEBUG", category, msg, args...) } func (l *LogScope) Trace(category string, msg string, args ...interface{}) { l.LogFunc(l, LogTrace, "TRACE", category, msg, args...) } func (l *LogScope) Access(category string, msg string, args ...interface{}) { l.LogFunc(l, LogAccess, "ACCESS", category, msg, args...) } func (l *LogScope) Info(category string, msg string, args ...interface{}) { l.LogFunc(l, LogInfo, "INFO", category, msg, args...) } func (l *LogScope) Error(category string, msg string, args ...interface{}) { l.LogFunc(l, LogError, "ERROR", category, msg, args...) } func (l *LogScope) Fatal(category string, msg string, args ...interface{}) { l.LogFunc(l, LogFatal, "FATAL", category, msg, args...) } func (parent *LogScope) NewLevel(logFunc LogFunc) *LogScope { return &LogScope{ Parent: parent, MinLevel: parent.MinLevel, Mutex: parent.Mutex, Associated: make([]AssocPair, 0), LogFunc: logFunc} } func RootLogScope(minLevel LogLevel, logFunc LogFunc) *LogScope { return &LogScope{ Parent: nil, MinLevel: minLevel, Mutex: &sync.Mutex{}, Associated: make([]AssocPair, 0), LogFunc: logFunc} } func Timestamp() string { return time.Now().Format(time.RFC1123Z) } func LevelFromString(s string) LogLevel { switch s { case "debug": return LogDebug case "trace": return LogTrace case "access": return LogAccess case "info": return LogInfo case "error": return LogError case "fatal": return LogFatal case "none": return LogNone default: return LogUnknown } } websocketd-0.3.1/libwebsocketd/process_endpoint.go000066400000000000000000000101661342360204300223740ustar00rootroot00000000000000// 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 ( "bufio" "io" "syscall" "time" ) type ProcessEndpoint struct { process *LaunchedProcess closetime time.Duration output chan []byte log *LogScope bin bool } func NewProcessEndpoint(process *LaunchedProcess, bin bool, log *LogScope) *ProcessEndpoint { return &ProcessEndpoint{ process: process, output: make(chan []byte), log: log, bin: bin, } } func (pe *ProcessEndpoint) Terminate() { terminated := make(chan struct{}) go func() { pe.process.cmd.Wait(); terminated <- struct{}{} }() // for some processes this is enough to finish them... pe.process.stdin.Close() // a bit verbose to create good debugging trail select { case <-terminated: pe.log.Debug("process", "Process %v terminated after stdin was closed", pe.process.cmd.Process.Pid) return // means process finished case <-time.After(100*time.Millisecond + pe.closetime): } err := pe.process.cmd.Process.Signal(syscall.SIGINT) if err != nil { // process is done without this, great! pe.log.Error("process", "SIGINT unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) } select { case <-terminated: pe.log.Debug("process", "Process %v terminated after SIGINT", pe.process.cmd.Process.Pid) return // means process finished case <-time.After(250*time.Millisecond + pe.closetime): } err = pe.process.cmd.Process.Signal(syscall.SIGTERM) if err != nil { // process is done without this, great! pe.log.Error("process", "SIGTERM unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) } select { case <-terminated: pe.log.Debug("process", "Process %v terminated after SIGTERM", pe.process.cmd.Process.Pid) return // means process finished case <-time.After(500*time.Millisecond + pe.closetime): } err = pe.process.cmd.Process.Kill() if err != nil { pe.log.Error("process", "SIGKILL unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) return } select { case <-terminated: pe.log.Debug("process", "Process %v terminated after SIGKILL", pe.process.cmd.Process.Pid) return // means process finished case <-time.After(1000 * time.Millisecond): } pe.log.Error("process", "SIGKILL did not terminate %v!", pe.process.cmd.Process.Pid) } func (pe *ProcessEndpoint) Output() chan []byte { return pe.output } func (pe *ProcessEndpoint) Send(msg []byte) bool { pe.process.stdin.Write(msg) return true } func (pe *ProcessEndpoint) StartReading() { go pe.log_stderr() if pe.bin { go pe.process_binout() } else { go pe.process_txtout() } } func (pe *ProcessEndpoint) process_txtout() { bufin := bufio.NewReader(pe.process.stdout) for { buf, err := bufin.ReadBytes('\n') if err != nil { if err != io.EOF { pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err) } else { pe.log.Debug("process", "Process STDOUT closed") } break } pe.output <- trimEOL(buf) } close(pe.output) } func (pe *ProcessEndpoint) process_binout() { buf := make([]byte, 10*1024*1024) for { n, err := pe.process.stdout.Read(buf) if err != nil { if err != io.EOF { pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err) } else { pe.log.Debug("process", "Process STDOUT closed") } break } pe.output <- append(make([]byte, 0, n), buf[:n]...) // cloned buffer } close(pe.output) } func (pe *ProcessEndpoint) log_stderr() { bufstderr := bufio.NewReader(pe.process.stderr) for { buf, err := bufstderr.ReadSlice('\n') if err != nil { if err != io.EOF { pe.log.Error("process", "Unexpected error while reading STDERR from process: %s", err) } else { pe.log.Debug("process", "Process STDERR closed") } break } pe.log.Error("stderr", "%s", string(trimEOL(buf))) } } // trimEOL cuts unixy style \n and windowsy style \r\n suffix from the string func trimEOL(b []byte) []byte { lns := len(b) if lns > 0 && b[lns-1] == '\n' { lns-- if lns > 0 && b[lns-1] == '\r' { lns-- } } return b[:lns] } websocketd-0.3.1/libwebsocketd/websocket_endpoint.go000066400000000000000000000037641342360204300227120ustar00rootroot00000000000000// 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 ( "io" "io/ioutil" "github.com/gorilla/websocket" ) // CONVERT GORILLA // This file should be altered to use gorilla's websocket connection type and proper // message dispatching methods type WebSocketEndpoint struct { ws *websocket.Conn output chan []byte log *LogScope mtype int } func NewWebSocketEndpoint(ws *websocket.Conn, bin bool, log *LogScope) *WebSocketEndpoint { endpoint := &WebSocketEndpoint{ ws: ws, output: make(chan []byte), log: log, mtype: websocket.TextMessage, } if bin { endpoint.mtype = websocket.BinaryMessage } return endpoint } func (we *WebSocketEndpoint) Terminate() { we.log.Trace("websocket", "Terminated websocket connection") } func (we *WebSocketEndpoint) Output() chan []byte { return we.output } func (we *WebSocketEndpoint) Send(msg []byte) bool { w, err := we.ws.NextWriter(we.mtype) if err == nil { _, err = w.Write(msg) } w.Close() // could need error handling if err != nil { we.log.Trace("websocket", "Cannot send: %s", err) return false } return true } func (we *WebSocketEndpoint) StartReading() { go we.read_frames() } func (we *WebSocketEndpoint) read_frames() { for { mtype, rd, err := we.ws.NextReader() if err != nil { we.log.Debug("websocket", "Cannot receive: %s", err) break } if mtype != we.mtype { we.log.Debug("websocket", "Received message of type that we did not expect... Ignoring...") } p, err := ioutil.ReadAll(rd) if err != nil && err != io.EOF { we.log.Debug("websocket", "Cannot read received message: %s", err) break } switch mtype { case websocket.TextMessage: we.output <- append(p, '\n') case websocket.BinaryMessage: we.output <- p default: we.log.Debug("websocket", "Received message of unknown type: %d", mtype) } } close(we.output) } websocketd-0.3.1/main.go000066400000000000000000000072651342360204300151270ustar00rootroot00000000000000// 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 main import ( "fmt" "net/http" "os" "runtime" "strconv" "strings" "github.com/joewalnes/websocketd/libwebsocketd" ) func logfunc(l *libwebsocketd.LogScope, level libwebsocketd.LogLevel, levelName string, category string, msg string, args ...interface{}) { if level < l.MinLevel { return } fullMsg := fmt.Sprintf(msg, args...) assocDump := "" for index, pair := range l.Associated { if index > 0 { assocDump += " " } assocDump += fmt.Sprintf("%s:'%s'", pair.Key, pair.Value) } l.Mutex.Lock() fmt.Printf("%s | %-6s | %-10s | %s | %s\n", libwebsocketd.Timestamp(), levelName, category, assocDump, fullMsg) l.Mutex.Unlock() } func main() { config := parseCommandLine() log := libwebsocketd.RootLogScope(config.LogLevel, logfunc) 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) } } if runtime.GOOS != "windows" { // windows relies on env variables to find its libs... e.g. socket stuff os.Clearenv() // it's ok to wipe it clean, we already read env variables from passenv into config } handler := libwebsocketd.NewWebsocketdServer(config.Config, log, config.MaxForks) http.Handle("/", handler) if config.UsingScriptDir { 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) } rejects := make(chan error, 1) for _, addrSingle := range config.Addr { log.Info("server", "Starting WebSocket server : %s", handler.TellURL("ws", addrSingle, "/")) if config.DevConsole { log.Info("server", "Developer console enabled : %s", handler.TellURL("http", addrSingle, "/")) } else if config.StaticDir != "" || config.CgiDir != "" { log.Info("server", "Serving CGI or static files : %s", handler.TellURL("http", addrSingle, "/")) } // ListenAndServe is blocking function. Let's run it in // go routine, reporting result to control channel. // Since it's blocking it'll never return non-error. go func(addr string) { if config.Ssl { rejects <- http.ListenAndServeTLS(addr, config.CertFile, config.KeyFile, nil) } else { rejects <- http.ListenAndServe(addr, nil) } }(addrSingle) if config.RedirPort != 0 { go func(addr string) { pos := strings.IndexByte(addr, ':') rediraddr := addr[:pos] + ":" + strconv.Itoa(config.RedirPort) // it would be silly to optimize this one redir := &http.Server{Addr: rediraddr, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // redirect to same hostname as in request but different port and probably schema uri := "https://" if !config.Ssl { uri = "http://" } uri += r.Host[:strings.IndexByte(r.Host, ':')] + addr[pos:] + "/" http.Redirect(w, r, uri, http.StatusMovedPermanently) })} log.Info("server", "Starting redirect server : http://%s/", rediraddr) rejects <- redir.ListenAndServe() }(addrSingle) } } select { case err := <-rejects: log.Fatal("server", "Can't start server: %s", err) os.Exit(3) } } websocketd-0.3.1/release/000077500000000000000000000000001342360204300152625ustar00rootroot00000000000000websocketd-0.3.1/release/.gitignore000066400000000000000000000000501342360204300172450ustar00rootroot00000000000000bin go-local out go-path websocketd.exe websocketd-0.3.1/release/Makefile000066400000000000000000000161631342360204300167310ustar00rootroot00000000000000# Copyright 2013-2019 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. # Uses Semantic Versioning scheme - http://semver.org/ VERSION_MAJOR=0 VERSION_MINOR=3 # Last part of version number (patch) is incremented automatically from Git tags LAST_PATCH_VERSION:=$(shell git ls-remote git@github.com:joewalnes/websocketd.git \ | grep -e 'refs/tags/v[0-9\.]*$$' \ | sed -e 's|^.*refs/tags/v||' \ | grep -e "^$(VERSION_MAJOR)\.$(VERSION_MINOR)\.[0-9][0-9]*$$" \ | sed -e 's/^.*\.//' \ | sort -n \ | tail -n 1) VERSION_PATCH:=$(shell expr $$(( $(or $(LAST_PATCH_VERSION),-1) + 1 ))) RELEASE_VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH) GO_VERSION=1.11.5 PLATFORMS=linux_amd64 linux_386 linux_arm linux_arm64 darwin_amd64 freebsd_amd64 freebsd_386 windows_386 windows_amd64 openbsd_386 openbsd_amd64 solaris_amd64 # Would NOT WORK on: ARM, WINDOWS UNAME_SYS=$(shell uname -s | tr A-Z a-z) UNAME_ARCH=$(shell uname -m) ifeq ($(UNAME_ARCH),x86_64) UNAME_ARCH=amd64 else UNAME_ARCH=386 endif ifeq ($(UNAME_SYS),gnu) UNAME_SYS=linux else ifeq ($(UNAME_SYS),sunos) UNAME_SYS=solaris endif GO_DOWNLOAD_URL=https://dl.google.com/go/go$(GO_VERSION).$(UNAME_SYS)-$(UNAME_ARCH).tar.gz GO_DIR=../go-$(GO_VERSION) # Prevent any global environment polluting the builds FLAGS_linux_amd64 = GOOS=linux GOARCH=amd64 FLAGS_linux_386 = GOOS=linux GOARCH=386 FLAGS_linux_arm = GOOS=linux GOARCH=arm GOARM=5 # ARM5 support for Raspberry Pi FLAGS_linux_arm64 = GOOS=linux GOARCH=arm64 # no need for GOARM= (which is technically 8) FLAGS_darwin_amd64 = GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 FLAGS_darwin_386 = GOOS=darwin GOARCH=386 CGO_ENABLED=0 FLAGS_freebsd_amd64 = GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0 FLAGS_freebsd_386 = GOOS=freebsd GOARCH=386 CGO_ENABLED=0 FLAGS_windows_386 = GOOS=windows GOARCH=386 CGO_ENABLED=0 FLAGS_windows_amd64 = GOOS=windows GOARCH=amd64 CGO_ENABLED=0 FLAGS_openbsd_386 = GOOS=openbsd GOARCH=386 CGO_ENABLED=0 FLAGS_openbsd_amd64 = GOOS=openbsd GOARCH=amd64 CGO_ENABLED=0 FLAGS_solaris_amd64 = GOOS=solaris GOARCH=amd64 CGO_ENABLED=0 all: build localgo: $(GO_DIR)/bin/go $(GO_DIR)/bin/go: mkdir -p $(GO_DIR) rm -f $@ @echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR) curl -s $(GO_DOWNLOAD_URL) | tar xzf - --strip-components=1 -C $(GO_DIR) # Cross-compile final applications out/$(RELEASE_VERSION)/%/websocketd: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go rm -f $@ mkdir -p $(dir $@) $(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd .. out/$(RELEASE_VERSION)/%/websocketd.exe: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go rm -f $@ mkdir -p $(dir $@) $(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd.exe .. out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-%.zip: out/$(RELEASE_VERSION)/%/websocketd rm -f $@ zip -j $@ out/$(RELEASE_VERSION)/$*/* ../{README.md,LICENSE,CHANGES} BINARIES = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/$(PLATFORM)/websocketd$(EXTENSION_$(PLATFORM))) ZIPS = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-$(PLATFORM).zip) DEBS = out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb RPMS = out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm binaries: $(BINARIES) build: out/$(RELEASE_VERSION)/CHECKSUMS out/$(RELEASE_VERSION)/CHECKSUMS: $(BINARIES) $(ZIPS) $(DEBS) $(RPMS) sha256sum $^ | sed -e 's/out\/$(RELEASE_VERSION)\///' >$@ BASEFPM=--description "WebSockets server that converts STDIO scripts to powerful HTML5 applications." --url http://websocketd.com/ --license MIT --vendor "websocketd team " --maintainer "abc@alexsergeyev.com" DEBFPM="" RPMFPM=--rpm-os linux deb: $(DEBS) rpm: $(RPMS) out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd mkdir -p out/$(RELEASE_VERSION)/deb32/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/deb32/usr/bin/ cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb32/usr/share/doc/websocketd-$(RELEASE_VERSION) cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb32/usr/share/man/man1/websocket.1.gz fpm -f -s dir -t deb -a i386 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/deb32/ -p out/$(RELEASE_VERSION)/websocketd-VERSION_ARCH.deb $(BASEFPM) $(DEB_FPM) usr/ rm -rf out/$(RELEASE_VERSION)/deb32/ out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd mkdir -p out/$(RELEASE_VERSION)/deb64/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/deb64/usr/bin/ cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb64/usr/share/doc/websocketd-$(RELEASE_VERSION) cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb64/usr/share/man/man1/websocket.1.gz fpm -f -s dir -t deb -a amd64 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/deb64/ -p out/$(RELEASE_VERSION)/websocketd-VERSION_ARCH.deb $(BASEFPM) $(DEB_FPM) usr/ rm -rf out/$(RELEASE_VERSION)/deb64/ out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd mkdir -p out/$(RELEASE_VERSION)/rpm64/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/rpm64/usr/bin/ cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm64/usr/share/doc/websocketd-$(RELEASE_VERSION) cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm64/usr/share/man/man1/websocket.1.gz fpm -f -s dir -t rpm -a x86_64 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/rpm64/ -p out/$(RELEASE_VERSION)/websocketd.VERSION.ARCH.rpm $(BASEFPM) $(RPMFPM) usr/ rm -rf out/$(RELEASE_VERSION)/rpm64/ out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd mkdir -p out/$(RELEASE_VERSION)/rpm32/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/rpm32/usr/bin/ cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm32/usr/share/doc/websocketd-$(RELEASE_VERSION) cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm32/usr/share/man/man1/websocket.1.gz fpm -f -s dir -t rpm -a i386 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/rpm32/ -p out/$(RELEASE_VERSION)/websocketd.VERSION.ARCH.rpm $(BASEFPM) $(RPMFPM) usr/ rm -rf out/$(RELEASE_VERSION)/rpm32/ # Clean up clobber: clean rm -rf $(GO_DIR) clean: rm -rf out .PHONY: all build deb rpm localgo clobber clean websocketd-0.3.1/release/README000066400000000000000000000035351342360204300161500ustar00rootroot00000000000000Release scripts for websocketd ============================== Perform a fully automated repeatable release of websocketd. This is three-stage process in normal release cycle that's performed by repository maintainers. * Update CHANGES and Tag * Build * Release Those that do not have permissions to push tags could still use this build chain to build their own customized versions or packages. ## Step 1: Update CHANGES and Tag First edit and commit CHANGES file. List all significant updates between releases. Annotated tags require for release: git tag -a vx.x.x To see currently tagged version run tag with -l option. ## Step 2: Build release/Makefile contains all required to download pre-verified for build release of Go and cross-compile websocketd for all platforms that we release for. cd release make Is generally recommended but other build options are available: * go-compile: build cross-platform golang pakages * binaries: only create websocketd binaries, do not generate zip distributions * deb, rpm: only create particular kind of packages * clean-go: remove build files for go * clean-out: remove built binaries, zip and linux packages * clean: remove everything (go and release files) Building requires to have gcc and other build tools installed. Building rpm/deb files would fail without fpm ("gem install fpm" itself requires ruby devel tools). Create CHECKSUMS.asc file using gpg and key that other developers shared with you: gpg -u KEYID --clearsign CHECKSUMS (key has to be installed in your gpg environment first) ## Step 3: Release In order to make release official following steps required: # push tags (assuming joewalnes repo is origin): go push --tags origin master:master Upload files to github release (to be automated). Use these instructions for now: https://github.com/blog/1547-release-your-software websocketd-0.3.1/release/websocketd.man000066400000000000000000000071221342360204300201130ustar00rootroot00000000000000.\" Manpage for websocketd. .\" Contact abc@alexsergeyev.com to correct errors or typos. .TH websocketd 8 "28 Sep 2014" "0.0" "websocketd man page" .SH NAME websocketd \- turns any program that uses STDIN/STDOUT into a WebSocket server. .SH SYNOPSIS websocketd [options] COMMAND [command args] or websocketd [options] --dir=SOMEDIR .SH DESCRIPTION \fBwebsocketd\fR is a command line tool that will allow any executable program that accepts input on stdin and produces output on stdout to be turned into a WebSocket server. To learn more about websocketd visit \fIhttp://websocketd.com\fR and project WIKI on GitHub! .SH OPTIONS A summary of the options supported by websocketd is included below. .PP \-\-port=PORT .RS 4 HTTP port to listen on. .RE .PP \-\-address=ADDRESS .RS 4 Address to bind to (multiple options allowed). Use square brackets to specify IPv6 address. Default: "" (all) .RE .PP \-\-sameorigin={true,false} .RS 4 Restrict (HTTP 403) protocol upgrades if the Origin header does not match to requested HTTP Host. Default: false. .RE .PP --origin=host[:port][,host[:port]...] .RS 4 Restrict (HTTP 403) protocol upgrades if the Origin header does not match to one of the host and port combinations listed. If the port is not specified, any port number will match. Default: "" (allow any origin) .RE .PP \-\-ssl \-\-sslcert=FILE \-\-sslkey=FILE .RS 4 Listen for HTTPS socket instead of HTTP. All three options must be used or all of them should be omitted. .RE .PP \-\-passenv VAR[,VAR...] .RS 4 Lists environment variables allowed to be passed to executed scripts. .RE .PP \-\-reverselookup={true,false} .RS 4 Perform DNS reverse lookups on remote clients. Default: true .RE .PP \-\-dir=DIR .RS 4 Allow all scripts in the local directory to be accessed as WebSockets. If using this, option, then the standard program and args options should not be specified. .RE .PP \-\-staticdir=DIR .RS 4 Serve static files in this directory over HTTP. .RE .PP \-\-cgidir=DIR .RS 4 Serve CGI scripts in this directory over HTTP. .RE .PP \-\-help .RS 4 Print help and exit. .RE .PP \-\-version .RS 4 Print version and exit. .RE .PP \-\-license .RS 4 Print license and exit. .RE .PP \-\-devconsole .RS 4 Enable interactive development console. This enables you to access the websocketd server with a web-browser and use a user interface to quickly test WebSocket endpoints. For example, to test an endpoint at ws://[host]/foo, you can visit http://[host]/foo in your browser. This flag cannot be used in conjunction with \-\-staticdir or \-\-cgidir. .RE .PP \-\-loglevel=LEVEL .RS 4 Log level to use (default access). From most to least verbose: debug, trace, access, info, error, fatal .RE .SH SEE ALSO .RS 2 * full documentation at \fIhttp://websocketd.com\fR .RE .RS 2 * project source at \fIhttps://github.com/joewalnes/websocketd\fR .RE .SH BUGS The only known condition so far is that certain applications in programming languages that enforce implicit STDOUT buffering (Perl, Python, etc.) would be producing unexpected data passing delays when run under \fBwebsocketd\fR. Such issues could be solved by editing the source code of those applications (prohibiting buffering) or modifying their environment to trick them into autoflush mode (e.g. pseudo-terminal wrapper "unbuffer"). Active issues in development are discussed on GitHub: \fIhttps://github.com/joewalnes/websocketd/issues\fR. Please use that page to share your concerns and ideas about \fBwebsocketd\fR, authors would greatly appreciate your help! .SH AUTHOR Copyright 2013-2014 Joe Walnes and the websocketd team. All rights reserved. BSD license: Run 'websocketd \-\-license' for details. websocketd-0.3.1/version.go000066400000000000000000000011711342360204300156560ustar00rootroot00000000000000// 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 main import ( "fmt" "runtime" ) // This value can be set for releases at build time using: // go {build|run} -ldflags "-X main.version 1.2.3 -X main.buildinfo timestamp-@githubuser-platform". // If unset, Version() shall return "DEVBUILD". var version string = "DEVBUILD" var buildinfo string = "--" func Version() string { return fmt.Sprintf("%s (%s %s-%s) %s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH, buildinfo) }