├── .gitignore ├── AUTHORS ├── CHANGES ├── LICENSE ├── Makefile ├── README.md ├── config.go ├── examples ├── bash │ ├── README.txt │ ├── chat.sh │ ├── count.sh │ ├── dump-env.sh │ ├── greeter.sh │ └── send-receive.sh ├── c# │ ├── .gitignore │ ├── Count │ │ ├── App.config │ │ ├── Count.csproj │ │ ├── Program.cs │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ ├── Echo │ │ ├── App.config │ │ ├── Echo.csproj │ │ ├── Program.cs │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ ├── Examples.sln │ ├── README.md │ ├── run_count.cmd │ └── run_echo.cmd ├── cgi-bin │ ├── README.txt │ └── dump-env.sh ├── f# │ ├── .gitignore │ ├── Count │ │ ├── App.config │ │ ├── Count.fsproj │ │ └── Program.fs │ ├── Echo │ │ ├── App.config │ │ ├── Echo.fsproj │ │ └── Program.fs │ ├── Examples.sln │ ├── README.md │ ├── run_count.cmd │ └── run_echo.cmd ├── hack │ ├── README.md │ ├── count.hh │ ├── dump-env.hh │ └── greeter.hh ├── haskell │ ├── README.md │ ├── count.hs │ └── greeter.hs ├── html │ └── count.html ├── java │ ├── Count │ │ ├── Count.java │ │ └── count.sh │ ├── Echo │ │ ├── Echo.java │ │ └── echo.sh │ └── README.md ├── lua │ ├── README.md │ ├── greeter.lua │ ├── json.lua │ └── json_ws.lua ├── nodejs │ ├── README.md │ ├── count.js │ └── greeter.js ├── perl │ ├── README.txt │ ├── count.pl │ ├── dump-env.pl │ └── greeter.pl ├── php │ ├── README.txt │ ├── count.php │ ├── dump-env.php │ └── greeter.php ├── python │ ├── README.txt │ ├── count.py │ ├── dump-env.py │ └── greeter.py ├── qjs │ └── request-reply.js ├── ruby │ ├── README.txt │ ├── count.rb │ ├── dump-env.rb │ └── greeter.rb ├── rust │ ├── README.txt │ ├── count.rs │ ├── dump-env.rs │ └── greeter.rs ├── swift │ ├── README.md │ ├── count.swift │ └── greeter.swift ├── windows-jscript │ ├── README.txt │ ├── count.cmd │ ├── count.js │ ├── dump-env.cmd │ ├── dump-env.js │ ├── greeter.cmd │ └── greeter.js └── windows-vbscript │ ├── README.txt │ ├── count.cmd │ ├── count.vbs │ ├── dump-env.cmd │ ├── dump-env.vbs │ ├── greeter.cmd │ └── greeter.vbs ├── go.mod ├── go.sum ├── help.go ├── libwebsocketd ├── config.go ├── console.go ├── endpoint.go ├── endpoint_test.go ├── env.go ├── handler.go ├── handler_test.go ├── http.go ├── http_test.go ├── launcher.go ├── license.go ├── logscope.go ├── process_endpoint.go └── websocket_endpoint.go ├── main.go ├── release ├── .gitignore ├── Makefile ├── README └── websocketd.man └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | websocketd 2 | go-* 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | websocketd authors 2 | ================== 3 | 4 | Joe Walnes 5 | Gareth Jones 6 | Ajit George 7 | Alex Sergeyev 8 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Version 0.4.1 (Jan 24, 2021) 2 | 3 | * Minor changes only 4 | * Updated to Go 1.15.7 5 | 6 | Version 0.3.1 (Jan 28, 2019) 7 | 8 | * Minor improvements to websocketd itself 9 | * Use of go modules, gorilla websockets set to 1.4.0 10 | * Binaries build code switched to 1.11.5 (improving underlying protocol handlers) 11 | 12 | Version 0.3.0 (??, 2017) 13 | 14 | * Migration of underlying websocket server to Gorilla Websocket lib. 15 | * Binaries build code switched to 1.9.2 16 | 17 | Version 0.2.12 (Feb 17, 2016) 18 | 19 | * Update of underlying go standard libraries change how SSL works. SSL3 is no longer supported. 20 | * Support of commands that do not provide text IO (using them as binary websocket frames) 21 | * Minor changes in examples and --help output 22 | 23 | Version 0.2.11 (Jul 1, 2015) 24 | 25 | * PATH env variable is now passed to process by default 26 | * new --header* flags could generate custom HTTP headers for all websocketd-generated answers 27 | * fixed bug causing process to hang when WebSockets client disconnect is detected 28 | * minor changes for console app (default url building logic and tab char printing) 29 | * multiple changes of examples. 30 | 31 | 32 | Version 0.2.10 (Feb 16, 2015) 33 | 34 | * fixes for null-origin situations (#75, #96) 35 | * better bash examples (#103) 36 | * changelog and checksums for released files (#101, #105) 37 | 38 | 39 | Version 0.2.9 (May 19, 2014) 40 | 41 | * ability to listen multiple IP addresses (#40, #43) 42 | * proper support for TLS (#17) 43 | * resource limits enforcement (a.k.a. maxforks feature, #46) 44 | * passenv option to limit environment variables visible by running commands (#4) 45 | * fix for problem of closing upgraded websocket connection when script is not found (#29) 46 | * websocket origin restrictions via command line option (#20) 47 | * minor update for help flag behavior 48 | * minor fix for devconsole 49 | 50 | Version 0.2.8 (Jan 11, 2014) 51 | 52 | * ... -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Joe Walnes and the websocketd authors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Joe Walnes and the websocketd team. 2 | # All rights reserved. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | 6 | # Self contained Go build file that will download and install (locally) the correct 7 | # version of Go, and build our programs. Go does not need to be installed on the 8 | # system (and if it already is, it will be ignored). 9 | 10 | # To manually invoke the locally installed Go, use ./go 11 | 12 | # Go installation config. 13 | GO_VER=1.11.5 14 | SYSTEM_NAME:=$(shell uname -s | tr '[:upper:]' '[:lower:]') 15 | SYSTEM_ARCH:=$(shell uname -m) 16 | GO_ARCH:=$(if $(filter x86_64, $(SYSTEM_ARCH)),amd64,386) 17 | GO_VERSION:=$(GO_VER).$(SYSTEM_NAME)-$(GO_ARCH) 18 | GO_DOWNLOAD_URL:=https://dl.google.com/go/go$(GO_VERSION).tar.gz 19 | GO_DIR:=go-$(GO_VER) 20 | 21 | # Build websocketd binary 22 | websocketd: $(GO_DIR)/bin/go $(wildcard *.go) $(wildcard libwebsocketd/*.go) 23 | $(GO_DIR)/bin/go build 24 | 25 | localgo: $(GO_DIR)/bin/go 26 | 27 | # Download and unpack Go distribution. 28 | $(GO_DIR)/bin/go: 29 | mkdir -p $(GO_DIR) 30 | rm -f $@ 31 | @echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR) 32 | curl -s $(GO_DOWNLOAD_URL) | tar xfz - --strip-components=1 -C $(GO_DIR) 33 | 34 | # Clean up binary 35 | clean: 36 | rm -rf websocketd 37 | 38 | .PHONY: clean 39 | 40 | # Also clean up downloaded Go 41 | clobber: clean 42 | rm -rf $(wildcard go-v*) 43 | 44 | .PHONY: clobber 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | websocketd 2 | ========== 3 | 4 | `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. 5 | 6 | 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. 7 | 8 | -[@joewalnes](https://twitter.com/joewalnes) 9 | 10 | Details 11 | ------- 12 | 13 | Upon startup, `websocketd` will start a WebSocket server on a specified port, and listen for connections. 14 | 15 | Upon a connection, it will fork the appropriate process, and disconnect the process when the WebSocket connection closes (and vice-versa). 16 | 17 | Any message sent from the WebSocket client will be piped to the process's `STDIN` stream, followed by a `\n` newline. 18 | 19 | Any text printed by the process to `STDOUT` shall be sent as a WebSocket message whenever a `\n` newline is encountered. 20 | 21 | 22 | Download 23 | -------- 24 | 25 | 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. 26 | 27 | **[Download for Linux, OS X and Windows](https://github.com/joewalnes/websocketd/wiki/Download-and-install)** 28 | 29 | 30 | Quickstart 31 | ---------- 32 | 33 | 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. 34 | 35 | To show how simple it is, let's do it in Bash! 36 | 37 | __count.sh__: 38 | 39 | ```sh 40 | #!/bin/bash 41 | for ((COUNT = 1; COUNT <= 10; COUNT++)); do 42 | echo $COUNT 43 | sleep 1 44 | done 45 | ``` 46 | 47 | 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. 48 | 49 | ```sh 50 | $ chmod +x count.sh 51 | $ ./count.sh 52 | 1 53 | 2 54 | 3 55 | 4 56 | 5 57 | 6 58 | 7 59 | 8 60 | 9 61 | 10 62 | ``` 63 | 64 | Now let's turn it into a WebSocket server: 65 | 66 | ```sh 67 | $ websocketd --port=8080 ./count.sh 68 | ``` 69 | 70 | Finally, let's create a web-page to test it. 71 | 72 | __count.html__: 73 | 74 | ```html 75 | 76 |

 77 | 
 95 | ```
 96 | Open this page in your web-browser. It will even work if you open it directly
 97 | from disk using a `file://` URL.
 98 | 
 99 | More Features
100 | -------------
101 | 
102 | *   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.
103 | *   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).
104 | *   As well as serving websocket daemons it also includes a static file server and classic CGI server for convenience.
105 | *   Command line help available via `websocketd --help`.
106 | *   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.
107 | *   [Examples in many programming languages](https://github.com/joewalnes/websocketd/tree/master/examples) are available to help you getting started.
108 | 
109 | User Manual
110 | -----------
111 | 
112 | **[More documentation in the user manual](https://github.com/joewalnes/websocketd/wiki)**
113 | 
114 | Example Projects
115 | ----------------
116 | 
117 | *   [Plot real time Linux CPU/IO/Mem stats to a HTML5 dashboard using websocketd and vmstat](https://github.com/joewalnes/web-vmstats) _(for Linux)_
118 | *   [Arbitrary REPL in the browser using websocketd](https://github.com/rowanthorpe/ws-repl)
119 | *   [Retrieve SQL data from server with LiveCode and webSocketd](https://github.com/samansjukur/wslc)
120 | *   [List files from a configured folder](https://github.com/dbalakirev/directator) _(for Linux)_
121 | *   [Listen for gamepad events and report them to the system](https://github.com/experiment322/controlloid-server) _(this + android = gamepad emulator)_
122 | 
123 | Got more examples? Open a pull request.
124 | 
125 | My Other Projects
126 | -----------------
127 | 
128 | *   [ReconnectingWebSocket](https://github.com/joewalnes/reconnecting-websocket) - Simplest way to add some robustness to your WebSocket connections.
129 | *   [Smoothie Charts](http://smoothiecharts.org/) - JavaScript charts for streaming data.
130 | *   Visit [The Igloo Lab](http://theigloolab.com/) to see and subscribe to other thingies I make.
131 | 
132 | And [follow @joewalnes](https://twitter.com/joewalnes)!
133 | 


--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
  1 | // Copyright 2013 Joe Walnes and the websocketd team.
  2 | // All rights reserved.
  3 | // Use of this source code is governed by a BSD-style
  4 | // license that can be found in the LICENSE file.
  5 | 
  6 | package main
  7 | 
  8 | import (
  9 | 	"flag"
 10 | 	"fmt"
 11 | 	"os"
 12 | 	"os/exec"
 13 | 	"path/filepath"
 14 | 	"runtime"
 15 | 	"strings"
 16 | 	"time"
 17 | 
 18 | 	"github.com/joewalnes/websocketd/libwebsocketd"
 19 | )
 20 | 
 21 | type Config struct {
 22 | 	Addr              []string // TCP addresses to listen on. e.g. ":1234", "1.2.3.4:1234" or "[::1]:1234"
 23 | 	MaxForks          int      // Number of allowable concurrent forks
 24 | 	LogLevel          libwebsocketd.LogLevel
 25 | 	RedirPort         int
 26 | 	CertFile, KeyFile string
 27 | 	*libwebsocketd.Config
 28 | }
 29 | 
 30 | type Arglist []string
 31 | 
 32 | func (al *Arglist) String() string {
 33 | 	return fmt.Sprintf("%v", []string(*al))
 34 | }
 35 | 
 36 | func (al *Arglist) Set(value string) error {
 37 | 	*al = append(*al, value)
 38 | 	return nil
 39 | }
 40 | 
 41 | // Borrowed from net/http/cgi
 42 | var defaultPassEnv = map[string]string{
 43 | 	"darwin":  "PATH,DYLD_LIBRARY_PATH",
 44 | 	"freebsd": "PATH,LD_LIBRARY_PATH",
 45 | 	"hpux":    "PATH,LD_LIBRARY_PATH,SHLIB_PATH",
 46 | 	"irix":    "PATH,LD_LIBRARY_PATH,LD_LIBRARYN32_PATH,LD_LIBRARY64_PATH",
 47 | 	"linux":   "PATH,LD_LIBRARY_PATH",
 48 | 	"openbsd": "PATH,LD_LIBRARY_PATH",
 49 | 	"solaris": "PATH,LD_LIBRARY_PATH,LD_LIBRARY_PATH_32,LD_LIBRARY_PATH_64",
 50 | 	"windows": "PATH,SystemRoot,COMSPEC,PATHEXT,WINDIR",
 51 | }
 52 | 
 53 | func parseCommandLine() *Config {
 54 | 	var mainConfig Config
 55 | 	var config libwebsocketd.Config
 56 | 
 57 | 	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
 58 | 	flag.CommandLine.Usage = func() {}
 59 | 
 60 | 	// If adding new command line options, also update the help text in help.go.
 61 | 	// The flag library's auto-generate help message isn't pretty enough.
 62 | 
 63 | 	addrlist := Arglist(make([]string, 0, 1)) // pre-reserve for 1 address
 64 | 	flag.Var(&addrlist, "address", "Interfaces to bind to (e.g. 127.0.0.1 or [::1]).")
 65 | 
 66 | 	// server config options
 67 | 	portFlag := flag.Int("port", 0, "HTTP port to listen on")
 68 | 	versionFlag := flag.Bool("version", false, "Print version and exit")
 69 | 	licenseFlag := flag.Bool("license", false, "Print license and exit")
 70 | 	logLevelFlag := flag.String("loglevel", "access", "Log level, one of: debug, trace, access, info, error, fatal")
 71 | 	sslFlag := flag.Bool("ssl", false, "Use TLS on listening socket (see also --sslcert and --sslkey)")
 72 | 	sslCert := flag.String("sslcert", "", "Should point to certificate PEM file when --ssl is used")
 73 | 	sslKey := flag.String("sslkey", "", "Should point to certificate private key file when --ssl is used")
 74 | 	maxForksFlag := flag.Int("maxforks", 0, "Max forks, zero means unlimited")
 75 | 	closeMsFlag := flag.Uint("closems", 0, "Time to start sending signals (0 never)")
 76 | 	redirPortFlag := flag.Int("redirport", 0, "HTTP port to redirect to canonical --port address")
 77 | 
 78 | 	// lib config options
 79 | 	binaryFlag := flag.Bool("binary", false, "Set websocketd to experimental binary mode (default is line by line)")
 80 | 	reverseLookupFlag := flag.Bool("reverselookup", false, "Perform reverse DNS lookups on remote clients")
 81 | 	scriptDirFlag := flag.String("dir", "", "Base directory for WebSocket scripts")
 82 | 	staticDirFlag := flag.String("staticdir", "", "Serve static content from this directory over HTTP")
 83 | 	cgiDirFlag := flag.String("cgidir", "", "Serve CGI scripts from this directory over HTTP")
 84 | 	devConsoleFlag := flag.Bool("devconsole", false, "Enable development console (cannot be used in conjunction with --staticdir)")
 85 | 	passEnvFlag := flag.String("passenv", defaultPassEnv[runtime.GOOS], "List of envvars to pass to subprocesses (others will be cleaned out)")
 86 | 	sameOriginFlag := flag.Bool("sameorigin", false, "Restrict upgrades if origin and host headers differ")
 87 | 	allowOriginsFlag := flag.String("origin", "", "Restrict upgrades if origin does not match the list")
 88 | 
 89 | 	headers := Arglist(make([]string, 0))
 90 | 	headersWs := Arglist(make([]string, 0))
 91 | 	headersHttp := Arglist(make([]string, 0))
 92 | 	flag.Var(&headers, "header", "Custom headers for any response.")
 93 | 	flag.Var(&headersWs, "header-ws", "Custom headers for successful WebSocket upgrade responses.")
 94 | 	flag.Var(&headersHttp, "header-http", "Custom headers for all but WebSocket upgrade HTTP responses.")
 95 | 
 96 | 	err := flag.CommandLine.Parse(os.Args[1:])
 97 | 	if err != nil {
 98 | 		if err == flag.ErrHelp {
 99 | 			PrintHelp()
100 | 			os.Exit(0)
101 | 		} else {
102 | 			ShortHelp()
103 | 			os.Exit(2)
104 | 		}
105 | 	}
106 | 
107 | 	port := *portFlag
108 | 	if port == 0 {
109 | 		if *sslFlag {
110 | 			port = 443
111 | 		} else {
112 | 			port = 80
113 | 		}
114 | 	}
115 | 
116 | 	if socknum := len(addrlist); socknum != 0 {
117 | 		mainConfig.Addr = make([]string, socknum)
118 | 		for i, addrSingle := range addrlist {
119 | 			mainConfig.Addr[i] = fmt.Sprintf("%s:%d", addrSingle, port)
120 | 		}
121 | 	} else {
122 | 		mainConfig.Addr = []string{fmt.Sprintf(":%d", port)}
123 | 	}
124 | 	mainConfig.MaxForks = *maxForksFlag
125 | 	mainConfig.RedirPort = *redirPortFlag
126 | 	mainConfig.LogLevel = libwebsocketd.LevelFromString(*logLevelFlag)
127 | 	if mainConfig.LogLevel == libwebsocketd.LogUnknown {
128 | 		fmt.Printf("Incorrect loglevel flag '%s'. Use --help to see allowed values.\n", *logLevelFlag)
129 | 		ShortHelp()
130 | 		os.Exit(1)
131 | 	}
132 | 
133 | 	config.Headers = []string(headers)
134 | 	config.HeadersWs = []string(headersWs)
135 | 	config.HeadersHTTP = []string(headersHttp)
136 | 
137 | 	config.CloseMs = *closeMsFlag
138 | 	config.Binary = *binaryFlag
139 | 	config.ReverseLookup = *reverseLookupFlag
140 | 	config.Ssl = *sslFlag
141 | 	config.ScriptDir = *scriptDirFlag
142 | 	config.StaticDir = *staticDirFlag
143 | 	config.CgiDir = *cgiDirFlag
144 | 	config.DevConsole = *devConsoleFlag
145 | 	config.StartupTime = time.Now()
146 | 	config.ServerSoftware = fmt.Sprintf("websocketd/%s", Version())
147 | 	config.HandshakeTimeout = time.Millisecond * 1500 // only default for now
148 | 
149 | 	if len(os.Args) == 1 {
150 | 		fmt.Printf("Command line arguments are missing.\n")
151 | 		ShortHelp()
152 | 		os.Exit(1)
153 | 	}
154 | 
155 | 	if *versionFlag {
156 | 		fmt.Printf("%s %s\n", HelpProcessName(), Version())
157 | 		os.Exit(0)
158 | 	}
159 | 
160 | 	if *licenseFlag {
161 | 		fmt.Printf("%s %s\n", HelpProcessName(), Version())
162 | 		fmt.Printf("%s\n", libwebsocketd.License)
163 | 		os.Exit(0)
164 | 	}
165 | 
166 | 	// Reading SSL options
167 | 	if config.Ssl {
168 | 		if *sslCert == "" || *sslKey == "" {
169 | 			fmt.Fprintf(os.Stderr, "Please specify both --sslcert and --sslkey when requesting --ssl.\n")
170 | 			os.Exit(1)
171 | 		}
172 | 	} else {
173 | 		if *sslCert != "" || *sslKey != "" {
174 | 			fmt.Fprintf(os.Stderr, "You should not be using --ssl* flags when there is no --ssl option.\n")
175 | 			os.Exit(1)
176 | 		}
177 | 	}
178 | 
179 | 	mainConfig.CertFile = *sslCert
180 | 	mainConfig.KeyFile = *sslKey
181 | 
182 | 	// Building config.ParentEnv to avoid calling Environ all the time in the scripts
183 | 	// (caller is responsible for wiping environment if desired)
184 | 	config.ParentEnv = make([]string, 0)
185 | 	newlineCleaner := strings.NewReplacer("\n", " ", "\r", " ")
186 | 	for _, key := range strings.Split(*passEnvFlag, ",") {
187 | 		if key != "HTTPS" {
188 | 			if v := os.Getenv(key); v != "" {
189 | 				// inevitably adding flavor of libwebsocketd appendEnv func.
190 | 				// it's slightly nicer than in net/http/cgi implementation
191 | 				if clean := strings.TrimSpace(newlineCleaner.Replace(v)); clean != "" {
192 | 					config.ParentEnv = append(config.ParentEnv, fmt.Sprintf("%s=%s", key, clean))
193 | 				}
194 | 			}
195 | 		}
196 | 	}
197 | 
198 | 	if *allowOriginsFlag != "" {
199 | 		config.AllowOrigins = strings.Split(*allowOriginsFlag, ",")
200 | 	}
201 | 	config.SameOrigin = *sameOriginFlag
202 | 
203 | 	args := flag.Args()
204 | 	if len(args) < 1 && config.ScriptDir == "" && config.StaticDir == "" && config.CgiDir == "" {
205 | 		fmt.Fprintf(os.Stderr, "Please specify COMMAND or provide --dir, --staticdir or --cgidir argument.\n")
206 | 		ShortHelp()
207 | 		os.Exit(1)
208 | 	}
209 | 
210 | 	if len(args) > 0 {
211 | 		if config.ScriptDir != "" {
212 | 			fmt.Fprintf(os.Stderr, "Ambiguous. Provided COMMAND and --dir argument. Please only specify just one.\n")
213 | 			ShortHelp()
214 | 			os.Exit(1)
215 | 		}
216 | 		if path, err := exec.LookPath(args[0]); err == nil {
217 | 			config.CommandName = path // This can be command in PATH that we are able to execute
218 | 			config.CommandArgs = flag.Args()[1:]
219 | 			config.UsingScriptDir = false
220 | 		} else {
221 | 			fmt.Fprintf(os.Stderr, "Unable to locate specified COMMAND '%s' in OS path.\n", args[0])
222 | 			ShortHelp()
223 | 			os.Exit(1)
224 | 		}
225 | 	}
226 | 
227 | 	if config.ScriptDir != "" {
228 | 		scriptDir, err := filepath.Abs(config.ScriptDir)
229 | 		if err != nil {
230 | 			fmt.Fprintf(os.Stderr, "Could not resolve absolute path to dir '%s'.\n", config.ScriptDir)
231 | 			ShortHelp()
232 | 			os.Exit(1)
233 | 		}
234 | 		inf, err := os.Stat(scriptDir)
235 | 		if err != nil {
236 | 			fmt.Fprintf(os.Stderr, "Could not find your script dir '%s'.\n", config.ScriptDir)
237 | 			ShortHelp()
238 | 			os.Exit(1)
239 | 		}
240 | 		if !inf.IsDir() {
241 | 			fmt.Fprintf(os.Stderr, "Did you mean to specify COMMAND instead of --dir '%s'?\n", config.ScriptDir)
242 | 			ShortHelp()
243 | 			os.Exit(1)
244 | 		} else {
245 | 			config.ScriptDir = scriptDir
246 | 			config.UsingScriptDir = true
247 | 		}
248 | 	}
249 | 
250 | 	if config.CgiDir != "" {
251 | 		if inf, err := os.Stat(config.CgiDir); err != nil || !inf.IsDir() {
252 | 			fmt.Fprintf(os.Stderr, "Your CGI dir '%s' is not pointing to an accessible directory.\n", config.CgiDir)
253 | 			ShortHelp()
254 | 			os.Exit(1)
255 | 		}
256 | 	}
257 | 
258 | 	if config.StaticDir != "" {
259 | 		if inf, err := os.Stat(config.StaticDir); err != nil || !inf.IsDir() {
260 | 			fmt.Fprintf(os.Stderr, "Your static dir '%s' is not pointing to an accessible directory.\n", config.StaticDir)
261 | 			ShortHelp()
262 | 			os.Exit(1)
263 | 		}
264 | 	}
265 | 
266 | 	mainConfig.Config = &config
267 | 
268 | 	return &mainConfig
269 | }
270 | 


--------------------------------------------------------------------------------
/examples/bash/README.txt:
--------------------------------------------------------------------------------
1 | This examples directory shows some examples written in Bash.
2 | 
3 | You can also test the command files by running from the command line.


--------------------------------------------------------------------------------
/examples/bash/chat.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | # Copyright 2013 Jeroen Janssens
 4 | # All rights reserved.
 5 | # Use of this source code is governed by a BSD-style
 6 | # license that can be found in the LICENSE file.
 7 | 
 8 | # Run a simple chat server: websocketd --devconsole --port 8080 ./chat.sh
 9 | #
10 | # Please note that this example requires GNU tail, which is not the default
11 | # tail on OS X. Even though this script properly escapes the variables,
12 | # please keep in mind that it is in general a bad idea to read
13 | # untrusted data into variables and pass this onto the command line.
14 | 
15 | echo "Please enter your name:"; read USER
16 | echo "[$(date)] ${USER} joined the chat" >> chat.log
17 | echo "[$(date)] Welcome to the chat ${USER}!"
18 | tail -n 0 -f chat.log --pid=$$ | grep --line-buffered -v "] ${USER}>" &
19 | while read MSG; do echo "[$(date)] ${USER}> ${MSG}" >> chat.log; done
20 | 


--------------------------------------------------------------------------------
/examples/bash/count.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | # Copyright 2013 Joe Walnes and the websocketd team.
 4 | # All rights reserved.
 5 | # Use of this source code is governed by a BSD-style
 6 | # license that can be found in the LICENSE file.
 7 | 
 8 | # Simple example script that counts to 10 at ~2Hz, then stops.
 9 | for ((COUNT = 1; COUNT <= 10; COUNT++))
10 | do
11 |   echo $COUNT
12 |   sleep 0.5
13 | done
14 | 


--------------------------------------------------------------------------------
/examples/bash/dump-env.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | # Copyright 2013 Joe Walnes and the websocketd team.
 4 | # All rights reserved.
 5 | # Use of this source code is governed by a BSD-style
 6 | # license that can be found in the LICENSE file.
 7 | 
 8 | # Standard CGI(ish) environment variables, as defined in
 9 | # http://tools.ietf.org/html/rfc3875
10 | NAMES="""
11 |   AUTH_TYPE
12 |   CONTENT_LENGTH
13 |   CONTENT_TYPE
14 |   GATEWAY_INTERFACE
15 |   PATH_INFO
16 |   PATH_TRANSLATED
17 |   QUERY_STRING
18 |   REMOTE_ADDR
19 |   REMOTE_HOST
20 |   REMOTE_IDENT
21 |   REMOTE_PORT
22 |   REMOTE_USER
23 |   REQUEST_METHOD
24 |   REQUEST_URI
25 |   SCRIPT_NAME
26 |   SERVER_NAME
27 |   SERVER_PORT
28 |   SERVER_PROTOCOL
29 |   SERVER_SOFTWARE
30 |   UNIQUE_ID
31 |   HTTPS
32 | """
33 | 
34 | for NAME in ${NAMES}
35 | do
36 | 	echo "${NAME}=${!NAME:-}"
37 | done
38 | 
39 | # Additional HTTP headers
40 | env | grep '^HTTP_'
41 | 


--------------------------------------------------------------------------------
/examples/bash/greeter.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | # Copyright 2013 Joe Walnes and the websocketd team.
 4 | # All rights reserved.
 5 | # Use of this source code is governed by a BSD-style
 6 | # license that can be found in the LICENSE file.
 7 | 
 8 | # For each line FOO received on STDIN, respond with "Hello FOO!".
 9 | while read LINE
10 | do
11 | 	echo "Hello $LINE!"
12 | done


--------------------------------------------------------------------------------
/examples/bash/send-receive.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | 
 4 | while true; do
 5 | 	cnt=0
 6 | 	while read -t 0.01 _; do
 7 | 		((cnt++))
 8 | 	done
 9 | 
10 | 	echo "$(date)" "($cnt line(s) received)"
11 | 	sleep $((RANDOM % 10 + 1)) & wait
12 | done
13 | 


--------------------------------------------------------------------------------
/examples/c#/.gitignore:
--------------------------------------------------------------------------------
  1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
  2 | [Bb]in/
  3 | [Oo]bj/
  4 | 
  5 | # mstest test results
  6 | TestResults
  7 | 
  8 | ## Ignore Visual Studio temporary files, build results, and
  9 | ## files generated by popular Visual Studio add-ons.
 10 | 
 11 | # User-specific files
 12 | *.suo
 13 | *.user
 14 | *.sln.docstates
 15 | 
 16 | # Build results
 17 | [Dd]ebug/
 18 | [Rr]elease/
 19 | build/
 20 | test/
 21 | deploy/
 22 | x64/
 23 | *_i.c
 24 | *_p.c
 25 | *.ilk
 26 | *.meta
 27 | *.obj
 28 | *.pch
 29 | *.pdb
 30 | *.pgc
 31 | *.pgd
 32 | *.rsp
 33 | *.sbr
 34 | *.tlb
 35 | *.tli
 36 | *.tlh
 37 | *.tmp
 38 | *.vspscc
 39 | *.vssscc
 40 | .builds
 41 | 
 42 | # Visual C++ cache files
 43 | ipch/
 44 | *.aps
 45 | *.ncb
 46 | *.opensdf
 47 | *.sdf
 48 | 
 49 | # Visual Studio profiler
 50 | *.psess
 51 | *.vsp
 52 | 
 53 | # Guidance Automation Toolkit
 54 | *.gpState
 55 | 
 56 | # ReSharper is a .NET coding add-in
 57 | _ReSharper*
 58 | 
 59 | # NCrunch
 60 | *ncrunch*/*
 61 | *.ncrunch*
 62 | .*crunch*.local.xml
 63 | 
 64 | # Installshield output folder 
 65 | [Ee]xpress
 66 | 
 67 | # DocProject is a documentation generator add-in
 68 | DocProject/buildhelp/
 69 | DocProject/Help/*.HxT
 70 | DocProject/Help/*.HxC
 71 | DocProject/Help/*.hhc
 72 | DocProject/Help/*.hhk
 73 | DocProject/Help/*.hhp
 74 | DocProject/Help/Html2
 75 | DocProject/Help/html
 76 | 
 77 | # Click-Once directory
 78 | publish
 79 | 
 80 | # Publish Web Output
 81 | *.Publish.xml
 82 | 
 83 | # Windows Azure Build Output
 84 | csx
 85 | *.build.csdef
 86 | 
 87 | # Others
 88 | [Bb]in
 89 | [Oo]bj
 90 | sql
 91 | TestResults
 92 | [Tt]est[Rr]esult*
 93 | *.Cache
 94 | ClientBin
 95 | [Ss]tyle[Cc]op.*
 96 | ~$*
 97 | *.dbmdl
 98 | Generated_Code #added for RIA/Silverlight projects
 99 | 
100 | # Backup & report files from converting an old project file to a newer
101 | # Visual Studio version. Backup files are not needed, because we have git ;-)
102 | _UpgradeReport_Files/
103 | Backup*/
104 | UpgradeLog*.XML


--------------------------------------------------------------------------------
/examples/c#/Count/App.config:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |      
4 |         
5 |     
6 | 


--------------------------------------------------------------------------------
/examples/c#/Count/Count.csproj:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |   
 5 |     Debug
 6 |     AnyCPU
 7 |     {FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}
 8 |     Exe
 9 |     Properties
10 |     Count
11 |     Count
12 |     v4.5
13 |     512
14 |   
15 |   
16 |     AnyCPU
17 |     true
18 |     full
19 |     false
20 |     ..\bin\
21 |     DEBUG;TRACE
22 |     prompt
23 |     4
24 |   
25 |   
26 |     AnyCPU
27 |     pdbonly
28 |     true
29 |     ..\bin\
30 |     TRACE
31 |     prompt
32 |     4
33 |   
34 |   
35 |     
36 |     
37 |     
38 |     
39 |     
40 |     
41 |     
42 |   
43 |   
44 |     
45 |     
46 |   
47 |   
48 |     
49 |   
50 |   
51 |   
58 | 


--------------------------------------------------------------------------------
/examples/c#/Count/Program.cs:
--------------------------------------------------------------------------------
 1 | using System;
 2 | using System.Linq;
 3 | using System.Threading;
 4 | 
 5 | namespace Count
 6 | {
 7 |     class Program
 8 |     {
 9 |         static void Main(string[] args)
10 |         {
11 |             foreach (var i in Enumerable.Range(1, 10))
12 |             {
13 |                 Console.WriteLine(i);
14 |                 Thread.Sleep(1000);
15 |             }
16 |         }
17 |     }
18 | }


--------------------------------------------------------------------------------
/examples/c#/Count/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
 1 | using System.Reflection;
 2 | using System.Runtime.CompilerServices;
 3 | using System.Runtime.InteropServices;
 4 | 
 5 | // General Information about an assembly is controlled through the following 
 6 | // set of attributes. Change these attribute values to modify the information
 7 | // associated with an assembly.
 8 | [assembly: AssemblyTitle("Count")]
 9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyProduct("Count")]
12 | [assembly: AssemblyTrademark("")]
13 | [assembly: AssemblyCulture("")]
14 | 
15 | // Setting ComVisible to false makes the types in this assembly not visible 
16 | // to COM components.  If you need to access a type in this assembly from 
17 | // COM, set the ComVisible attribute to true on that type.
18 | [assembly: ComVisible(false)]
19 | 
20 | // The following GUID is for the ID of the typelib if this project is exposed to COM
21 | [assembly: Guid("38cb6838-4839-498f-b09f-d0e67a2e9974")]
22 | 
23 | // Version information for an assembly consists of the following four values:
24 | //
25 | //      Major Version
26 | //      Minor Version 
27 | //      Build Number
28 | //      Revision
29 | //
30 | // You can specify all the values or you can default the Build and Revision Numbers 
31 | // by using the '*' as shown below:
32 | // [assembly: AssemblyVersion("1.0.*")]
33 | [assembly: AssemblyVersion("1.0.0.0")]
34 | [assembly: AssemblyFileVersion("1.0.0.0")]
35 | 


--------------------------------------------------------------------------------
/examples/c#/Echo/App.config:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |      
4 |         
5 |     
6 | 


--------------------------------------------------------------------------------
/examples/c#/Echo/Echo.csproj:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |   
 5 |     Debug
 6 |     AnyCPU
 7 |     {65414388-1058-414C-910F-CBD58E2B064A}
 8 |     Exe
 9 |     Properties
10 |     Echo
11 |     Echo
12 |     v4.5
13 |     512
14 |   
15 |   
16 |     AnyCPU
17 |     true
18 |     full
19 |     false
20 |     ..\bin\
21 |     DEBUG;TRACE
22 |     prompt
23 |     4
24 |   
25 |   
26 |     AnyCPU
27 |     pdbonly
28 |     true
29 |     ..\bin\
30 |     TRACE
31 |     prompt
32 |     4
33 |   
34 |   
35 |     
36 |     
37 |     
38 |     
39 |     
40 |     
41 |     
42 |   
43 |   
44 |     
45 |     
46 |   
47 |   
48 |     
49 |   
50 |   
51 |   
58 | 


--------------------------------------------------------------------------------
/examples/c#/Echo/Program.cs:
--------------------------------------------------------------------------------
 1 | using System;
 2 | 
 3 | namespace Echo
 4 | {
 5 |     class Program
 6 |     {
 7 |         static void Main(string[] args)
 8 |         {
 9 |             while (true)
10 |             {
11 |                 var msg = Console.ReadLine();
12 |                 Console.WriteLine(msg);
13 |             }
14 |         }
15 |     }
16 | }


--------------------------------------------------------------------------------
/examples/c#/Echo/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
 1 | using System.Reflection;
 2 | using System.Runtime.CompilerServices;
 3 | using System.Runtime.InteropServices;
 4 | 
 5 | // General Information about an assembly is controlled through the following 
 6 | // set of attributes. Change these attribute values to modify the information
 7 | // associated with an assembly.
 8 | [assembly: AssemblyTitle("Echo")]
 9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyProduct("Echo")]
12 | [assembly: AssemblyTrademark("")]
13 | [assembly: AssemblyCulture("")]
14 | 
15 | // Setting ComVisible to false makes the types in this assembly not visible 
16 | // to COM components.  If you need to access a type in this assembly from 
17 | // COM, set the ComVisible attribute to true on that type.
18 | [assembly: ComVisible(false)]
19 | 
20 | // The following GUID is for the ID of the typelib if this project is exposed to COM
21 | [assembly: Guid("6199e327-f4cd-438c-a6c5-87861f837fb1")]
22 | 
23 | // Version information for an assembly consists of the following four values:
24 | //
25 | //      Major Version
26 | //      Minor Version 
27 | //      Build Number
28 | //      Revision
29 | //
30 | // You can specify all the values or you can default the Build and Revision Numbers 
31 | // by using the '*' as shown below:
32 | // [assembly: AssemblyVersion("1.0.*")]
33 | [assembly: AssemblyVersion("1.0.0.0")]
34 | [assembly: AssemblyFileVersion("1.0.0.0")]
35 | 


--------------------------------------------------------------------------------
/examples/c#/Examples.sln:
--------------------------------------------------------------------------------
 1 | 
 2 | Microsoft Visual Studio Solution File, Format Version 12.00
 3 | # Visual Studio 2012
 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Echo", "Echo\Echo.csproj", "{65414388-1058-414C-910F-CBD58E2B064A}"
 5 | EndProject
 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Count", "Count\Count.csproj", "{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}"
 7 | EndProject
 8 | Global
 9 | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | 		Debug|Any CPU = Debug|Any CPU
11 | 		Release|Any CPU = Release|Any CPU
12 | 	EndGlobalSection
13 | 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | 		{65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | 		{65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | 		{65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | 		{65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.Build.0 = Release|Any CPU
18 | 		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | 		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | 		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | 		{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.Build.0 = Release|Any CPU
22 | 	EndGlobalSection
23 | 	GlobalSection(SolutionProperties) = preSolution
24 | 		HideSolutionNode = FALSE
25 | 	EndGlobalSection
26 | EndGlobal
27 | 


--------------------------------------------------------------------------------
/examples/c#/README.md:
--------------------------------------------------------------------------------
1 | ## Running the examples on Windows
2 | 
3 | 1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH)
4 | 2. open and build the **Examples.sln** solution
5 | 3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it
6 | 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 browser


--------------------------------------------------------------------------------
/examples/c#/run_count.cmd:
--------------------------------------------------------------------------------
1 | websocketd --port=8080 --devconsole bin\Count.exe


--------------------------------------------------------------------------------
/examples/c#/run_echo.cmd:
--------------------------------------------------------------------------------
1 | websocketd --port=8080 --devconsole bin\Echo.exe


--------------------------------------------------------------------------------
/examples/cgi-bin/README.txt:
--------------------------------------------------------------------------------
1 | This examples directory shows how websocketd can also serve CGI scripts via HTTP.
2 | 
3 | $ websocketd --port=1234 --cgidir=examples/cgi-bin
4 | # Then access http://localhost:1234/dump-env.sh
5 | 
6 | 
7 | You can also test the command files by running from the command line.
8 | 
9 | 


--------------------------------------------------------------------------------
/examples/cgi-bin/dump-env.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | 
 3 | # Copyright 2013 Joe Walnes and the websocketd team.
 4 | # All rights reserved.
 5 | # Use of this source code is governed by a BSD-style
 6 | # license that can be found in the LICENSE file.
 7 | 
 8 | # Standard CGI(ish) environment variables, as defined in
 9 | # http://tools.ietf.org/html/rfc3875
10 | NAMES="""
11 |   AUTH_TYPE
12 |   CONTENT_LENGTH
13 |   CONTENT_TYPE
14 |   GATEWAY_INTERFACE
15 |   PATH_INFO
16 |   PATH_TRANSLATED
17 |   QUERY_STRING
18 |   REMOTE_ADDR
19 |   REMOTE_HOST
20 |   REMOTE_IDENT
21 |   REMOTE_PORT
22 |   REMOTE_USER
23 |   REQUEST_METHOD
24 |   REQUEST_URI
25 |   SCRIPT_NAME
26 |   SERVER_NAME
27 |   SERVER_PORT
28 |   SERVER_PROTOCOL
29 |   SERVER_SOFTWARE
30 |   UNIQUE_ID
31 |   HTTPS
32 | """
33 | 
34 | echo "Content-type: text/plain"
35 | echo
36 | 
37 | for NAME in ${NAMES}
38 | do
39 | 	eval "value=\${${NAME}}"
40 | 	env -i "${NAME}=${value:-}"
41 | done
42 | 
43 | # Additional HTTP headers
44 | env | egrep '^(HTTP|SSL)_'
45 | 


--------------------------------------------------------------------------------
/examples/f#/.gitignore:
--------------------------------------------------------------------------------
  1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
  2 | [Bb]in/
  3 | [Oo]bj/
  4 | 
  5 | # mstest test results
  6 | TestResults
  7 | 
  8 | ## Ignore Visual Studio temporary files, build results, and
  9 | ## files generated by popular Visual Studio add-ons.
 10 | 
 11 | # User-specific files
 12 | *.suo
 13 | *.user
 14 | *.sln.docstates
 15 | 
 16 | # Build results
 17 | [Dd]ebug/
 18 | [Rr]elease/
 19 | build/
 20 | test/
 21 | deploy/
 22 | x64/
 23 | *_i.c
 24 | *_p.c
 25 | *.ilk
 26 | *.meta
 27 | *.obj
 28 | *.pch
 29 | *.pdb
 30 | *.pgc
 31 | *.pgd
 32 | *.rsp
 33 | *.sbr
 34 | *.tlb
 35 | *.tli
 36 | *.tlh
 37 | *.tmp
 38 | *.vspscc
 39 | *.vssscc
 40 | .builds
 41 | 
 42 | # Visual C++ cache files
 43 | ipch/
 44 | *.aps
 45 | *.ncb
 46 | *.opensdf
 47 | *.sdf
 48 | 
 49 | # Visual Studio profiler
 50 | *.psess
 51 | *.vsp
 52 | 
 53 | # Guidance Automation Toolkit
 54 | *.gpState
 55 | 
 56 | # ReSharper is a .NET coding add-in
 57 | _ReSharper*
 58 | 
 59 | # NCrunch
 60 | *ncrunch*/*
 61 | *.ncrunch*
 62 | .*crunch*.local.xml
 63 | 
 64 | # Installshield output folder 
 65 | [Ee]xpress
 66 | 
 67 | # DocProject is a documentation generator add-in
 68 | DocProject/buildhelp/
 69 | DocProject/Help/*.HxT
 70 | DocProject/Help/*.HxC
 71 | DocProject/Help/*.hhc
 72 | DocProject/Help/*.hhk
 73 | DocProject/Help/*.hhp
 74 | DocProject/Help/Html2
 75 | DocProject/Help/html
 76 | 
 77 | # Click-Once directory
 78 | publish
 79 | 
 80 | # Publish Web Output
 81 | *.Publish.xml
 82 | 
 83 | # Windows Azure Build Output
 84 | csx
 85 | *.build.csdef
 86 | 
 87 | # Others
 88 | [Bb]in
 89 | [Oo]bj
 90 | sql
 91 | TestResults
 92 | [Tt]est[Rr]esult*
 93 | *.Cache
 94 | ClientBin
 95 | [Ss]tyle[Cc]op.*
 96 | ~$*
 97 | *.dbmdl
 98 | Generated_Code #added for RIA/Silverlight projects
 99 | 
100 | # Backup & report files from converting an old project file to a newer
101 | # Visual Studio version. Backup files are not needed, because we have git ;-)
102 | _UpgradeReport_Files/
103 | Backup*/
104 | UpgradeLog*.XML


--------------------------------------------------------------------------------
/examples/f#/Count/App.config:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |      
 4 |         
 5 |     
 6 |     
 7 |       
 8 |         
 9 |           
10 |           
11 |           
12 |           
13 |           
14 |         
15 |       
16 |     	
17 | 


--------------------------------------------------------------------------------
/examples/f#/Count/Count.fsproj:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |   
 5 |     Debug
 6 |     AnyCPU
 7 |     2.0
 8 |     367c65fd-485b-45c0-9c0e-7ce455951eae
 9 |     Exe
10 |     Count
11 |     Count
12 |     v4.5
13 |     Count
14 |   
15 |   
16 |     true
17 |     full
18 |     false
19 |     false
20 |     ..\bin\
21 |     DEBUG;TRACE
22 |     3
23 |     AnyCPU
24 |     ..\bin\Count.XML
25 |     true
26 |   
27 |   
28 |     pdbonly
29 |     true
30 |     true
31 |     ..\bin\
32 |     TRACE
33 |     3
34 |     AnyCPU
35 |     ..\bin\Count.XML
36 |     true
37 |   
38 |   
39 |     
40 |     
41 |       True
42 |     
43 |     
44 |     
45 |     
46 |   
47 |   
48 |     
49 |     
50 |   
51 |   
52 |     11
53 |   
54 |   
55 |   
62 | 


--------------------------------------------------------------------------------
/examples/f#/Count/Program.fs:
--------------------------------------------------------------------------------
1 | open System
2 | open System.Threading
3 | 
4 | []
5 | let main argv = 
6 |     [| 1..10 |] |> Array.iter (Console.WriteLine >> (fun _ -> Thread.Sleep(1000)))
7 | 
8 |     0 // return an integer exit code
9 | 


--------------------------------------------------------------------------------
/examples/f#/Echo/App.config:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |      
 4 |         
 5 |     
 6 |     
 7 |       
 8 |         
 9 |           
10 |           
11 |           
12 |           
13 |           
14 |         
15 |       
16 |     	
17 | 


--------------------------------------------------------------------------------
/examples/f#/Echo/Echo.fsproj:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |   
 5 |     Debug
 6 |     AnyCPU
 7 |     2.0
 8 |     6f680332-caa0-447b-a87e-af272ded5701
 9 |     Exe
10 |     Echo
11 |     Echo
12 |     v4.5
13 |     Echo
14 |   
15 |   
16 |     true
17 |     full
18 |     false
19 |     false
20 |     ..\bin
21 |     DEBUG;TRACE
22 |     3
23 |     AnyCPU
24 |     ..\bin\Echo.XML
25 |     true
26 |   
27 |   
28 |     pdbonly
29 |     true
30 |     true
31 |     ..\bin
32 |     TRACE
33 |     3
34 |     AnyCPU
35 |     bin\Release\Echo.XML
36 |     true
37 |   
38 |   
39 |     
40 |     
41 |       True
42 |     
43 |     
44 |     
45 |     
46 |   
47 |   
48 |     
49 |     
50 |   
51 |   
52 |     11
53 |   
54 |   
55 |   
62 | 


--------------------------------------------------------------------------------
/examples/f#/Echo/Program.fs:
--------------------------------------------------------------------------------
 1 | open System
 2 | 
 3 | []
 4 | let main argv = 
 5 |     let rec recLoop () =
 6 |         Console.ReadLine() |> Console.WriteLine
 7 |         recLoop()
 8 | 
 9 |     recLoop()
10 |     
11 |     0 // return an integer exit code
12 | 


--------------------------------------------------------------------------------
/examples/f#/Examples.sln:
--------------------------------------------------------------------------------
 1 | 
 2 | Microsoft Visual Studio Solution File, Format Version 12.00
 3 | # Visual Studio 2012
 4 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Echo", "Echo\Echo.fsproj", "{6F680332-CAA0-447B-A87E-AF272DED5701}"
 5 | EndProject
 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Count", "Count\Count.fsproj", "{367C65FD-485B-45C0-9C0E-7CE455951EAE}"
 7 | EndProject
 8 | Global
 9 | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | 		Debug|Any CPU = Debug|Any CPU
11 | 		Release|Any CPU = Release|Any CPU
12 | 	EndGlobalSection
13 | 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | 		{6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | 		{6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | 		{6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | 		{6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.Build.0 = Release|Any CPU
18 | 		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | 		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | 		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | 		{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.Build.0 = Release|Any CPU
22 | 	EndGlobalSection
23 | 	GlobalSection(SolutionProperties) = preSolution
24 | 		HideSolutionNode = FALSE
25 | 	EndGlobalSection
26 | EndGlobal
27 | 


--------------------------------------------------------------------------------
/examples/f#/README.md:
--------------------------------------------------------------------------------
1 | ## Running the examples on Windows
2 | 
3 | 1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH)
4 | 2. open and build the **Examples.sln** solution
5 | 3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it
6 | 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 browser


--------------------------------------------------------------------------------
/examples/f#/run_count.cmd:
--------------------------------------------------------------------------------
1 | websocketd --port=8080 --devconsole bin\Count.exe


--------------------------------------------------------------------------------
/examples/f#/run_echo.cmd:
--------------------------------------------------------------------------------
1 | websocketd --port=8080 --devconsole bin\Echo.exe


--------------------------------------------------------------------------------
/examples/hack/README.md:
--------------------------------------------------------------------------------
 1 | This examples directory shows some examples written in [Hack](https://hacklang.org).
 2 | 
 3 | ### Requirements :
 4 | 
 5 | - [HHVM](https://github.com/facebook/hhvm) 3.30+
 6 | - [HSL ( Hack Standard Library )](https://github.com/hhvm/hsl) 3.30+
 7 | - [HSL Experimental](https://github.com/hhvm/hsl-experimental) 3.30+
 8 | 
 9 | You can also test the command files by running from the command line :
10 | 
11 | ```
12 | $ hhvm count.hh
13 | 1
14 | 2
15 | 3
16 | 4
17 | 5
18 | 6
19 | 7
20 | 8
21 | 9
22 | 10
23 | ```
24 | 


--------------------------------------------------------------------------------
/examples/hack/count.hh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/hhvm
 2 | >
10 | async function count_to_ten(): Awaitable {
11 |   $output = request_output();
12 |   for ($count = 1; $count <= 10; $count++) {
13 |     await $output->writeAsync(
14 |       Str\format("%d\n",$count)
15 |     );
16 | 
17 |     HH\Asio\usleep(500000);
18 |   }
19 | 
20 |   // flush output
21 |   await $output->flushAsync();
22 | 
23 |   exit(0);
24 | }
25 | 


--------------------------------------------------------------------------------
/examples/hack/dump-env.hh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/hhvm
 2 | >
 8 | async function dumpEnv(): Awaitable {
 9 |   // Standard CGI(ish) environment variables, as defined in
10 |   // http://tools.ietf.org/html/rfc3875
11 |   $names = keyset[
12 |     'AUTH_TYPE',
13 |     'CONTENT_LENGTH',
14 |     'CONTENT_TYPE',
15 |     'GATEWAY_INTERFACE',
16 |     'PATH_INFO',
17 |     'PATH_TRANSLATED',
18 |     'QUERY_STRING',
19 |     'REMOTE_ADDR',
20 |     'REMOTE_HOST',
21 |     'REMOTE_IDENT',
22 |     'REMOTE_PORT',
23 |     'REMOTE_USER',
24 |     'REQUEST_METHOD',
25 |     'REQUEST_URI',
26 |     'SCRIPT_NAME',
27 |     'SERVER_NAME',
28 |     'SERVER_PORT',
29 |     'SERVER_PROTOCOL',
30 |     'SERVER_SOFTWARE',
31 |     'UNIQUE_ID',
32 |     'HTTPS'
33 |   ];
34 | 
35 |   /* HH_IGNORE_ERROR[2050] using global variable */
36 |   $server = dict($_SERVER);
37 |   
38 |   $ouput = request_output();
39 | 
40 |   foreach($names as $name) {
41 |     await $output->writeAsync(
42 |       Str\format("%s = %s\n", $name, $server[$name] ?? '')
43 |     );
44 |   }
45 | 
46 |   // Additional HTTP headers
47 |   foreach($server as $k => $v) {
48 |      if ($k is string && Str\starts_with($k, 'HTTP_')) {
49 |         await $output->writeAsync(
50 |           Str\format("%s = %s\n", $k, $v as string)
51 |         );
52 |      }
53 |   }
54 | 
55 |   // flush output
56 |   await $output->flushAsync();
57 | 
58 |   exit(0);
59 | }
60 | 


--------------------------------------------------------------------------------
/examples/hack/greeter.hh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/hhvm
 2 | >
 8 | async function greeter(): Awaitable {
 9 |   // For each line FOO received on STDIN, respond with "Hello FOO!".
10 |   $input = IO\request_input();
11 |   $output = IO\request_output();
12 |   while(!$input->isEndOfFile()) {
13 |     await $ouput->writeAsync(
14 |       Str\format("Hello %s!\n", await $input->readLineAsync())
15 |     );
16 |   }
17 |     
18 |   // flush output
19 |   await $output->flushAsync();
20 |   
21 |   exit(0);
22 | }
23 | 


--------------------------------------------------------------------------------
/examples/haskell/README.md:
--------------------------------------------------------------------------------
 1 | ## Haskell examples
 2 | 
 3 | ### Count
 4 | 
 5 | Start the server with
 6 | 
 7 | ```
 8 | $ websocketd --port=8080 --devconsole --passenv PATH ./count.hs
 9 | ```
10 | 
11 | 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`.
12 | 
13 | ### Greeter
14 | 
15 | 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.
16 | 
17 | ```
18 | $ websocketd --port=8080 --devconsole --passenv PATH ./greeter.hs
19 | ```
20 | 


--------------------------------------------------------------------------------
/examples/haskell/count.hs:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env runhaskell
 2 | 
 3 | import Control.Monad (forM_)
 4 | import Control.Concurrent (threadDelay)
 5 | import System.IO (hFlush, stdout)
 6 | 
 7 | -- | Count from 1 to 10 with a sleep
 8 | main :: IO ()
 9 | main = forM_ [1 :: Int .. 10] $ \count -> do
10 |   print count
11 |   hFlush stdout
12 |   threadDelay 500000
13 | 


--------------------------------------------------------------------------------
/examples/haskell/greeter.hs:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env runhaskell
 2 | 
 3 | import Control.Monad (unless)
 4 | import System.IO (hFlush, stdout, stdin, hIsEOF)
 5 | 
 6 | -- | For each line FOO received on STDIN, respond with "Hello FOO!".
 7 | main :: IO ()
 8 | main = do
 9 |   eof <- hIsEOF stdin
10 |   unless eof $ do
11 |     line <- getLine
12 |     putStrLn $ "Hello " ++ line ++ "!"
13 |     hFlush stdout
14 |     main
15 | 


--------------------------------------------------------------------------------
/examples/html/count.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     websocketd count example
 5 |     
13 |   
14 |   
15 | 
16 |     
17 | 18 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/java/Count/Count.java: -------------------------------------------------------------------------------- 1 | public class Count { 2 | public static void main(String[] args) { 3 | for(int i = 1; i <= 10; i++) { 4 | System.out.println(i); 5 | try { 6 | Thread.sleep(1000); 7 | } catch (InterruptedException e) { 8 | e.printStackTrace(); 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/java/Count/count.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | javac Count.java 3 | java Count -------------------------------------------------------------------------------- /examples/java/Echo/Echo.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | public class Echo { 3 | public static void main(String[] args) { 4 | while(true) { 5 | try { 6 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 7 | String message = in.readLine(); 8 | System.out.println(message); 9 | } catch (IOException e) { 10 | e.printStackTrace(); 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /examples/java/Echo/echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | javac Echo.java 3 | java Echo -------------------------------------------------------------------------------- /examples/java/README.md: -------------------------------------------------------------------------------- 1 | ## Running the examples on Mac 2 | 3 | 1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your PATH 4 | 2. Echo Server: Run `websocketd --port=8080 --devconsole ./echo.sh` and then go to http://localhost:8080 to interact with it 5 | 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 browser -------------------------------------------------------------------------------- /examples/lua/README.md: -------------------------------------------------------------------------------- 1 | The examples demonstrate the use of websocketd with lua. There are two examples in the directory both very basic. 2 | 3 | 1. Greeter.lua simply echos back any input made from the client 4 | 2. json_ws.lua echos back any input from the client *after* converting it into a json string 5 | 6 | It is pretty simple to extend these examples into full fledged applications. All you need is an stdin input loop 7 | 8 | ``` 9 | local input = io.stdin:read() 10 | 11 | while input do 12 | -- do anything here 13 | 14 | -- update the input 15 | input = io.stdin:read() 16 | 17 | end 18 | 19 | 20 | ``` 21 | 22 | any thing you `print` goes out to the websocket client 23 | 24 | Libraries and third party modules can be used by the standard `require` statement in lua. 25 | 26 | ## Running the examples 27 | 28 | 29 | 30 | ##### 1. Download 31 | 32 | [Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`. 33 | 34 | ##### 2. Start a server: greeter 35 | 36 | Run `websocketd --port=8080 --devconsole lua ./greeter.lua` and then go to `http://localhost:8080` to interact with it 37 | 38 | ##### 3. Start a server: json_ws 39 | 40 | Run `websocketd --port=8080 --devconsole lua ./json_ws.lua` and then go to `http://localhost:8080` to interact with it 41 | 42 | If you are using luajit instead of lua you may run the examples like this 43 | (this assumes that you've got luajit in your path) 44 | 45 | `websocketd --port=8080 --devconsole luajit ./json_ws.lua` and then go to `http://localhost:8080` -------------------------------------------------------------------------------- /examples/lua/greeter.lua: -------------------------------------------------------------------------------- 1 | local input = io.stdin:read() 2 | while input do 3 | print(input) 4 | io.stdout:flush() 5 | input = io.stdin:read() 6 | end 7 | 8 | -------------------------------------------------------------------------------- /examples/lua/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2015 rxi 5 | -- 6 | -- This library is free software; you can redistribute it and/or modify it 7 | -- under the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | local json = { _version = "0.1.0" } 11 | 12 | ------------------------------------------------------------------------------- 13 | -- Encode 14 | ------------------------------------------------------------------------------- 15 | 16 | local encode 17 | 18 | local escape_char_map = { 19 | [ "\\" ] = "\\\\", 20 | [ "\"" ] = "\\\"", 21 | [ "\b" ] = "\\b", 22 | [ "\f" ] = "\\f", 23 | [ "\n" ] = "\\n", 24 | [ "\r" ] = "\\r", 25 | [ "\t" ] = "\\t", 26 | } 27 | 28 | local escape_char_map_inv = { [ "\\/" ] = "/" } 29 | for k, v in pairs(escape_char_map) do 30 | escape_char_map_inv[v] = k 31 | end 32 | 33 | 34 | local function escape_char(c) 35 | return escape_char_map[c] or string.format("\\u%04x", c:byte()) 36 | end 37 | 38 | 39 | local function encode_nil(val) 40 | return "null" 41 | end 42 | 43 | 44 | local function encode_table(val, stack) 45 | local res = {} 46 | stack = stack or {} 47 | 48 | -- Circular reference? 49 | if stack[val] then error("circular reference") end 50 | 51 | stack[val] = true 52 | 53 | if val[1] ~= nil or next(val) == nil then 54 | -- Treat as array -- check keys are valid and it is not sparse 55 | local n = 0 56 | for k in pairs(val) do 57 | if type(k) ~= "number" then 58 | error("invalid table: mixed or invalid key types") 59 | end 60 | n = n + 1 61 | end 62 | if n ~= #val then 63 | error("invalid table: sparse array") 64 | end 65 | -- Encode 66 | for i, v in ipairs(val) do 67 | table.insert(res, encode(v, stack)) 68 | end 69 | stack[val] = nil 70 | return "[" .. table.concat(res, ",") .. "]" 71 | 72 | else 73 | -- Treat as an object 74 | for k, v in pairs(val) do 75 | if type(k) ~= "string" then 76 | error("invalid table: mixed or invalid key types") 77 | end 78 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 79 | end 80 | stack[val] = nil 81 | return "{" .. table.concat(res, ",") .. "}" 82 | end 83 | end 84 | 85 | 86 | local function encode_string(val) 87 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 88 | end 89 | 90 | 91 | local function encode_number(val) 92 | -- Check for NaN, -inf and inf 93 | if val ~= val or val <= -math.huge or val >= math.huge then 94 | error("unexpected number value '" .. tostring(val) .. "'") 95 | end 96 | return string.format("%.14g", val) 97 | end 98 | 99 | 100 | local type_func_map = { 101 | [ "nil" ] = encode_nil, 102 | [ "table" ] = encode_table, 103 | [ "string" ] = encode_string, 104 | [ "number" ] = encode_number, 105 | [ "boolean" ] = tostring, 106 | } 107 | 108 | 109 | encode = function(val, stack) 110 | local t = type(val) 111 | local f = type_func_map[t] 112 | if f then 113 | return f(val, stack) 114 | end 115 | error("unexpected type '" .. t .. "'") 116 | end 117 | 118 | 119 | function json.encode(val) 120 | return ( encode(val) ) 121 | end 122 | 123 | 124 | ------------------------------------------------------------------------------- 125 | -- Decode 126 | ------------------------------------------------------------------------------- 127 | 128 | local parse 129 | 130 | local function create_set(...) 131 | local res = {} 132 | for i = 1, select("#", ...) do 133 | res[ select(i, ...) ] = true 134 | end 135 | return res 136 | end 137 | 138 | local space_chars = create_set(" ", "\t", "\r", "\n") 139 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 140 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 141 | local literals = create_set("true", "false", "null") 142 | 143 | local literal_map = { 144 | [ "true" ] = true, 145 | [ "false" ] = false, 146 | [ "null" ] = nil, 147 | } 148 | 149 | 150 | local function next_char(str, idx, set, negate) 151 | for i = idx, #str do 152 | if set[str:sub(i, i)] ~= negate then 153 | return i 154 | end 155 | end 156 | return #str + 1 157 | end 158 | 159 | 160 | local function decode_error(str, idx, msg) 161 | local line_count = 1 162 | local col_count = 1 163 | for i = 1, idx - 1 do 164 | col_count = col_count + 1 165 | if str:sub(i, i) == "\n" then 166 | line_count = line_count + 1 167 | col_count = 1 168 | end 169 | end 170 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 171 | end 172 | 173 | 174 | local function codepoint_to_utf8(n) 175 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 176 | local f = math.floor 177 | if n <= 0x7f then 178 | return string.char(n) 179 | elseif n <= 0x7ff then 180 | return string.char(f(n / 64) + 192, n % 64 + 128) 181 | elseif n <= 0xffff then 182 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 183 | elseif n <= 0x10ffff then 184 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 185 | f(n % 4096 / 64) + 128, n % 64 + 128) 186 | end 187 | error( string.format("invalid unicode codepoint '%x'", n) ) 188 | end 189 | 190 | 191 | local function parse_unicode_escape(s) 192 | local n1 = tonumber( s:sub(3, 6), 16 ) 193 | local n2 = tonumber( s:sub(9, 12), 16 ) 194 | -- Surrogate pair? 195 | if n2 then 196 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 197 | else 198 | return codepoint_to_utf8(n1) 199 | end 200 | end 201 | 202 | 203 | local function parse_string(str, i) 204 | local has_unicode_escape = false 205 | local has_surrogate_escape = false 206 | local has_escape = false 207 | local last 208 | for j = i + 1, #str do 209 | local x = str:byte(j) 210 | 211 | if x < 32 then 212 | decode_error(str, j, "control character in string") 213 | end 214 | 215 | if last == 92 then -- "\\" (escape char) 216 | if x == 117 then -- "u" (unicode escape sequence) 217 | local hex = str:sub(j + 1, j + 5) 218 | if not hex:find("%x%x%x%x") then 219 | decode_error(str, j, "invalid unicode escape in string") 220 | end 221 | if hex:find("^[dD][89aAbB]") then 222 | has_surrogate_escape = true 223 | else 224 | has_unicode_escape = true 225 | end 226 | else 227 | local c = string.char(x) 228 | if not escape_chars[c] then 229 | decode_error(str, j, "invalid escape char '" .. c .. "' in string") 230 | end 231 | has_escape = true 232 | end 233 | last = nil 234 | 235 | elseif x == 34 then -- '"' (end of string) 236 | local s = str:sub(i + 1, j - 1) 237 | if has_surrogate_escape then 238 | s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) 239 | end 240 | if has_unicode_escape then 241 | s = s:gsub("\\u....", parse_unicode_escape) 242 | end 243 | if has_escape then 244 | s = s:gsub("\\.", escape_char_map_inv) 245 | end 246 | return s, j + 1 247 | 248 | else 249 | last = x 250 | end 251 | end 252 | decode_error(str, i, "expected closing quote for string") 253 | end 254 | 255 | 256 | local function parse_number(str, i) 257 | local x = next_char(str, i, delim_chars) 258 | local s = str:sub(i, x - 1) 259 | local n = tonumber(s) 260 | if not n then 261 | decode_error(str, i, "invalid number '" .. s .. "'") 262 | end 263 | return n, x 264 | end 265 | 266 | 267 | local function parse_literal(str, i) 268 | local x = next_char(str, i, delim_chars) 269 | local word = str:sub(i, x - 1) 270 | if not literals[word] then 271 | decode_error(str, i, "invalid literal '" .. word .. "'") 272 | end 273 | return literal_map[word], x 274 | end 275 | 276 | 277 | local function parse_array(str, i) 278 | local res = {} 279 | local n = 1 280 | i = i + 1 281 | while 1 do 282 | local x 283 | i = next_char(str, i, space_chars, true) 284 | -- Empty / end of array? 285 | if str:sub(i, i) == "]" then 286 | i = i + 1 287 | break 288 | end 289 | -- Read token 290 | x, i = parse(str, i) 291 | res[n] = x 292 | n = n + 1 293 | -- Next token 294 | i = next_char(str, i, space_chars, true) 295 | local chr = str:sub(i, i) 296 | i = i + 1 297 | if chr == "]" then break end 298 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 299 | end 300 | return res, i 301 | end 302 | 303 | 304 | local function parse_object(str, i) 305 | local res = {} 306 | i = i + 1 307 | while 1 do 308 | local key, val 309 | i = next_char(str, i, space_chars, true) 310 | -- Empty / end of object? 311 | if str:sub(i, i) == "}" then 312 | i = i + 1 313 | break 314 | end 315 | -- Read key 316 | if str:sub(i, i) ~= '"' then 317 | decode_error(str, i, "expected string for key") 318 | end 319 | key, i = parse(str, i) 320 | -- Read ':' delimiter 321 | i = next_char(str, i, space_chars, true) 322 | if str:sub(i, i) ~= ":" then 323 | decode_error(str, i, "expected ':' after key") 324 | end 325 | i = next_char(str, i + 1, space_chars, true) 326 | -- Read value 327 | val, i = parse(str, i) 328 | -- Set 329 | res[key] = val 330 | -- Next token 331 | i = next_char(str, i, space_chars, true) 332 | local chr = str:sub(i, i) 333 | i = i + 1 334 | if chr == "}" then break end 335 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 336 | end 337 | return res, i 338 | end 339 | 340 | 341 | local char_func_map = { 342 | [ '"' ] = parse_string, 343 | [ "0" ] = parse_number, 344 | [ "1" ] = parse_number, 345 | [ "2" ] = parse_number, 346 | [ "3" ] = parse_number, 347 | [ "4" ] = parse_number, 348 | [ "5" ] = parse_number, 349 | [ "6" ] = parse_number, 350 | [ "7" ] = parse_number, 351 | [ "8" ] = parse_number, 352 | [ "9" ] = parse_number, 353 | [ "-" ] = parse_number, 354 | [ "t" ] = parse_literal, 355 | [ "f" ] = parse_literal, 356 | [ "n" ] = parse_literal, 357 | [ "[" ] = parse_array, 358 | [ "{" ] = parse_object, 359 | } 360 | 361 | 362 | parse = function(str, idx) 363 | local chr = str:sub(idx, idx) 364 | local f = char_func_map[chr] 365 | if f then 366 | return f(str, idx) 367 | end 368 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 369 | end 370 | 371 | 372 | function json.decode(str) 373 | if type(str) ~= "string" then 374 | error("expected argument of type string, got " .. type(str)) 375 | end 376 | return ( parse(str, next_char(str, 1, space_chars, true)) ) 377 | end 378 | 379 | 380 | return json 381 | -------------------------------------------------------------------------------- /examples/lua/json_ws.lua: -------------------------------------------------------------------------------- 1 | local input = io.stdin:read() 2 | local json = require("json") 3 | while input do 4 | print(json.encode({res="json",mess=input})) 5 | io.stdout:flush() 6 | input = io.stdin:read() 7 | end 8 | -------------------------------------------------------------------------------- /examples/nodejs/README.md: -------------------------------------------------------------------------------- 1 | ## Running the examples on Mac 2 | 3 | ##### 1. Download 4 | 5 | [Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`. 6 | 7 | ##### 2. Start a server: getter 8 | 9 | Run `websocketd --port=8080 --devconsole node greeter.js` and then go to `http://localhost:8080` to interact with it 10 | 11 | ##### 3. Start a server: counter 12 | 13 | Run `websocketd --port=8080 node count.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 14 | -------------------------------------------------------------------------------- /examples/nodejs/count.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var counter = 0; 3 | var echo = function(){ 4 | if (counter === 10){ 5 | return; 6 | } 7 | 8 | setTimeout(function(){ 9 | counter++; 10 | echo(); 11 | process.stdout.write(counter.toString() + "\n"); 12 | }, 500); 13 | } 14 | 15 | echo(); 16 | })(); 17 | -------------------------------------------------------------------------------- /examples/nodejs/greeter.js: -------------------------------------------------------------------------------- 1 | // from node.js sample 2 | // https://nodejs.org/api/process.html#process_process_stdin 3 | process.stdin.setEncoding('utf8'); 4 | 5 | process.stdin.on('readable', function() { 6 | var chunk = process.stdin.read(); 7 | if (chunk !== null) { 8 | process.stdout.write('data: ' + chunk); 9 | } 10 | }); -------------------------------------------------------------------------------- /examples/perl/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Perl. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/perl/count.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | use strict; 9 | 10 | # Autoflush output 11 | use IO::Handle; 12 | STDOUT->autoflush(1); 13 | 14 | use Time::HiRes qw(sleep); 15 | 16 | # Simple example script that counts to 10 at ~2Hz, then stops. 17 | for my $count (1 .. 10) { 18 | print "$count\n"; 19 | sleep 0.5; 20 | } 21 | -------------------------------------------------------------------------------- /examples/perl/dump-env.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | use strict; 9 | 10 | # Autoflush output 11 | use IO::Handle; 12 | STDOUT->autoflush(1); 13 | 14 | # Standard CGI(ish) environment variables, as defined in 15 | # http://tools.ietf.org/html/rfc3875 16 | my @names = qw( 17 | AUTH_TYPE 18 | CONTENT_LENGTH 19 | CONTENT_TYPE 20 | GATEWAY_INTERFACE 21 | PATH_INFO 22 | PATH_TRANSLATED 23 | QUERY_STRING 24 | REMOTE_ADDR 25 | REMOTE_HOST 26 | REMOTE_IDENT 27 | REMOTE_PORT 28 | REMOTE_USER 29 | REQUEST_METHOD 30 | REQUEST_URI 31 | SCRIPT_NAME 32 | SERVER_NAME 33 | SERVER_PORT 34 | SERVER_PROTOCOL 35 | SERVER_SOFTWARE 36 | UNIQUE_ID 37 | HTTPS 38 | ); 39 | 40 | for my $name (@names) { 41 | my $value = $ENV{$name} || ''; 42 | print "$name=$value\n"; 43 | } 44 | 45 | # Additional HTTP headers 46 | for my $name (keys(%ENV)) { 47 | if ($name =~ /^HTTP_/) { 48 | my $value = $ENV{$name}; 49 | print "$name=$value\n"; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/perl/greeter.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | use strict; 9 | 10 | # Autoflush output 11 | use IO::Handle; 12 | STDOUT->autoflush(1); 13 | 14 | # For each line FOO received on STDIN, respond with "Hello FOO!". 15 | while (<>) { 16 | chomp; # remove \n 17 | print "Hello $_!\n"; 18 | } 19 | -------------------------------------------------------------------------------- /examples/php/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in PHP. 2 | 3 | This relies on the CLI verson of PHP being installed and in the path. 4 | See http://www.php.net/manual/en/features.commandline.introduction.php 5 | 6 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/php/count.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | -------------------------------------------------------------------------------- /examples/php/dump-env.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | '; 37 | echo $name . '=' . $value . "\n"; 38 | } 39 | 40 | // Additional HTTP headers 41 | foreach ($_SERVER as $name => $value) { 42 | if (strpos($name, 'HTTP_') === 0) { 43 | echo $name . '=' . $value . "\n"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/php/greeter.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | -------------------------------------------------------------------------------- /examples/python/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Python. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/python/count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | from sys import stdout 9 | from time import sleep 10 | 11 | # Simple example script that counts to 10 at ~2Hz, then stops. 12 | for count in range(0, 10): 13 | print(count + 1) 14 | stdout.flush() # Remember to flush 15 | sleep(0.5) 16 | -------------------------------------------------------------------------------- /examples/python/dump-env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | import os 9 | from sys import stdout 10 | 11 | # Standard CGI(ish) environment variables, as defined in 12 | # http://tools.ietf.org/html/rfc3875 13 | var_names = [ 14 | 'AUTH_TYPE', 15 | 'CONTENT_LENGTH', 16 | 'CONTENT_TYPE', 17 | 'GATEWAY_INTERFACE', 18 | 'PATH_INFO', 19 | 'PATH_TRANSLATED', 20 | 'QUERY_STRING', 21 | 'REMOTE_ADDR', 22 | 'REMOTE_HOST', 23 | 'REMOTE_IDENT', 24 | 'REMOTE_PORT', 25 | 'REMOTE_USER', 26 | 'REQUEST_METHOD', 27 | 'REQUEST_URI', 28 | 'SCRIPT_NAME', 29 | 'SERVER_NAME', 30 | 'SERVER_PORT', 31 | 'SERVER_PROTOCOL', 32 | 'SERVER_SOFTWARE', 33 | 'UNIQUE_ID', 34 | 'HTTPS' 35 | ] 36 | for var_name in var_names: 37 | print('%s=%s' % (var_name, os.environ.get(var_name, ''))) 38 | stdout.flush() # Remember to flush 39 | 40 | # Additional HTTP headers 41 | for var_name in os.environ: 42 | if var_name.startswith('HTTP_'): 43 | print('%s=%s' % (var_name, os.environ[var_name])) 44 | stdout.flush() # Remember to flush 45 | -------------------------------------------------------------------------------- /examples/python/greeter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | from sys import stdin, stdout 9 | 10 | # For each line FOO received on STDIN, respond with "Hello FOO!". 11 | while True: 12 | line = stdin.readline().strip() 13 | print('Hello %s!' % line) 14 | stdout.flush() # Remember to flush 15 | -------------------------------------------------------------------------------- /examples/qjs/request-reply.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S qjs --module 2 | import * as std from "std"; 3 | 4 | let line; 5 | while ((line = std.in.getline()) != null) { 6 | console.log("RCVD: " + line) 7 | std.out.flush(); 8 | } 9 | -------------------------------------------------------------------------------- /examples/ruby/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Ruby. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/ruby/count.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Autoflush output 9 | STDOUT.sync = true 10 | 11 | # Simple example script that counts to 10 at ~2Hz, then stops. 12 | (1..10).each do |count| 13 | puts count 14 | sleep(0.5) 15 | end 16 | -------------------------------------------------------------------------------- /examples/ruby/dump-env.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Autoflush output 9 | STDOUT.sync = true 10 | 11 | # Standard CGI(ish) environment variables, as defined in 12 | # http://tools.ietf.org/html/rfc3875 13 | names = [ 14 | 'AUTH_TYPE', 15 | 'CONTENT_LENGTH', 16 | 'CONTENT_TYPE', 17 | 'GATEWAY_INTERFACE', 18 | 'PATH_INFO', 19 | 'PATH_TRANSLATED', 20 | 'QUERY_STRING', 21 | 'REMOTE_ADDR', 22 | 'REMOTE_HOST', 23 | 'REMOTE_IDENT', 24 | 'REMOTE_PORT', 25 | 'REMOTE_USER', 26 | 'REQUEST_METHOD', 27 | 'REQUEST_URI', 28 | 'SCRIPT_NAME', 29 | 'SERVER_NAME', 30 | 'SERVER_PORT', 31 | 'SERVER_PROTOCOL', 32 | 'SERVER_SOFTWARE', 33 | 'UNIQUE_ID', 34 | 'HTTPS', 35 | ] 36 | 37 | names.each do |name| 38 | value = ENV[name] || '' 39 | puts "#{name}=#{value}" 40 | end 41 | 42 | # Additional HTTP headers 43 | ENV.each do |name,value| 44 | puts "#{name}=#{value}" if name.start_with?('HTTP_') 45 | end 46 | -------------------------------------------------------------------------------- /examples/ruby/greeter.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Autoflush output 9 | STDOUT.sync = true 10 | 11 | # For each line FOO received on STDIN, respond with "Hello FOO!". 12 | while 1 13 | line = STDIN.readline.strip 14 | puts "Hello #{line}!" 15 | end 16 | -------------------------------------------------------------------------------- /examples/rust/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Rust. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/rust/count.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | use std::{thread, time}; 3 | 4 | // Simple example script that counts to 10 at ~2Hz, then stops. 5 | fn main() { 6 | for i in 1..11 { 7 | println!("{}", i); 8 | io::stdout().flush().ok().expect("Could not flush stdout"); 9 | thread::sleep(time::Duration::from_millis(500)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/rust/dump-env.rs: -------------------------------------------------------------------------------- 1 | // Standard CGI(ish) environment variables, as defined in 2 | // http://tools.ietf.org/html/rfc3875 3 | 4 | use std::env; 5 | 6 | const NAMES: &'static [&'static str] = &[ 7 | "AUTH_TYPE", 8 | "CONTENT_LENGTH", 9 | "CONTENT_TYPE", 10 | "GATEWAY_INTERFACE", 11 | "PATH_INFO", 12 | "PATH_TRANSLATED", 13 | "QUERY_STRING", 14 | "REMOTE_ADDR", 15 | "REMOTE_HOST", 16 | "REMOTE_IDENT", 17 | "REMOTE_PORT", 18 | "REMOTE_USER", 19 | "REQUEST_METHOD", 20 | "REQUEST_URI", 21 | "SCRIPT_NAME", 22 | "SERVER_NAME", 23 | "SERVER_PORT", 24 | "SERVER_PROTOCOL", 25 | "SERVER_SOFTWARE", 26 | "UNIQUE_ID", 27 | "HTTPS", 28 | ]; 29 | 30 | fn main() { 31 | for key in NAMES { 32 | let value = env::var(key).unwrap_or(String::from("")); 33 | println!("{}={}", key, value); 34 | } 35 | for (key, value) in env::vars() { 36 | if key.starts_with("HTTP_") { 37 | println!("{}={}", key, value); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/rust/greeter.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | // For each line FOO received on STDIN, respond with "Hello FOO!". 4 | fn main() { 5 | loop { 6 | let mut msg = String::new(); 7 | io::stdin() 8 | .read_line(&mut msg) 9 | .expect("Failed to read line"); 10 | let msg = msg.trim(); 11 | println!("Hello {}!", msg); 12 | io::stdout().flush().ok().expect("Could not flush stdout"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/swift/README.md: -------------------------------------------------------------------------------- 1 | ## Swift examples 2 | 3 | ### Count 4 | 5 | Run the following line and open "html/count.html" from the websocketd examples directory. 6 | 7 | ``` 8 | $ websocketd --port=8080 count.swift 9 | ``` 10 | 11 | ### Greeter 12 | 13 | Run the following line and open "http://localhost:8080" in your browser to interact with the greeter server. 14 | 15 | ``` 16 | $ websocketd --port=8080 --devconsole greeter.swift 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/swift/count.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xcrun -sdk macosx swift 2 | 3 | import AppKit 4 | 5 | for index in 1...10 { 6 | print(index) 7 | 8 | // Flush output 9 | fflush(__stdoutp) 10 | 11 | NSThread.sleepForTimeInterval(0.5) 12 | } -------------------------------------------------------------------------------- /examples/swift/greeter.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xcrun -sdk macosx swift 2 | 3 | import Foundation 4 | 5 | while(true){ 6 | var stdin = NSFileHandle.fileHandleWithStandardInput().availableData 7 | var line = NSString(data: stdin, encoding: NSUTF8StringEncoding)! 8 | var name = line.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) 9 | print("Hello \(name)!") 10 | fflush(__stdoutp) 11 | } 12 | -------------------------------------------------------------------------------- /examples/windows-jscript/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in JScript 2 | that can be run using Windows Script Hosting. 3 | 4 | Note that each .js file, also requires a .cmd file to launch it. 5 | The WebSocket should connect to ws://..../[example].cmd. 6 | 7 | http://en.wikipedia.org/wiki/Windows_Script_Host 8 | 9 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/windows-jscript/count.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\count.js 3 | -------------------------------------------------------------------------------- /examples/windows-jscript/count.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Simple example script that counts to 10 at ~2Hz, then stops. 7 | for (var i = 1; i <= 10; i++) { 8 | WScript.echo(i); 9 | WScript.sleep(500); 10 | } 11 | -------------------------------------------------------------------------------- /examples/windows-jscript/dump-env.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\dump-env.js 3 | -------------------------------------------------------------------------------- /examples/windows-jscript/dump-env.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | 7 | // Standard CGI(ish) environment variables, as defined in 8 | // http://tools.ietf.org/html/rfc3875 9 | var names = [ 10 | 'AUTH_TYPE', 11 | 'CONTENT_LENGTH', 12 | 'CONTENT_TYPE', 13 | 'GATEWAY_INTERFACE', 14 | 'PATH_INFO', 15 | 'PATH_TRANSLATED', 16 | 'QUERY_STRING', 17 | 'REMOTE_ADDR', 18 | 'REMOTE_HOST', 19 | 'REMOTE_IDENT', 20 | 'REMOTE_PORT', 21 | 'REMOTE_USER', 22 | 'REQUEST_METHOD', 23 | 'REQUEST_URI', 24 | 'SCRIPT_NAME', 25 | 'SERVER_NAME', 26 | 'SERVER_PORT', 27 | 'SERVER_PROTOCOL', 28 | 'SERVER_SOFTWARE', 29 | 'UNIQUE_ID', 30 | 'HTTPS' 31 | ]; 32 | 33 | var shell = WScript.CreateObject("WScript.Shell"); 34 | var env = shell.Environment('PROCESS'); 35 | 36 | for (var i = 0; i < names.length; i++) { 37 | var name = names[i]; 38 | var value = env(name) || ''; 39 | WScript.echo(name + '=' + value); 40 | } 41 | 42 | for(var en = new Enumerator(env); !en.atEnd(); en.moveNext()) { 43 | var item = en.item(); 44 | if (item.indexOf('HTTP_') == 0) { 45 | WScript.Echo(item); 46 | } 47 | } -------------------------------------------------------------------------------- /examples/windows-jscript/greeter.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\greeter.js 3 | -------------------------------------------------------------------------------- /examples/windows-jscript/greeter.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | 7 | // For each line FOO received on STDIN, respond with "Hello FOO!". 8 | while (true) { 9 | var input= WScript.stdIn.readLine(); 10 | WScript.echo('Hello ' + input+ '!'); 11 | } 12 | -------------------------------------------------------------------------------- /examples/windows-vbscript/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in VBScript 2 | that can be run using Windows Script Hosting. 3 | 4 | Note that each .vbs file, also requires a .cmd file to launch it. 5 | The WebSocket should connect to ws://..../[example].cmd. 6 | 7 | http://en.wikipedia.org/wiki/Windows_Script_Host 8 | 9 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/windows-vbscript/count.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\count.vbs 3 | -------------------------------------------------------------------------------- /examples/windows-vbscript/count.vbs: -------------------------------------------------------------------------------- 1 | ' Copyright 2013 Joe Walnes and the websocketd team. 2 | ' All rights reserved. 3 | ' Use of this source code is governed by a BSD-style 4 | ' license that can be found in the LICENSE file. 5 | 6 | 7 | ' Simple example script that counts to 10 at ~2Hz, then stops. 8 | for i = 1 to 10 9 | WScript.echo i 10 | WScript.sleep 500 11 | next 12 | -------------------------------------------------------------------------------- /examples/windows-vbscript/dump-env.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\dump-env.vbs 3 | -------------------------------------------------------------------------------- /examples/windows-vbscript/dump-env.vbs: -------------------------------------------------------------------------------- 1 | ' Copyright 2013 Joe Walnes and the websocketd team. 2 | ' All rights reserved. 3 | ' Use of this source code is governed by a BSD-style 4 | ' license that can be found in the LICENSE file. 5 | 6 | 7 | ' Standard CGI(ish) environment variables, as defined in 8 | ' http://tools.ietf.org/html/rfc3875 9 | names = Array(_ 10 | "AUTH_TYPE", _ 11 | "CONTENT_LENGTH", _ 12 | "CONTENT_TYPE", _ 13 | "GATEWAY_INTERFACE", _ 14 | "PATH_INFO", _ 15 | "PATH_TRANSLATED", _ 16 | "QUERY_STRING", _ 17 | "REMOTE_ADDR", _ 18 | "REMOTE_HOST", _ 19 | "REMOTE_IDENT", _ 20 | "REMOTE_PORT", _ 21 | "REMOTE_USER", _ 22 | "REQUEST_METHOD", _ 23 | "REQUEST_URI", _ 24 | "SCRIPT_NAME", _ 25 | "SERVER_NAME", _ 26 | "SERVER_PORT", _ 27 | "SERVER_PROTOCOL", _ 28 | "SERVER_SOFTWARE", _ 29 | "UNIQUE_ID", _ 30 | "HTTPS"_ 31 | ) 32 | 33 | set shell = WScript.CreateObject("WScript.Shell") 34 | set env = shell.Environment("PROCESS") 35 | 36 | for each name in names 37 | value = env(name) 38 | if value = "" then 39 | value = "" 40 | end if 41 | WScript.echo name & "=" & value 42 | next 43 | 44 | for each item in env 45 | if instr(1, item, "HTTP_", 1) = 1 then 46 | WScript.Echo item 47 | end if 48 | next 49 | -------------------------------------------------------------------------------- /examples/windows-vbscript/greeter.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\greeter.vbs 3 | -------------------------------------------------------------------------------- /examples/windows-vbscript/greeter.vbs: -------------------------------------------------------------------------------- 1 | ' Copyright 2013 Joe Walnes and the websocketd team. 2 | ' All rights reserved. 3 | ' Use of this source code is governed by a BSD-style 4 | ' license that can be found in the LICENSE file. 5 | 6 | 7 | ' For each line FOO received on STDIN, respond with "Hello FOO!". 8 | while true 9 | line = WScript.stdIn.readLine 10 | WScript.echo "Hello " & line & "!" 11 | wend 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/joewalnes/websocketd 2 | 3 | go 1.15 4 | 5 | require github.com/gorilla/websocket v1.4.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 2 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 3 | -------------------------------------------------------------------------------- /help.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | help = ` 17 | {{binary}} ({{version}}) 18 | 19 | {{binary}} is a command line tool that will allow any executable program 20 | that accepts input on stdin and produces output on stdout to be turned into 21 | a WebSocket server. 22 | 23 | Usage: 24 | 25 | Export a single executable program a WebSocket server: 26 | {{binary}} [options] COMMAND [command args] 27 | 28 | Or, export an entire directory of executables as WebSocket endpoints: 29 | {{binary}} [options] --dir=SOMEDIR 30 | 31 | Options: 32 | 33 | --port=PORT HTTP port to listen on. 34 | 35 | --address=ADDRESS Address to bind to (multiple options allowed) 36 | Use square brackets to specify IPv6 address. 37 | Default: "" (all) 38 | 39 | --sameorigin={true,false} Restrict (HTTP 403) protocol upgrades if the 40 | Origin header does not match to requested HTTP 41 | Host. Default: false. 42 | 43 | --origin=host[:port][,host[:port]...] 44 | Restrict (HTTP 403) protocol upgrades if the 45 | Origin header does not match to one of the host 46 | and port combinations listed. If the port is not 47 | specified, any port number will match. 48 | Default: "" (allow any origin) 49 | 50 | --ssl Listen for HTTPS socket instead of HTTP. 51 | --sslcert=FILE All three options must be used or all of 52 | --sslkey=FILE them should be omitted. 53 | 54 | --redirport=PORT Open alternative port and redirect HTTP traffic 55 | from it to canonical address (mostly useful 56 | for HTTPS-only configurations to redirect HTTP 57 | traffic) 58 | 59 | --passenv VAR[,VAR...] Lists environment variables allowed to be 60 | passed to executed scripts. Does not work for 61 | Windows since all the variables are kept there. 62 | 63 | --binary={true,false} Switches communication to binary, process reads 64 | send to browser as blobs and all reads from the 65 | browser are immediately flushed to the process. 66 | Default: false 67 | 68 | --reverselookup={true,false} Perform DNS reverse lookups on remote clients. 69 | Default: false 70 | 71 | --dir=DIR Allow all scripts in the local directory 72 | to be accessed as WebSockets. If using this, 73 | option, then the standard program and args 74 | options should not be specified. 75 | 76 | --staticdir=DIR Serve static files in this directory over HTTP. 77 | 78 | --cgidir=DIR Serve CGI scripts in this directory over HTTP. 79 | 80 | --maxforks=N Limit number of processes that websocketd is 81 | able to execute with WS and CGI handlers. 82 | When maxforks reached the server will be 83 | rejecting requests that require executing 84 | another process (unlimited when 0 or negative). 85 | Default: 0 86 | 87 | --closems=milliseconds Specifies additional time process needs to gracefully 88 | finish before websocketd will send termination signals 89 | to it. Default: 0 (signals sent after 100ms, 250ms, 90 | and 500ms of waiting) 91 | 92 | --header="..." Set custom HTTP header to each answer. For 93 | example: --header="Server: someserver/0.0.1" 94 | 95 | --header-ws="...." Same as --header, just applies to only those 96 | responses that indicate upgrade of TCP connection 97 | to a WebSockets protocol. 98 | 99 | --header-http="...." Same as --header, just applies to only to plain 100 | HTTP responses that do not indicate WebSockets 101 | upgrade 102 | 103 | 104 | --help Print help and exit. 105 | 106 | --version Print version and exit. 107 | 108 | --license Print license and exit. 109 | 110 | --devconsole Enable interactive development console. 111 | This enables you to access the websocketd 112 | server with a web-browser and use a 113 | user interface to quickly test WebSocket 114 | endpoints. For example, to test an 115 | endpoint at ws://[host]/foo, you can 116 | visit http://[host]/foo in your browser. 117 | This flag cannot be used in conjunction 118 | with --staticdir or --cgidir. 119 | 120 | --loglevel=LEVEL Log level to use (default access). 121 | From most to least verbose: 122 | debug, trace, access, info, error, fatal 123 | 124 | Full documentation at http://websocketd.com/ 125 | 126 | Copyright 2013 Joe Walnes and the websocketd team. All rights reserved. 127 | BSD license: Run '{{binary}} --license' for details. 128 | ` 129 | short = ` 130 | Usage: 131 | 132 | Export a single executable program a WebSocket server: 133 | {{binary}} [options] COMMAND [command args] 134 | 135 | Or, export an entire directory of executables as WebSocket endpoints: 136 | {{binary}} [options] --dir=SOMEDIR 137 | 138 | Or, show extended help message using: 139 | {{binary}} --help 140 | ` 141 | ) 142 | 143 | func get_help_message(content string) string { 144 | msg := strings.Trim(content, " \n") 145 | msg = strings.Replace(msg, "{{binary}}", HelpProcessName(), -1) 146 | return strings.Replace(msg, "{{version}}", Version(), -1) 147 | } 148 | 149 | func HelpProcessName() string { 150 | binary := os.Args[0] 151 | if strings.Contains(binary, "/go-build") { // this was run using "go run", let's use something appropriate 152 | binary = "websocketd" 153 | } else { 154 | binary = filepath.Base(binary) 155 | } 156 | return binary 157 | } 158 | 159 | func PrintHelp() { 160 | fmt.Fprintf(os.Stderr, "%s\n", get_help_message(help)) 161 | } 162 | 163 | func ShortHelp() { 164 | // Shown after some error 165 | fmt.Fprintf(os.Stderr, "\n%s\n", get_help_message(short)) 166 | } 167 | -------------------------------------------------------------------------------- /libwebsocketd/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "time" 10 | ) 11 | 12 | type Config struct { 13 | // base initiaization fields 14 | StartupTime time.Time // Server startup time (used for dev console caching). 15 | CommandName string // Command to execute. 16 | CommandArgs []string // Additional args to pass to command. 17 | ServerSoftware string // Value to pass to SERVER_SOFTWARE environment variable (e.g. websocketd/1.2.3). 18 | CloseMs uint // Milliseconds to start sending signals 19 | 20 | HandshakeTimeout time.Duration // time to finish handshake (default 1500ms) 21 | 22 | // settings 23 | Binary bool // Use binary communication (send data in chunks they are read from process) 24 | ReverseLookup bool // Perform reverse DNS lookups on hostnames (useful, but slower). 25 | Ssl bool // websocketd works with --ssl which means TLS is in use 26 | ScriptDir string // Base directory for websocket scripts. 27 | UsingScriptDir bool // Are we running with a script dir. 28 | StaticDir string // If set, static files will be served from this dir over HTTP. 29 | CgiDir string // If set, CGI scripts will be served from this dir over HTTP. 30 | DevConsole bool // Enable dev console. This disables StaticDir and CgiDir. 31 | AllowOrigins []string // List of allowed origin addresses for websocket upgrade. 32 | SameOrigin bool // If set, requires websocket upgrades to be performed from same origin only. 33 | Headers []string 34 | HeadersWs []string 35 | HeadersHTTP []string 36 | 37 | // created environment 38 | Env []string // Additional environment variables to pass to process ("key=value"). 39 | ParentEnv []string // Variables kept from os.Environ() before sanitizing it for subprocess. 40 | } 41 | -------------------------------------------------------------------------------- /libwebsocketd/console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | // Although this isn't particularly elegant, it's the simplest 9 | // way to embed the console content into the binary. 10 | 11 | // Note that the console is served by a single HTML file containing 12 | // all CSS and JS inline. 13 | // We can get by without jQuery or Bootstrap for this one ;). 14 | 15 | const ( 16 | defaultConsoleContent = ` 17 | 18 | 25 | 26 | 27 | 28 | 29 | websocketd console 30 | 31 | 137 | 138 |
139 | 140 | 141 |
142 | 143 |
144 |
145 | 146 |
147 |
148 | 149 | 150 |
151 |
152 | send » 153 | 154 |
155 |
156 | 157 | 346 | 347 | ` 348 | ) 349 | 350 | var ConsoleContent = defaultConsoleContent 351 | -------------------------------------------------------------------------------- /libwebsocketd/endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | type Endpoint interface { 9 | StartReading() 10 | Terminate() 11 | Output() chan []byte 12 | Send([]byte) bool 13 | } 14 | 15 | func PipeEndpoints(e1, e2 Endpoint) { 16 | e1.StartReading() 17 | e2.StartReading() 18 | 19 | defer e1.Terminate() 20 | defer e2.Terminate() 21 | for { 22 | select { 23 | case msgOne, ok := <-e1.Output(): 24 | if !ok || !e2.Send(msgOne) { 25 | return 26 | } 27 | case msgTwo, ok := <-e2.Output(): 28 | if !ok || !e1.Send(msgTwo) { 29 | return 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libwebsocketd/endpoint_test.go: -------------------------------------------------------------------------------- 1 | package libwebsocketd 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var eol_tests = []string{ 10 | "", "\n", "\r\n", "ok\n", "ok\n", 11 | "quite long string for our test\n", 12 | "quite long string for our test\r\n", 13 | } 14 | 15 | var eol_answers = []string{ 16 | "", "", "", "ok", "ok", 17 | "quite long string for our test", "quite long string for our test", 18 | } 19 | 20 | func TestTrimEOL(t *testing.T) { 21 | for n := 0; n < len(eol_tests); n++ { 22 | answ := trimEOL([]byte(eol_tests[n])) 23 | if string(answ) != eol_answers[n] { 24 | t.Errorf("Answer '%s' did not match predicted '%s'", answ, eol_answers[n]) 25 | } 26 | } 27 | } 28 | 29 | func BenchmarkTrimEOL(b *testing.B) { 30 | for n := 0; n < b.N; n++ { 31 | trimEOL([]byte(eol_tests[n%len(eol_tests)])) 32 | } 33 | } 34 | 35 | type TestEndpoint struct { 36 | limit int 37 | prefix string 38 | c chan []byte 39 | result []string 40 | } 41 | 42 | func (e *TestEndpoint) StartReading() { 43 | go func() { 44 | for i := 0; i < e.limit; i++ { 45 | e.c <- []byte(e.prefix + strconv.Itoa(i)) 46 | } 47 | time.Sleep(time.Millisecond) // should be enough for smaller channel to catch up with long one 48 | close(e.c) 49 | }() 50 | } 51 | 52 | func (e *TestEndpoint) Terminate() { 53 | } 54 | 55 | func (e *TestEndpoint) Output() chan []byte { 56 | return e.c 57 | } 58 | 59 | func (e *TestEndpoint) Send(msg []byte) bool { 60 | e.result = append(e.result, string(msg)) 61 | return true 62 | } 63 | 64 | func TestEndpointPipe(t *testing.T) { 65 | one := &TestEndpoint{2, "one:", make(chan []byte), make([]string, 0)} 66 | two := &TestEndpoint{4, "two:", make(chan []byte), make([]string, 0)} 67 | PipeEndpoints(one, two) 68 | if len(one.result) != 4 || len(two.result) != 2 { 69 | t.Errorf("Invalid lengths, should be 4 and 2: %v %v", one.result, two.result) 70 | } else if one.result[0] != "two:0" || two.result[0] != "one:0" { 71 | t.Errorf("Invalid first results, should be two:0 and one:0: %#v %#v", one.result[0], two.result[0]) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /libwebsocketd/env.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "fmt" 10 | "net/http" 11 | "strings" 12 | ) 13 | 14 | const ( 15 | gatewayInterface = "websocketd-CGI/0.1" 16 | ) 17 | 18 | var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") 19 | var headerDashToUnderscore = strings.NewReplacer("-", "_") 20 | 21 | func createEnv(handler *WebsocketdHandler, req *http.Request, log *LogScope) []string { 22 | headers := req.Header 23 | 24 | url := req.URL 25 | 26 | serverName, serverPort, err := tellHostPort(req.Host, handler.server.Config.Ssl) 27 | if err != nil { 28 | // This does mean that we cannot detect port from Host: header... Just keep going with "", guessing is bad. 29 | log.Debug("env", "Host port detection error: %s", err) 30 | serverPort = "" 31 | } 32 | 33 | standardEnvCount := 20 34 | if handler.server.Config.Ssl { 35 | standardEnvCount += 1 36 | } 37 | 38 | parentLen := len(handler.server.Config.ParentEnv) 39 | env := make([]string, 0, len(headers)+standardEnvCount+parentLen+len(handler.server.Config.Env)) 40 | 41 | // This variable could be rewritten from outside 42 | env = appendEnv(env, "SERVER_SOFTWARE", handler.server.Config.ServerSoftware) 43 | 44 | parentStarts := len(env) 45 | env = append(env, handler.server.Config.ParentEnv...) 46 | 47 | // IMPORTANT ---> Adding a header? Make sure standardEnvCount (above) is up to date. 48 | 49 | // Standard CGI specification headers. 50 | // As defined in http://tools.ietf.org/html/rfc3875 51 | env = appendEnv(env, "REMOTE_ADDR", handler.RemoteInfo.Addr) 52 | env = appendEnv(env, "REMOTE_HOST", handler.RemoteInfo.Host) 53 | env = appendEnv(env, "SERVER_NAME", serverName) 54 | env = appendEnv(env, "SERVER_PORT", serverPort) 55 | env = appendEnv(env, "SERVER_PROTOCOL", req.Proto) 56 | env = appendEnv(env, "GATEWAY_INTERFACE", gatewayInterface) 57 | env = appendEnv(env, "REQUEST_METHOD", req.Method) 58 | env = appendEnv(env, "SCRIPT_NAME", handler.URLInfo.ScriptPath) 59 | env = appendEnv(env, "PATH_INFO", handler.URLInfo.PathInfo) 60 | env = appendEnv(env, "PATH_TRANSLATED", url.Path) 61 | env = appendEnv(env, "QUERY_STRING", url.RawQuery) 62 | 63 | // Not supported, but we explicitly clear them so we don't get leaks from parent environment. 64 | env = appendEnv(env, "AUTH_TYPE", "") 65 | env = appendEnv(env, "CONTENT_LENGTH", "") 66 | env = appendEnv(env, "CONTENT_TYPE", "") 67 | env = appendEnv(env, "REMOTE_IDENT", "") 68 | env = appendEnv(env, "REMOTE_USER", "") 69 | 70 | // Non standard, but commonly used headers. 71 | env = appendEnv(env, "UNIQUE_ID", handler.Id) // Based on Apache mod_unique_id. 72 | env = appendEnv(env, "REMOTE_PORT", handler.RemoteInfo.Port) 73 | env = appendEnv(env, "REQUEST_URI", url.RequestURI()) // e.g. /foo/blah?a=b 74 | 75 | // The following variables are part of the CGI specification, but are optional 76 | // and not set by websocketd: 77 | // 78 | // AUTH_TYPE, REMOTE_USER, REMOTE_IDENT 79 | // -- Authentication left to the underlying programs. 80 | // 81 | // CONTENT_LENGTH, CONTENT_TYPE 82 | // -- makes no sense for WebSocket connections. 83 | // 84 | // SSL_* 85 | // -- SSL variables are not supported, HTTPS=on added for websocketd running with --ssl 86 | 87 | if handler.server.Config.Ssl { 88 | env = appendEnv(env, "HTTPS", "on") 89 | } 90 | 91 | if log.MinLevel == LogDebug { 92 | for i, v := range env { 93 | if i >= parentStarts && i < parentLen+parentStarts { 94 | log.Debug("env", "Parent envvar: %v", v) 95 | } else { 96 | log.Debug("env", "Std. variable: %v", v) 97 | } 98 | } 99 | } 100 | 101 | for k, hdrs := range headers { 102 | header := fmt.Sprintf("HTTP_%s", headerDashToUnderscore.Replace(k)) 103 | env = appendEnv(env, header, hdrs...) 104 | log.Debug("env", "Header variable %s", env[len(env)-1]) 105 | } 106 | 107 | for _, v := range handler.server.Config.Env { 108 | env = append(env, v) 109 | log.Debug("env", "External variable: %s", v) 110 | } 111 | 112 | return env 113 | } 114 | 115 | // Adapted from net/http/header.go 116 | func appendEnv(env []string, k string, v ...string) []string { 117 | if len(v) == 0 { 118 | return env 119 | } 120 | 121 | vCleaned := make([]string, 0, len(v)) 122 | for _, val := range v { 123 | vCleaned = append(vCleaned, strings.TrimSpace(headerNewlineToSpace.Replace(val))) 124 | } 125 | return append(env, fmt.Sprintf("%s=%s", 126 | strings.ToUpper(k), 127 | strings.Join(vCleaned, ", "))) 128 | } 129 | -------------------------------------------------------------------------------- /libwebsocketd/handler.go: -------------------------------------------------------------------------------- 1 | package libwebsocketd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/gorilla/websocket" 15 | ) 16 | 17 | var ScriptNotFoundError = errors.New("script not found") 18 | 19 | // WebsocketdHandler is a single request information and processing structure, it handles WS requests out of all that daemon can handle (static, cgi, devconsole) 20 | type WebsocketdHandler struct { 21 | server *WebsocketdServer 22 | 23 | Id string 24 | *RemoteInfo 25 | *URLInfo // TODO: I cannot find where it's used except in one single place as URLInfo.FilePath 26 | Env []string 27 | 28 | command string 29 | } 30 | 31 | // NewWebsocketdHandler constructs the struct and parses all required things in it... 32 | func NewWebsocketdHandler(s *WebsocketdServer, req *http.Request, log *LogScope) (wsh *WebsocketdHandler, err error) { 33 | wsh = &WebsocketdHandler{server: s, Id: generateId()} 34 | log.Associate("id", wsh.Id) 35 | 36 | wsh.RemoteInfo, err = GetRemoteInfo(req.RemoteAddr, s.Config.ReverseLookup) 37 | if err != nil { 38 | log.Error("session", "Could not understand remote address '%s': %s", req.RemoteAddr, err) 39 | return nil, err 40 | } 41 | log.Associate("remote", wsh.RemoteInfo.Host) 42 | 43 | wsh.URLInfo, err = GetURLInfo(req.URL.Path, s.Config) 44 | if err != nil { 45 | log.Access("session", "NOT FOUND: %s", err) 46 | return nil, err 47 | } 48 | 49 | wsh.command = s.Config.CommandName 50 | if s.Config.UsingScriptDir { 51 | wsh.command = wsh.URLInfo.FilePath 52 | } 53 | log.Associate("command", wsh.command) 54 | 55 | wsh.Env = createEnv(wsh, req, log) 56 | 57 | return wsh, nil 58 | } 59 | 60 | func (wsh *WebsocketdHandler) accept(ws *websocket.Conn, log *LogScope) { 61 | defer ws.Close() 62 | 63 | log.Access("session", "CONNECT") 64 | defer log.Access("session", "DISCONNECT") 65 | 66 | launched, err := launchCmd(wsh.command, wsh.server.Config.CommandArgs, wsh.Env) 67 | if err != nil { 68 | log.Error("process", "Could not launch process %s %s (%s)", wsh.command, strings.Join(wsh.server.Config.CommandArgs, " "), err) 69 | return 70 | } 71 | 72 | log.Associate("pid", strconv.Itoa(launched.cmd.Process.Pid)) 73 | 74 | binary := wsh.server.Config.Binary 75 | process := NewProcessEndpoint(launched, binary, log) 76 | if cms := wsh.server.Config.CloseMs; cms != 0 { 77 | process.closetime += time.Duration(cms) * time.Millisecond 78 | } 79 | wsEndpoint := NewWebSocketEndpoint(ws, binary, log) 80 | 81 | PipeEndpoints(process, wsEndpoint) 82 | } 83 | 84 | // RemoteInfo holds information about remote http client 85 | type RemoteInfo struct { 86 | Addr, Host, Port string 87 | } 88 | 89 | // GetRemoteInfo creates RemoteInfo structure and fills its fields appropriately 90 | func GetRemoteInfo(remote string, doLookup bool) (*RemoteInfo, error) { 91 | addr, port, err := net.SplitHostPort(remote) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | var host string 97 | if doLookup { 98 | hosts, err := net.LookupAddr(addr) 99 | if err != nil || len(hosts) == 0 { 100 | host = addr 101 | } else { 102 | host = hosts[0] 103 | } 104 | } else { 105 | host = addr 106 | } 107 | 108 | return &RemoteInfo{Addr: addr, Host: host, Port: port}, nil 109 | } 110 | 111 | // URLInfo - structure carrying information about current request and it's mapping to filesystem 112 | type URLInfo struct { 113 | ScriptPath string 114 | PathInfo string 115 | FilePath string 116 | } 117 | 118 | // GetURLInfo is a function that parses path and provides URL info according to libwebsocketd.Config fields 119 | func GetURLInfo(path string, config *Config) (*URLInfo, error) { 120 | if !config.UsingScriptDir { 121 | return &URLInfo{"/", path, ""}, nil 122 | } 123 | 124 | parts := strings.Split(path[1:], "/") 125 | urlInfo := &URLInfo{} 126 | 127 | for i, part := range parts { 128 | urlInfo.ScriptPath = strings.Join([]string{urlInfo.ScriptPath, part}, "/") 129 | urlInfo.FilePath = filepath.Join(config.ScriptDir, urlInfo.ScriptPath) 130 | isLastPart := i == len(parts)-1 131 | statInfo, err := os.Stat(urlInfo.FilePath) 132 | 133 | // not a valid path 134 | if err != nil { 135 | return nil, ScriptNotFoundError 136 | } 137 | 138 | // at the end of url but is a dir 139 | if isLastPart && statInfo.IsDir() { 140 | return nil, ScriptNotFoundError 141 | } 142 | 143 | // we've hit a dir, carry on looking 144 | if statInfo.IsDir() { 145 | continue 146 | } 147 | 148 | // no extra args 149 | if isLastPart { 150 | return urlInfo, nil 151 | } 152 | 153 | // build path info from extra parts of url 154 | urlInfo.PathInfo = "/" + strings.Join(parts[i+1:], "/") 155 | return urlInfo, nil 156 | } 157 | panic(fmt.Sprintf("GetURLInfo cannot parse path %#v", path)) 158 | } 159 | 160 | func generateId() string { 161 | return strconv.FormatInt(time.Now().UnixNano(), 10) 162 | } 163 | -------------------------------------------------------------------------------- /libwebsocketd/handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "io/ioutil" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | ) 14 | 15 | func TestParsePathWithScriptDir(t *testing.T) { 16 | baseDir, _ := ioutil.TempDir("", "websockets") 17 | scriptDir := filepath.Join(baseDir, "foo", "bar") 18 | scriptPath := filepath.Join(scriptDir, "baz.sh") 19 | 20 | defer os.RemoveAll(baseDir) 21 | 22 | if err := os.MkdirAll(scriptDir, os.ModePerm); err != nil { 23 | t.Error("could not create ", scriptDir) 24 | } 25 | if _, err := os.Create(scriptPath); err != nil { 26 | t.Error("could not create ", scriptPath) 27 | } 28 | 29 | config := new(Config) 30 | config.UsingScriptDir = true 31 | config.ScriptDir = baseDir 32 | 33 | var res *URLInfo 34 | var err error 35 | 36 | // simple url 37 | res, err = GetURLInfo("/foo/bar/baz.sh", config) 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | if res.ScriptPath != "/foo/bar/baz.sh" { 42 | t.Error("scriptPath") 43 | } 44 | if res.PathInfo != "" { 45 | t.Error("GetURLInfo") 46 | } 47 | if res.FilePath != scriptPath { 48 | t.Error("filePath") 49 | } 50 | 51 | // url with extra path info 52 | res, err = GetURLInfo("/foo/bar/baz.sh/some/extra/stuff", config) 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | if res.ScriptPath != "/foo/bar/baz.sh" { 57 | t.Error("scriptPath") 58 | } 59 | if res.PathInfo != "/some/extra/stuff" { 60 | t.Error("GetURLInfo") 61 | } 62 | if res.FilePath != scriptPath { 63 | t.Error("filePath") 64 | } 65 | 66 | // non-existing file 67 | _, err = GetURLInfo("/foo/bar/bang.sh", config) 68 | if err == nil { 69 | t.Error("non-existing file should fail") 70 | } 71 | if err != ScriptNotFoundError { 72 | t.Error("should fail with script not found") 73 | } 74 | 75 | // non-existing dir 76 | _, err = GetURLInfo("/hoohar/bang.sh", config) 77 | if err == nil { 78 | t.Error("non-existing dir should fail") 79 | } 80 | if err != ScriptNotFoundError { 81 | t.Error("should fail with script not found") 82 | } 83 | } 84 | 85 | func TestParsePathExplicitScript(t *testing.T) { 86 | config := new(Config) 87 | config.UsingScriptDir = false 88 | 89 | res, err := GetURLInfo("/some/path", config) 90 | if err != nil { 91 | t.Error(err) 92 | } 93 | if res.ScriptPath != "/" { 94 | t.Error("scriptPath") 95 | } 96 | if res.PathInfo != "/some/path" { 97 | t.Error("GetURLInfo") 98 | } 99 | if res.FilePath != "" { 100 | t.Error("filePath") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /libwebsocketd/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "net" 12 | "net/http" 13 | "net/http/cgi" 14 | "net/textproto" 15 | "net/url" 16 | "os" 17 | "path" 18 | "path/filepath" 19 | "regexp" 20 | "strings" 21 | 22 | "github.com/gorilla/websocket" 23 | ) 24 | 25 | var ForkNotAllowedError = errors.New("too many forks active") 26 | 27 | // WebsocketdServer presents http.Handler interface for requests libwebsocketd is handling. 28 | type WebsocketdServer struct { 29 | Config *Config 30 | Log *LogScope 31 | forks chan byte 32 | } 33 | 34 | // NewWebsocketdServer creates WebsocketdServer struct with pre-determined config, logscope and maxforks limit 35 | func NewWebsocketdServer(config *Config, log *LogScope, maxforks int) *WebsocketdServer { 36 | mux := &WebsocketdServer{ 37 | Config: config, 38 | Log: log, 39 | } 40 | if maxforks > 0 { 41 | mux.forks = make(chan byte, maxforks) 42 | } 43 | return mux 44 | } 45 | 46 | func splitMimeHeader(s string) (string, string) { 47 | p := strings.IndexByte(s, ':') 48 | if p < 0 { 49 | return s, "" 50 | } 51 | key := textproto.CanonicalMIMEHeaderKey(s[:p]) 52 | 53 | for p = p + 1; p < len(s); p++ { 54 | if s[p] != ' ' { 55 | break 56 | } 57 | } 58 | return key, s[p:] 59 | } 60 | 61 | func pushHeaders(h http.Header, hdrs []string) { 62 | for _, hstr := range hdrs { 63 | h.Add(splitMimeHeader(hstr)) 64 | } 65 | } 66 | 67 | // ServeHTTP muxes between WebSocket handler, CGI handler, DevConsole, Static HTML or 404. 68 | func (h *WebsocketdServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 69 | log := h.Log.NewLevel(h.Log.LogFunc) 70 | log.Associate("url", h.TellURL("http", req.Host, req.RequestURI)) 71 | 72 | if h.Config.CommandName != "" || h.Config.UsingScriptDir { 73 | hdrs := req.Header 74 | upgradeRe := regexp.MustCompile(`(?i)(^|[,\s])Upgrade($|[,\s])`) 75 | // WebSocket, limited to size of h.forks 76 | if strings.ToLower(hdrs.Get("Upgrade")) == "websocket" && upgradeRe.MatchString(hdrs.Get("Connection")) { 77 | if h.noteForkCreated() == nil { 78 | defer h.noteForkCompled() 79 | 80 | // start figuring out if we even need to upgrade 81 | handler, err := NewWebsocketdHandler(h, req, log) 82 | if err != nil { 83 | if err == ScriptNotFoundError { 84 | log.Access("session", "NOT FOUND: %s", err) 85 | http.Error(w, "404 Not Found", 404) 86 | } else { 87 | log.Access("session", "INTERNAL ERROR: %s", err) 88 | http.Error(w, "500 Internal Server Error", 500) 89 | } 90 | return 91 | } 92 | 93 | var headers http.Header 94 | if len(h.Config.Headers)+len(h.Config.HeadersWs) > 0 { 95 | headers = http.Header(make(map[string][]string)) 96 | pushHeaders(headers, h.Config.Headers) 97 | pushHeaders(headers, h.Config.HeadersWs) 98 | } 99 | 100 | upgrader := &websocket.Upgrader{ 101 | HandshakeTimeout: h.Config.HandshakeTimeout, 102 | CheckOrigin: func(r *http.Request) bool { 103 | // backporting previous checkorigin for use in gorilla/websocket for now 104 | err := checkOrigin(req, h.Config, log) 105 | return err == nil 106 | }, 107 | } 108 | conn, err := upgrader.Upgrade(w, req, headers) 109 | if err != nil { 110 | log.Access("session", "Unable to Upgrade: %s", err) 111 | http.Error(w, "500 Internal Error", 500) 112 | return 113 | } 114 | 115 | // old func was used in x/net/websocket style, we reuse it here for gorilla/websocket 116 | handler.accept(conn, log) 117 | return 118 | 119 | } else { 120 | log.Error("http", "Max of possible forks already active, upgrade rejected") 121 | http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests) 122 | } 123 | return 124 | } 125 | } 126 | 127 | pushHeaders(w.Header(), h.Config.HeadersHTTP) 128 | 129 | // Dev console (if enabled) 130 | if h.Config.DevConsole { 131 | log.Access("http", "DEVCONSOLE") 132 | content := ConsoleContent 133 | content = strings.Replace(content, "{{license}}", License, -1) 134 | content = strings.Replace(content, "{{addr}}", h.TellURL("ws", req.Host, req.RequestURI), -1) 135 | http.ServeContent(w, req, ".html", h.Config.StartupTime, strings.NewReader(content)) 136 | return 137 | } 138 | 139 | // CGI scripts, limited to size of h.forks 140 | if h.Config.CgiDir != "" { 141 | filePath := path.Join(h.Config.CgiDir, fmt.Sprintf(".%s", filepath.FromSlash(req.URL.Path))) 142 | if fi, err := os.Stat(filePath); err == nil && !fi.IsDir() { 143 | 144 | log.Associate("cgiscript", filePath) 145 | if h.noteForkCreated() == nil { 146 | defer h.noteForkCompled() 147 | 148 | // Make variables to supplement cgi... Environ it uses will show empty list. 149 | envlen := len(h.Config.ParentEnv) 150 | cgienv := make([]string, envlen+1) 151 | if envlen > 0 { 152 | copy(cgienv, h.Config.ParentEnv) 153 | } 154 | cgienv[envlen] = "SERVER_SOFTWARE=" + h.Config.ServerSoftware 155 | cgiHandler := &cgi.Handler{ 156 | Path: filePath, 157 | Env: []string{ 158 | "SERVER_SOFTWARE=" + h.Config.ServerSoftware, 159 | }, 160 | } 161 | log.Access("http", "CGI") 162 | cgiHandler.ServeHTTP(w, req) 163 | } else { 164 | log.Error("http", "Fork not allowed since maxforks amount has been reached. CGI was not run.") 165 | http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests) 166 | } 167 | return 168 | } 169 | } 170 | 171 | // Static files 172 | if h.Config.StaticDir != "" { 173 | handler := http.FileServer(http.Dir(h.Config.StaticDir)) 174 | log.Access("http", "STATIC") 175 | handler.ServeHTTP(w, req) 176 | return 177 | } 178 | 179 | // 404 180 | log.Access("http", "NOT FOUND") 181 | http.NotFound(w, req) 182 | } 183 | 184 | var canonicalHostname string 185 | 186 | // TellURL is a helper function that changes http to https or ws to wss in case if SSL is used 187 | func (h *WebsocketdServer) TellURL(scheme, host, path string) string { 188 | if len(host) > 0 && host[0] == ':' { 189 | if canonicalHostname == "" { 190 | var err error 191 | canonicalHostname, err = os.Hostname() 192 | if err != nil { 193 | canonicalHostname = "UNKNOWN" 194 | } 195 | } 196 | host = canonicalHostname + host 197 | } 198 | if h.Config.Ssl { 199 | return scheme + "s://" + host + path 200 | } 201 | return scheme + "://" + host + path 202 | } 203 | 204 | func (h *WebsocketdServer) noteForkCreated() error { 205 | // note that forks can be nil since the construct could've been created by 206 | // someone who is not using NewWebsocketdServer 207 | if h.forks != nil { 208 | select { 209 | case h.forks <- 1: 210 | return nil 211 | default: 212 | return ForkNotAllowedError 213 | } 214 | } else { 215 | return nil 216 | } 217 | } 218 | 219 | func (h *WebsocketdServer) noteForkCompled() { 220 | if h.forks != nil { // see comment in noteForkCreated 221 | select { 222 | case <-h.forks: 223 | return 224 | default: 225 | // This could only happen if the completion handler called more times than creation handler above 226 | // Code should be audited to not allow this to happen, it's desired to have test that would 227 | // make sure this is impossible but it is not exist yet. 228 | panic("Cannot deplet number of allowed forks, something is not right in code!") 229 | } 230 | } 231 | } 232 | 233 | func checkOrigin(req *http.Request, config *Config, log *LogScope) (err error) { 234 | // CONVERT GORILLA: 235 | // this is origin checking function, it's called from wshandshake which is from ServeHTTP main handler 236 | // should be trivial to reuse in gorilla's upgrader.CheckOrigin function. 237 | // Only difference is to parse request and fetching passed Origin header out of it instead of using 238 | // pre-parsed wsconf.Origin 239 | 240 | // check for origin to be correct in future 241 | // handshaker triggers answering with 403 if error was returned 242 | // We keep behavior of original handshaker that populates this field 243 | origin := req.Header.Get("Origin") 244 | if origin == "" || (origin == "null" && config.AllowOrigins == nil) { 245 | // we don't want to trust string "null" if there is any 246 | // enforcements are active 247 | origin = "file:" 248 | } 249 | 250 | originParsed, err := url.ParseRequestURI(origin) 251 | if err != nil { 252 | log.Access("session", "Origin parsing error: %s", err) 253 | return err 254 | } 255 | 256 | log.Associate("origin", originParsed.String()) 257 | 258 | // If some origin restrictions are present: 259 | if config.SameOrigin || config.AllowOrigins != nil { 260 | originServer, originPort, err := tellHostPort(originParsed.Host, originParsed.Scheme == "https") 261 | if err != nil { 262 | log.Access("session", "Origin hostname parsing error: %s", err) 263 | return err 264 | } 265 | if config.SameOrigin { 266 | localServer, localPort, err := tellHostPort(req.Host, req.TLS != nil) 267 | if err != nil { 268 | log.Access("session", "Request hostname parsing error: %s", err) 269 | return err 270 | } 271 | if originServer != localServer || originPort != localPort { 272 | log.Access("session", "Same origin policy mismatch") 273 | return fmt.Errorf("same origin policy violated") 274 | } 275 | } 276 | if config.AllowOrigins != nil { 277 | matchFound := false 278 | for _, allowed := range config.AllowOrigins { 279 | if pos := strings.Index(allowed, "://"); pos > 0 { 280 | // allowed schema has to match 281 | allowedURL, err := url.Parse(allowed) 282 | if err != nil { 283 | continue // pass bad URLs in origin list 284 | } 285 | if allowedURL.Scheme != originParsed.Scheme { 286 | continue // mismatch 287 | } 288 | allowed = allowed[pos+3:] 289 | } 290 | allowServer, allowPort, err := tellHostPort(allowed, false) 291 | if err != nil { 292 | continue // unparseable 293 | } 294 | if allowPort == "80" && allowed[len(allowed)-3:] != ":80" { 295 | // any port is allowed, host names need to match 296 | matchFound = allowServer == originServer 297 | } else { 298 | // exact match of host names and ports 299 | matchFound = allowServer == originServer && allowPort == originPort 300 | } 301 | if matchFound { 302 | break 303 | } 304 | } 305 | if !matchFound { 306 | log.Access("session", "Origin is not listed in allowed list") 307 | return fmt.Errorf("origin list matches were not found") 308 | } 309 | } 310 | } 311 | return nil 312 | } 313 | 314 | func tellHostPort(host string, ssl bool) (server, port string, err error) { 315 | server, port, err = net.SplitHostPort(host) 316 | if err != nil { 317 | if addrerr, ok := err.(*net.AddrError); ok && strings.Contains(addrerr.Err, "missing port") { 318 | server = host 319 | if ssl { 320 | port = "443" 321 | } else { 322 | port = "80" 323 | } 324 | err = nil 325 | } 326 | } 327 | return server, port, err 328 | } 329 | -------------------------------------------------------------------------------- /libwebsocketd/http_test.go: -------------------------------------------------------------------------------- 1 | package libwebsocketd 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "fmt" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | var tellHostPortTests = []struct { 13 | src string 14 | ssl bool 15 | server, port string 16 | }{ 17 | {"localhost", false, "localhost", "80"}, 18 | {"localhost:8080", false, "localhost", "8080"}, 19 | {"localhost", true, "localhost", "443"}, 20 | {"localhost:8080", true, "localhost", "8080"}, 21 | } 22 | 23 | func TestTellHostPort(t *testing.T) { 24 | for _, testcase := range tellHostPortTests { 25 | s, p, e := tellHostPort(testcase.src, testcase.ssl) 26 | if testcase.server == "" { 27 | if e == nil { 28 | t.Errorf("test case for %#v failed, error was not returned", testcase.src) 29 | } 30 | } else if e != nil { 31 | t.Errorf("test case for %#v failed, error should not happen", testcase.src) 32 | } 33 | if testcase.server != s || testcase.port != p { 34 | t.Errorf("test case for %#v failed, server or port mismatch to expected values (%s:%s)", testcase.src, s, p) 35 | } 36 | } 37 | } 38 | 39 | var NoOriginsAllowed = []string{} 40 | var NoOriginList []string = nil 41 | 42 | const ( 43 | ReqHTTPS = iota 44 | ReqHTTP 45 | OriginMustBeSame 46 | OriginCouldDiffer 47 | ReturnsPass 48 | ReturnsError 49 | ) 50 | 51 | var CheckOriginTests = []struct { 52 | host string 53 | reqtls int 54 | origin string 55 | same int 56 | allowed []string 57 | getsErr int 58 | name string 59 | }{ 60 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed"}, 61 | {"server.example.com", ReqHTTP, "http://example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin mismatch"}, 62 | {"server.example.com", ReqHTTP, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsPass, "same origin match"}, 63 | {"server.example.com", ReqHTTP, "https://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 1"}, 64 | {"server.example.com", ReqHTTPS, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 2"}, 65 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginsAllowed, ReturnsError, "no origins allowed"}, 66 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"server.example.com"}, ReturnsError, "no origin allowed matches (junk prefix)"}, 67 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com.t"}, ReturnsError, "no origin allowed matches (junk suffix)"}, 68 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed clean match"}, 69 | {"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed any port match"}, 70 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:80"}, ReturnsPass, "origin allowed port match"}, 71 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"}, 72 | {"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"}, 73 | {"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com:81"}, ReturnsPass, "origin allowed port 81 match"}, 74 | {"server.example.com", ReqHTTP, "null", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even null"}, 75 | {"server.example.com", ReqHTTP, "", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even empty"}, 76 | } 77 | 78 | // CONVERT GORILLA 79 | // as method for origin checking changes to handle things without websocket.Config the test 80 | // should be altered too 81 | 82 | func TestCheckOrigin(t *testing.T) { 83 | for _, testcase := range CheckOriginTests { 84 | br := bufio.NewReader(strings.NewReader(fmt.Sprintf(`GET /chat HTTP/1.1 85 | Host: %s 86 | Upgrade: websocket 87 | Connection: Upgrade 88 | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== 89 | Origin: %s 90 | Sec-WebSocket-Version: 13 91 | 92 | `, testcase.host, testcase.origin))) 93 | 94 | req, err := http.ReadRequest(br) 95 | if err != nil { 96 | t.Fatal("request", err) 97 | } 98 | 99 | log := new(LogScope) 100 | log.LogFunc = func(*LogScope, LogLevel, string, string, string, ...interface{}) {} 101 | 102 | config := new(Config) 103 | 104 | if testcase.reqtls == ReqHTTPS { // Fake TLS 105 | config.Ssl = true 106 | req.TLS = &tls.ConnectionState{} 107 | } 108 | if testcase.same == OriginMustBeSame { 109 | config.SameOrigin = true 110 | } 111 | if testcase.allowed != nil { 112 | config.AllowOrigins = testcase.allowed 113 | } 114 | 115 | err = checkOrigin(req, config, log) 116 | if testcase.getsErr == ReturnsError && err == nil { 117 | t.Errorf("Test case %#v did not get an error", testcase.name) 118 | } else if testcase.getsErr == ReturnsPass && err != nil { 119 | t.Errorf("Test case %#v got error while expected to pass", testcase.name) 120 | } 121 | } 122 | } 123 | 124 | var mimetest = [][3]string{ 125 | {"Content-Type: text/plain", "Content-Type", "text/plain"}, 126 | {"Content-Type: ", "Content-Type", ""}, 127 | } 128 | 129 | func TestSplitMimeHeader(t *testing.T) { 130 | for _, tst := range mimetest { 131 | s, v := splitMimeHeader(tst[0]) 132 | if tst[1] != s || tst[2] != v { 133 | t.Errorf("%v and %v are not same as expexted %v and %v", s, v, tst[1], tst[2]) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /libwebsocketd/launcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "io" 10 | "os/exec" 11 | ) 12 | 13 | type LaunchedProcess struct { 14 | cmd *exec.Cmd 15 | stdin io.WriteCloser 16 | stdout io.ReadCloser 17 | stderr io.ReadCloser 18 | } 19 | 20 | func launchCmd(commandName string, commandArgs []string, env []string) (*LaunchedProcess, error) { 21 | cmd := exec.Command(commandName, commandArgs...) 22 | cmd.Env = env 23 | 24 | stdout, err := cmd.StdoutPipe() 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | stderr, err := cmd.StderrPipe() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | stdin, err := cmd.StdinPipe() 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | err = cmd.Start() 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return &LaunchedProcess{cmd, stdin, stdout, stderr}, err 45 | } 46 | -------------------------------------------------------------------------------- /libwebsocketd/license.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | const ( 9 | license = ` 10 | Copyright (c) 2013, Joe Walnes and the websocketd authors. 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | 16 | 1. Redistributions of source code must retain the above copyright notice, this 17 | list of conditions and the following disclaimer. 18 | 2. Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 29 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | ` 33 | License = license 34 | ) 35 | -------------------------------------------------------------------------------- /libwebsocketd/logscope.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type LogLevel int 14 | 15 | const ( 16 | LogDebug = iota 17 | LogTrace 18 | LogAccess 19 | LogInfo 20 | LogError 21 | LogFatal 22 | 23 | LogNone = 126 24 | LogUnknown = 127 25 | ) 26 | 27 | type LogFunc func(logScope *LogScope, level LogLevel, levelName string, category string, msg string, args ...interface{}) 28 | 29 | type LogScope struct { 30 | Parent *LogScope // Parent scope 31 | MinLevel LogLevel // Minimum log level to write out. 32 | Mutex *sync.Mutex // Should be shared across all LogScopes that write to the same destination. 33 | Associated []AssocPair // Additional data associated with scope 34 | LogFunc LogFunc 35 | } 36 | 37 | type AssocPair struct { 38 | Key string 39 | Value string 40 | } 41 | 42 | func (l *LogScope) Associate(key string, value string) { 43 | l.Associated = append(l.Associated, AssocPair{key, value}) 44 | } 45 | 46 | func (l *LogScope) Debug(category string, msg string, args ...interface{}) { 47 | l.LogFunc(l, LogDebug, "DEBUG", category, msg, args...) 48 | } 49 | 50 | func (l *LogScope) Trace(category string, msg string, args ...interface{}) { 51 | l.LogFunc(l, LogTrace, "TRACE", category, msg, args...) 52 | } 53 | 54 | func (l *LogScope) Access(category string, msg string, args ...interface{}) { 55 | l.LogFunc(l, LogAccess, "ACCESS", category, msg, args...) 56 | } 57 | 58 | func (l *LogScope) Info(category string, msg string, args ...interface{}) { 59 | l.LogFunc(l, LogInfo, "INFO", category, msg, args...) 60 | } 61 | 62 | func (l *LogScope) Error(category string, msg string, args ...interface{}) { 63 | l.LogFunc(l, LogError, "ERROR", category, msg, args...) 64 | } 65 | 66 | func (l *LogScope) Fatal(category string, msg string, args ...interface{}) { 67 | l.LogFunc(l, LogFatal, "FATAL", category, msg, args...) 68 | } 69 | 70 | func (parent *LogScope) NewLevel(logFunc LogFunc) *LogScope { 71 | return &LogScope{ 72 | Parent: parent, 73 | MinLevel: parent.MinLevel, 74 | Mutex: parent.Mutex, 75 | Associated: make([]AssocPair, 0), 76 | LogFunc: logFunc} 77 | } 78 | 79 | func RootLogScope(minLevel LogLevel, logFunc LogFunc) *LogScope { 80 | return &LogScope{ 81 | Parent: nil, 82 | MinLevel: minLevel, 83 | Mutex: &sync.Mutex{}, 84 | Associated: make([]AssocPair, 0), 85 | LogFunc: logFunc} 86 | } 87 | 88 | func Timestamp() string { 89 | return time.Now().Format(time.RFC1123Z) 90 | } 91 | 92 | func LevelFromString(s string) LogLevel { 93 | switch s { 94 | case "debug": 95 | return LogDebug 96 | case "trace": 97 | return LogTrace 98 | case "access": 99 | return LogAccess 100 | case "info": 101 | return LogInfo 102 | case "error": 103 | return LogError 104 | case "fatal": 105 | return LogFatal 106 | case "none": 107 | return LogNone 108 | default: 109 | return LogUnknown 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /libwebsocketd/process_endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "bufio" 10 | "io" 11 | "syscall" 12 | "time" 13 | ) 14 | 15 | type ProcessEndpoint struct { 16 | process *LaunchedProcess 17 | closetime time.Duration 18 | output chan []byte 19 | log *LogScope 20 | bin bool 21 | } 22 | 23 | func NewProcessEndpoint(process *LaunchedProcess, bin bool, log *LogScope) *ProcessEndpoint { 24 | return &ProcessEndpoint{ 25 | process: process, 26 | output: make(chan []byte), 27 | log: log, 28 | bin: bin, 29 | } 30 | } 31 | 32 | func (pe *ProcessEndpoint) Terminate() { 33 | terminated := make(chan struct{}) 34 | go func() { pe.process.cmd.Wait(); terminated <- struct{}{} }() 35 | 36 | // for some processes this is enough to finish them... 37 | pe.process.stdin.Close() 38 | 39 | // a bit verbose to create good debugging trail 40 | select { 41 | case <-terminated: 42 | pe.log.Debug("process", "Process %v terminated after stdin was closed", pe.process.cmd.Process.Pid) 43 | return // means process finished 44 | case <-time.After(100*time.Millisecond + pe.closetime): 45 | } 46 | 47 | err := pe.process.cmd.Process.Signal(syscall.SIGINT) 48 | if err != nil { 49 | // process is done without this, great! 50 | pe.log.Error("process", "SIGINT unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) 51 | } 52 | 53 | select { 54 | case <-terminated: 55 | pe.log.Debug("process", "Process %v terminated after SIGINT", pe.process.cmd.Process.Pid) 56 | return // means process finished 57 | case <-time.After(250*time.Millisecond + pe.closetime): 58 | } 59 | 60 | err = pe.process.cmd.Process.Signal(syscall.SIGTERM) 61 | if err != nil { 62 | // process is done without this, great! 63 | pe.log.Error("process", "SIGTERM unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) 64 | } 65 | 66 | select { 67 | case <-terminated: 68 | pe.log.Debug("process", "Process %v terminated after SIGTERM", pe.process.cmd.Process.Pid) 69 | return // means process finished 70 | case <-time.After(500*time.Millisecond + pe.closetime): 71 | } 72 | 73 | err = pe.process.cmd.Process.Kill() 74 | if err != nil { 75 | pe.log.Error("process", "SIGKILL unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) 76 | return 77 | } 78 | 79 | select { 80 | case <-terminated: 81 | pe.log.Debug("process", "Process %v terminated after SIGKILL", pe.process.cmd.Process.Pid) 82 | return // means process finished 83 | case <-time.After(1000 * time.Millisecond): 84 | } 85 | 86 | pe.log.Error("process", "SIGKILL did not terminate %v!", pe.process.cmd.Process.Pid) 87 | } 88 | 89 | func (pe *ProcessEndpoint) Output() chan []byte { 90 | return pe.output 91 | } 92 | 93 | func (pe *ProcessEndpoint) Send(msg []byte) bool { 94 | pe.process.stdin.Write(msg) 95 | return true 96 | } 97 | 98 | func (pe *ProcessEndpoint) StartReading() { 99 | go pe.log_stderr() 100 | if pe.bin { 101 | go pe.process_binout() 102 | } else { 103 | go pe.process_txtout() 104 | } 105 | } 106 | 107 | func (pe *ProcessEndpoint) process_txtout() { 108 | bufin := bufio.NewReader(pe.process.stdout) 109 | for { 110 | buf, err := bufin.ReadBytes('\n') 111 | if err != nil { 112 | if err != io.EOF { 113 | pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err) 114 | } else { 115 | pe.log.Debug("process", "Process STDOUT closed") 116 | } 117 | break 118 | } 119 | pe.output <- trimEOL(buf) 120 | } 121 | close(pe.output) 122 | } 123 | 124 | func (pe *ProcessEndpoint) process_binout() { 125 | buf := make([]byte, 10*1024*1024) 126 | for { 127 | n, err := pe.process.stdout.Read(buf) 128 | if err != nil { 129 | if err != io.EOF { 130 | pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err) 131 | } else { 132 | pe.log.Debug("process", "Process STDOUT closed") 133 | } 134 | break 135 | } 136 | pe.output <- append(make([]byte, 0, n), buf[:n]...) // cloned buffer 137 | } 138 | close(pe.output) 139 | } 140 | 141 | func (pe *ProcessEndpoint) log_stderr() { 142 | bufstderr := bufio.NewReader(pe.process.stderr) 143 | for { 144 | buf, err := bufstderr.ReadSlice('\n') 145 | if err != nil { 146 | if err != io.EOF { 147 | pe.log.Error("process", "Unexpected error while reading STDERR from process: %s", err) 148 | } else { 149 | pe.log.Debug("process", "Process STDERR closed") 150 | } 151 | break 152 | } 153 | pe.log.Error("stderr", "%s", string(trimEOL(buf))) 154 | } 155 | } 156 | 157 | // trimEOL cuts unixy style \n and windowsy style \r\n suffix from the string 158 | func trimEOL(b []byte) []byte { 159 | lns := len(b) 160 | if lns > 0 && b[lns-1] == '\n' { 161 | lns-- 162 | if lns > 0 && b[lns-1] == '\r' { 163 | lns-- 164 | } 165 | } 166 | return b[:lns] 167 | } 168 | -------------------------------------------------------------------------------- /libwebsocketd/websocket_endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "io" 10 | "io/ioutil" 11 | 12 | "github.com/gorilla/websocket" 13 | ) 14 | 15 | // CONVERT GORILLA 16 | // This file should be altered to use gorilla's websocket connection type and proper 17 | // message dispatching methods 18 | 19 | type WebSocketEndpoint struct { 20 | ws *websocket.Conn 21 | output chan []byte 22 | log *LogScope 23 | mtype int 24 | } 25 | 26 | func NewWebSocketEndpoint(ws *websocket.Conn, bin bool, log *LogScope) *WebSocketEndpoint { 27 | endpoint := &WebSocketEndpoint{ 28 | ws: ws, 29 | output: make(chan []byte), 30 | log: log, 31 | mtype: websocket.TextMessage, 32 | } 33 | if bin { 34 | endpoint.mtype = websocket.BinaryMessage 35 | } 36 | return endpoint 37 | } 38 | 39 | func (we *WebSocketEndpoint) Terminate() { 40 | we.log.Trace("websocket", "Terminated websocket connection") 41 | } 42 | 43 | func (we *WebSocketEndpoint) Output() chan []byte { 44 | return we.output 45 | } 46 | 47 | func (we *WebSocketEndpoint) Send(msg []byte) bool { 48 | w, err := we.ws.NextWriter(we.mtype) 49 | if err == nil { 50 | _, err = w.Write(msg) 51 | } 52 | w.Close() // could need error handling 53 | 54 | if err != nil { 55 | we.log.Trace("websocket", "Cannot send: %s", err) 56 | return false 57 | } 58 | 59 | return true 60 | } 61 | 62 | func (we *WebSocketEndpoint) StartReading() { 63 | go we.read_frames() 64 | } 65 | 66 | func (we *WebSocketEndpoint) read_frames() { 67 | for { 68 | mtype, rd, err := we.ws.NextReader() 69 | if err != nil { 70 | we.log.Debug("websocket", "Cannot receive: %s", err) 71 | break 72 | } 73 | if mtype != we.mtype { 74 | we.log.Debug("websocket", "Received message of type that we did not expect... Ignoring...") 75 | } 76 | 77 | p, err := ioutil.ReadAll(rd) 78 | if err != nil && err != io.EOF { 79 | we.log.Debug("websocket", "Cannot read received message: %s", err) 80 | break 81 | } 82 | switch mtype { 83 | case websocket.TextMessage: 84 | we.output <- append(p, '\n') 85 | case websocket.BinaryMessage: 86 | we.output <- p 87 | default: 88 | we.log.Debug("websocket", "Received message of unknown type: %d", mtype) 89 | } 90 | } 91 | close(we.output) 92 | } 93 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "net/http" 11 | "os" 12 | "runtime" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/joewalnes/websocketd/libwebsocketd" 17 | ) 18 | 19 | func logfunc(l *libwebsocketd.LogScope, level libwebsocketd.LogLevel, levelName string, category string, msg string, args ...interface{}) { 20 | if level < l.MinLevel { 21 | return 22 | } 23 | fullMsg := fmt.Sprintf(msg, args...) 24 | 25 | assocDump := "" 26 | for index, pair := range l.Associated { 27 | if index > 0 { 28 | assocDump += " " 29 | } 30 | assocDump += fmt.Sprintf("%s:'%s'", pair.Key, pair.Value) 31 | } 32 | 33 | l.Mutex.Lock() 34 | fmt.Printf("%s | %-6s | %-10s | %s | %s\n", libwebsocketd.Timestamp(), levelName, category, assocDump, fullMsg) 35 | l.Mutex.Unlock() 36 | } 37 | 38 | func main() { 39 | config := parseCommandLine() 40 | 41 | log := libwebsocketd.RootLogScope(config.LogLevel, logfunc) 42 | 43 | if config.DevConsole { 44 | if config.StaticDir != "" { 45 | log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --staticdir. Pick one.") 46 | os.Exit(4) 47 | } 48 | if config.CgiDir != "" { 49 | log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --cgidir. Pick one.") 50 | os.Exit(4) 51 | } 52 | } 53 | 54 | if runtime.GOOS != "windows" { // windows relies on env variables to find its libs... e.g. socket stuff 55 | os.Clearenv() // it's ok to wipe it clean, we already read env variables from passenv into config 56 | } 57 | handler := libwebsocketd.NewWebsocketdServer(config.Config, log, config.MaxForks) 58 | http.Handle("/", handler) 59 | 60 | if config.UsingScriptDir { 61 | log.Info("server", "Serving from directory : %s", config.ScriptDir) 62 | } else if config.CommandName != "" { 63 | log.Info("server", "Serving using application : %s %s", config.CommandName, strings.Join(config.CommandArgs, " ")) 64 | } 65 | if config.StaticDir != "" { 66 | log.Info("server", "Serving static content from : %s", config.StaticDir) 67 | } 68 | if config.CgiDir != "" { 69 | log.Info("server", "Serving CGI scripts from : %s", config.CgiDir) 70 | } 71 | 72 | rejects := make(chan error, 1) 73 | for _, addrSingle := range config.Addr { 74 | log.Info("server", "Starting WebSocket server : %s", handler.TellURL("ws", addrSingle, "/")) 75 | if config.DevConsole { 76 | log.Info("server", "Developer console enabled : %s", handler.TellURL("http", addrSingle, "/")) 77 | } else if config.StaticDir != "" || config.CgiDir != "" { 78 | log.Info("server", "Serving CGI or static files : %s", handler.TellURL("http", addrSingle, "/")) 79 | } 80 | // ListenAndServe is blocking function. Let's run it in 81 | // go routine, reporting result to control channel. 82 | // Since it's blocking it'll never return non-error. 83 | 84 | go func(addr string) { 85 | if config.Ssl { 86 | rejects <- http.ListenAndServeTLS(addr, config.CertFile, config.KeyFile, nil) 87 | } else { 88 | rejects <- http.ListenAndServe(addr, nil) 89 | } 90 | }(addrSingle) 91 | 92 | if config.RedirPort != 0 { 93 | go func(addr string) { 94 | pos := strings.IndexByte(addr, ':') 95 | rediraddr := addr[:pos] + ":" + strconv.Itoa(config.RedirPort) // it would be silly to optimize this one 96 | redir := &http.Server{Addr: rediraddr, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 97 | // redirect to same hostname as in request but different port and probably schema 98 | uri := "https://" 99 | if !config.Ssl { 100 | uri = "http://" 101 | } 102 | if cpos := strings.IndexByte(r.Host, ':'); cpos > 0 { 103 | uri += r.Host[:strings.IndexByte(r.Host, ':')] + addr[pos:] + "/" 104 | } else { 105 | uri += r.Host + addr[pos:] + "/" 106 | } 107 | 108 | http.Redirect(w, r, uri, http.StatusMovedPermanently) 109 | })} 110 | log.Info("server", "Starting redirect server : http://%s/", rediraddr) 111 | rejects <- redir.ListenAndServe() 112 | }(addrSingle) 113 | } 114 | } 115 | err := <-rejects 116 | if err != nil { 117 | log.Fatal("server", "Can't start server: %s", err) 118 | os.Exit(3) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /release/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | go-local 3 | out 4 | go-path 5 | websocketd.exe 6 | -------------------------------------------------------------------------------- /release/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2013-2019 Joe Walnes and the websocketd team. 2 | # All rights reserved. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | 6 | # Uses Semantic Versioning scheme - http://semver.org/ 7 | VERSION_MAJOR=0 8 | VERSION_MINOR=4 9 | 10 | # Last part of version number (patch) is incremented automatically from Git tags 11 | LAST_PATCH_VERSION:=$(shell git ls-remote git@github.com:joewalnes/websocketd.git \ 12 | | grep -e 'refs/tags/v[0-9\.]*$$' \ 13 | | sed -e 's|^.*refs/tags/v||' \ 14 | | grep -e "^$(VERSION_MAJOR)\.$(VERSION_MINOR)\.[0-9][0-9]*$$" \ 15 | | sed -e 's/^.*\.//' \ 16 | | sort -n \ 17 | | tail -n 1) 18 | 19 | 20 | 21 | VERSION_PATCH:=$(or $(LAST_PATCH_VERSION),0) 22 | RELEASE_VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH) 23 | 24 | GO_VERSION=1.15.7 25 | 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 26 | 27 | 28 | # Would NOT WORK on: ARM, WINDOWS 29 | 30 | UNAME_SYS=$(shell uname -s | tr A-Z a-z) 31 | UNAME_ARCH=$(shell uname -m) 32 | ifeq ($(UNAME_ARCH),x86_64) 33 | UNAME_ARCH=amd64 34 | else 35 | UNAME_ARCH=386 36 | endif 37 | 38 | ifeq ($(UNAME_SYS),gnu) 39 | UNAME_SYS=linux 40 | else ifeq ($(UNAME_SYS),sunos) 41 | UNAME_SYS=solaris 42 | endif 43 | 44 | 45 | 46 | GO_DOWNLOAD_URL=https://dl.google.com/go/go$(GO_VERSION).$(UNAME_SYS)-$(UNAME_ARCH).tar.gz 47 | GO_DIR=../go-$(GO_VERSION) 48 | 49 | # Prevent any global environment polluting the builds 50 | FLAGS_linux_amd64 = GOOS=linux GOARCH=amd64 51 | FLAGS_linux_386 = GOOS=linux GOARCH=386 52 | FLAGS_linux_arm = GOOS=linux GOARCH=arm GOARM=5 # ARM5 support for Raspberry Pi 53 | FLAGS_linux_arm64 = GOOS=linux GOARCH=arm64 # no need for GOARM= (which is technically 8) 54 | FLAGS_darwin_amd64 = GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 55 | FLAGS_darwin_386 = GOOS=darwin GOARCH=386 CGO_ENABLED=0 56 | FLAGS_freebsd_amd64 = GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0 57 | FLAGS_freebsd_386 = GOOS=freebsd GOARCH=386 CGO_ENABLED=0 58 | FLAGS_windows_386 = GOOS=windows GOARCH=386 CGO_ENABLED=0 59 | FLAGS_windows_amd64 = GOOS=windows GOARCH=amd64 CGO_ENABLED=0 60 | FLAGS_openbsd_386 = GOOS=openbsd GOARCH=386 CGO_ENABLED=0 61 | FLAGS_openbsd_amd64 = GOOS=openbsd GOARCH=amd64 CGO_ENABLED=0 62 | FLAGS_solaris_amd64 = GOOS=solaris GOARCH=amd64 CGO_ENABLED=0 63 | 64 | EXTENSION_windows_386 = .exe 65 | EXTENSION_windows_amd64 = .exe 66 | 67 | all: build 68 | 69 | localgo: $(GO_DIR)/bin/go 70 | 71 | $(GO_DIR)/bin/go: 72 | mkdir -p $(GO_DIR) 73 | rm -f $@ 74 | @echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR) 75 | curl -s $(GO_DOWNLOAD_URL) | tar xzf - --strip-components=1 -C $(GO_DIR) 76 | 77 | 78 | # Cross-compile final applications 79 | out/$(RELEASE_VERSION)/%/websocketd: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go 80 | rm -f $@ 81 | mkdir -p $(dir $@) 82 | $(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd .. 83 | 84 | out/$(RELEASE_VERSION)/%/websocketd.exe: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go 85 | rm -f $@ 86 | mkdir -p $(dir $@) 87 | $(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd.exe .. 88 | 89 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-%.zip: out/$(RELEASE_VERSION)/%/websocketd 90 | rm -f $@ 91 | zip -j $@ $< ../{README.md,LICENSE,CHANGES} 92 | 93 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-windows_%.zip: out/$(RELEASE_VERSION)/windows_%/websocketd.exe 94 | rm -f $@ 95 | zip -j $@ $< ../{README.md,LICENSE,CHANGES} 96 | 97 | 98 | BINARIES = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/$(PLATFORM)/websocketd$(EXTENSION_$(PLATFORM))) 99 | ZIPS = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-$(PLATFORM).zip) 100 | DEBS = out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb 101 | RPMS = out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm 102 | 103 | binaries: $(BINARIES) 104 | 105 | build: out/$(RELEASE_VERSION)/CHECKSUMS 106 | 107 | out/$(RELEASE_VERSION)/CHECKSUMS: $(BINARIES) $(ZIPS) $(DEBS) $(RPMS) 108 | sha256sum $^ | sed -e 's/out\/$(RELEASE_VERSION)\///' >$@ 109 | 110 | 111 | 112 | 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" 113 | 114 | DEBFPM="" 115 | RPMFPM=--rpm-os linux 116 | 117 | deb: $(DEBS) 118 | 119 | rpm: $(RPMS) 120 | 121 | 122 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd 123 | mkdir -p out/$(RELEASE_VERSION)/deb32/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} 124 | cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/deb32/usr/bin/ 125 | cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb32/usr/share/doc/websocketd-$(RELEASE_VERSION) 126 | cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb32/usr/share/man/man1/websocket.1.gz 127 | 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 --deb-no-default-config-files $(BASEFPM) $(DEB_FPM) usr/ 128 | rm -rf out/$(RELEASE_VERSION)/deb32/ 129 | 130 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd 131 | mkdir -p out/$(RELEASE_VERSION)/deb64/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} 132 | cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/deb64/usr/bin/ 133 | cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb64/usr/share/doc/websocketd-$(RELEASE_VERSION) 134 | cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb64/usr/share/man/man1/websocket.1.gz 135 | 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 --deb-no-default-config-files $(BASEFPM) $(DEB_FPM) usr/ 136 | rm -rf out/$(RELEASE_VERSION)/deb64/ 137 | 138 | out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd 139 | mkdir -p out/$(RELEASE_VERSION)/rpm64/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} 140 | cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/rpm64/usr/bin/ 141 | cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm64/usr/share/doc/websocketd-$(RELEASE_VERSION) 142 | cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm64/usr/share/man/man1/websocket.1.gz 143 | 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/ 144 | rm -rf out/$(RELEASE_VERSION)/rpm64/ 145 | 146 | out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd 147 | mkdir -p out/$(RELEASE_VERSION)/rpm32/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)} 148 | cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/rpm32/usr/bin/ 149 | cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm32/usr/share/doc/websocketd-$(RELEASE_VERSION) 150 | cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm32/usr/share/man/man1/websocket.1.gz 151 | 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/ 152 | rm -rf out/$(RELEASE_VERSION)/rpm32/ 153 | 154 | 155 | # Clean up 156 | clobber: clean 157 | rm -rf $(GO_DIR) 158 | 159 | 160 | clean: 161 | rm -rf out 162 | 163 | .PHONY: all build deb rpm localgo clobber clean 164 | -------------------------------------------------------------------------------- /release/README: -------------------------------------------------------------------------------- 1 | Release scripts for websocketd 2 | ============================== 3 | 4 | Perform a fully automated repeatable release of websocketd. 5 | 6 | This is three-stage process in normal release cycle that's performed by 7 | repository maintainers. 8 | 9 | * Update CHANGES and Tag 10 | * Build 11 | * Release 12 | 13 | Those that do not have permissions to push tags could still use this build 14 | chain to build their own customized versions or packages. 15 | 16 | ## Step 1: Update CHANGES and Tag 17 | 18 | First edit and commit CHANGES file. List all significant updates between releases. 19 | 20 | Annotated tags require for release: 21 | 22 | git tag -a vx.x.x 23 | 24 | To see currently tagged version run tag with -l option. 25 | 26 | ## Step 2: Build 27 | 28 | release/Makefile contains all required to download pre-verified for build release of Go and cross-compile 29 | websocketd for all platforms that we release for. 30 | 31 | cd release 32 | make 33 | 34 | Is generally recommended but other build options are available: 35 | 36 | * go-compile: build cross-platform golang pakages 37 | * binaries: only create websocketd binaries, do not generate zip distributions 38 | * deb, rpm: only create particular kind of packages 39 | * clean-go: remove build files for go 40 | * clean-out: remove built binaries, zip and linux packages 41 | * clean: remove everything (go and release files) 42 | 43 | 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). 44 | 45 | Create CHECKSUMS.asc file using gpg and key that other developers shared with you: 46 | 47 | gpg -u KEYID --clearsign CHECKSUMS 48 | 49 | (key has to be installed in your gpg environment first) 50 | 51 | 52 | ## Step 3: Release 53 | 54 | In order to make release official following steps required: 55 | 56 | # push tags (assuming joewalnes repo is origin): 57 | go push --tags origin master:master 58 | 59 | Upload files to github release (to be automated). Use these instructions for now: https://github.com/blog/1547-release-your-software 60 | 61 | 62 | -------------------------------------------------------------------------------- /release/websocketd.man: -------------------------------------------------------------------------------- 1 | .\" Manpage for websocketd. 2 | .\" Contact abc@alexsergeyev.com to correct errors or typos. 3 | .TH websocketd 8 "28 Sep 2014" "0.0" "websocketd man page" 4 | .SH NAME 5 | websocketd \- turns any program that uses STDIN/STDOUT into a WebSocket server. 6 | .SH SYNOPSIS 7 | websocketd [options] COMMAND [command args] 8 | 9 | or 10 | 11 | websocketd [options] --dir=SOMEDIR 12 | .SH DESCRIPTION 13 | \fBwebsocketd\fR is a command line tool that will allow any executable program 14 | that accepts input on stdin and produces output on stdout to be turned into 15 | a WebSocket server. 16 | 17 | To learn more about websocketd visit \fIhttp://websocketd.com\fR and project WIKI 18 | on GitHub! 19 | .SH OPTIONS 20 | A summary of the options supported by websocketd is included below. 21 | .PP 22 | \-\-port=PORT 23 | .RS 4 24 | HTTP port to listen on. 25 | .RE 26 | .PP 27 | \-\-address=ADDRESS 28 | .RS 4 29 | Address to bind to (multiple options allowed). Use square brackets to specify IPv6 address. Default: "" (all) 30 | .RE 31 | .PP 32 | \-\-sameorigin={true,false} 33 | .RS 4 34 | Restrict (HTTP 403) protocol upgrades if the Origin header does not match to requested HTTP Host. Default: false. 35 | .RE 36 | .PP 37 | --origin=host[:port][,host[:port]...] 38 | .RS 4 39 | 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) 40 | .RE 41 | .PP 42 | \-\-ssl \-\-sslcert=FILE \-\-sslkey=FILE 43 | .RS 4 44 | Listen for HTTPS socket instead of HTTP. All three options must be used or all of them should be omitted. 45 | .RE 46 | .PP 47 | \-\-passenv VAR[,VAR...] 48 | .RS 4 49 | Lists environment variables allowed to be passed to executed scripts. 50 | .RE 51 | .PP 52 | \-\-reverselookup={true,false} 53 | .RS 4 54 | Perform DNS reverse lookups on remote clients. Default: true 55 | .RE 56 | .PP 57 | \-\-dir=DIR 58 | .RS 4 59 | 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. 60 | .RE 61 | .PP 62 | \-\-staticdir=DIR 63 | .RS 4 64 | Serve static files in this directory over HTTP. 65 | .RE 66 | .PP 67 | \-\-cgidir=DIR 68 | .RS 4 69 | Serve CGI scripts in this directory over HTTP. 70 | .RE 71 | .PP 72 | \-\-help 73 | .RS 4 74 | Print help and exit. 75 | .RE 76 | .PP 77 | \-\-version 78 | .RS 4 79 | Print version and exit. 80 | .RE 81 | .PP 82 | \-\-license 83 | .RS 4 84 | Print license and exit. 85 | .RE 86 | .PP 87 | \-\-devconsole 88 | .RS 4 89 | 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. 90 | .RE 91 | .PP 92 | \-\-loglevel=LEVEL 93 | .RS 4 94 | Log level to use (default access). From most to least verbose: debug, trace, access, info, error, fatal 95 | .RE 96 | .SH SEE ALSO 97 | .RS 2 98 | * full documentation at \fIhttp://websocketd.com\fR 99 | .RE 100 | .RS 2 101 | * project source at \fIhttps://github.com/joewalnes/websocketd\fR 102 | .RE 103 | .SH BUGS 104 | 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 105 | 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 106 | into autoflush mode (e.g. pseudo-terminal wrapper "unbuffer"). 107 | 108 | Active issues in development are discussed on GitHub: \fIhttps://github.com/joewalnes/websocketd/issues\fR. 109 | 110 | Please use that page to share your concerns and ideas about \fBwebsocketd\fR, authors would greatly appreciate your help! 111 | .SH AUTHOR 112 | Copyright 2013-2014 Joe Walnes and the websocketd team. All rights reserved. 113 | 114 | BSD license: Run 'websocketd \-\-license' for details. 115 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "runtime" 11 | ) 12 | 13 | // This value can be set for releases at build time using: 14 | // go {build|run} -ldflags "-X main.version 1.2.3 -X main.buildinfo timestamp-@githubuser-platform". 15 | // If unset, Version() shall return "DEVBUILD". 16 | var version string = "DEVBUILD" 17 | var buildinfo string = "--" 18 | 19 | func Version() string { 20 | return fmt.Sprintf("%s (%s %s-%s) %s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH, buildinfo) 21 | } 22 | --------------------------------------------------------------------------------