Repository: joewalnes/websocketd
Branch: master
Commit: 8d6da3004585
Files: 120
Total size: 147.3 KB
Directory structure:
gitextract_mkgu5c8j/
├── .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
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
websocketd
go-*
================================================
FILE: AUTHORS
================================================
websocketd authors
==================
Joe Walnes <joe@walnes.com>
Gareth Jones <gareth.e.jones@gmail.com>
Ajit George <ajit@ajitgeorge.com>
Alex Sergeyev <abc@alexsergeyev.com>
================================================
FILE: CHANGES
================================================
Version 0.4.1 (Jan 24, 2021)
* Minor changes only
* Updated to Go 1.15.7
Version 0.3.1 (Jan 28, 2019)
* Minor improvements to websocketd itself
* Use of go modules, gorilla websockets set to 1.4.0
* Binaries build code switched to 1.11.5 (improving underlying protocol handlers)
Version 0.3.0 (??, 2017)
* Migration of underlying websocket server to Gorilla Websocket lib.
* Binaries build code switched to 1.9.2
Version 0.2.12 (Feb 17, 2016)
* Update of underlying go standard libraries change how SSL works. SSL3 is no longer supported.
* Support of commands that do not provide text IO (using them as binary websocket frames)
* Minor changes in examples and --help output
Version 0.2.11 (Jul 1, 2015)
* PATH env variable is now passed to process by default
* new --header* flags could generate custom HTTP headers for all websocketd-generated answers
* fixed bug causing process to hang when WebSockets client disconnect is detected
* minor changes for console app (default url building logic and tab char printing)
* multiple changes of examples.
Version 0.2.10 (Feb 16, 2015)
* fixes for null-origin situations (#75, #96)
* better bash examples (#103)
* changelog and checksums for released files (#101, #105)
Version 0.2.9 (May 19, 2014)
* ability to listen multiple IP addresses (#40, #43)
* proper support for TLS (#17)
* resource limits enforcement (a.k.a. maxforks feature, #46)
* passenv option to limit environment variables visible by running commands (#4)
* fix for problem of closing upgraded websocket connection when script is not found (#29)
* websocket origin restrictions via command line option (#20)
* minor update for help flag behavior
* minor fix for devconsole
Version 0.2.8 (Jan 11, 2014)
* ...
================================================
FILE: LICENSE
================================================
Copyright (c) 2014, Joe Walnes and the websocketd authors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: Makefile
================================================
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Self contained Go build file that will download and install (locally) the correct
# version of Go, and build our programs. Go does not need to be installed on the
# system (and if it already is, it will be ignored).
# To manually invoke the locally installed Go, use ./go
# Go installation config.
GO_VER=1.11.5
SYSTEM_NAME:=$(shell uname -s | tr '[:upper:]' '[:lower:]')
SYSTEM_ARCH:=$(shell uname -m)
GO_ARCH:=$(if $(filter x86_64, $(SYSTEM_ARCH)),amd64,386)
GO_VERSION:=$(GO_VER).$(SYSTEM_NAME)-$(GO_ARCH)
GO_DOWNLOAD_URL:=https://dl.google.com/go/go$(GO_VERSION).tar.gz
GO_DIR:=go-$(GO_VER)
# Build websocketd binary
websocketd: $(GO_DIR)/bin/go $(wildcard *.go) $(wildcard libwebsocketd/*.go)
$(GO_DIR)/bin/go build
localgo: $(GO_DIR)/bin/go
# Download and unpack Go distribution.
$(GO_DIR)/bin/go:
mkdir -p $(GO_DIR)
rm -f $@
@echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR)
curl -s $(GO_DOWNLOAD_URL) | tar xfz - --strip-components=1 -C $(GO_DIR)
# Clean up binary
clean:
rm -rf websocketd
.PHONY: clean
# Also clean up downloaded Go
clobber: clean
rm -rf $(wildcard go-v*)
.PHONY: clobber
================================================
FILE: README.md
================================================
websocketd
==========
`websocketd` is a small command-line tool that will wrap an existing command-line interface program, and allow it to be accessed via a WebSocket.
WebSocket-capable applications can now be built very easily. As long as you can write an executable program that reads `STDIN` and writes to `STDOUT`, you can build a WebSocket server. Do it in Python, Ruby, Perl, Bash, .NET, C, Go, PHP, Java, Clojure, Scala, Groovy, Expect, Awk, VBScript, Haskell, Lua, R, whatever! No networking libraries necessary.
-[@joewalnes](https://twitter.com/joewalnes)
Details
-------
Upon startup, `websocketd` will start a WebSocket server on a specified port, and listen for connections.
Upon a connection, it will fork the appropriate process, and disconnect the process when the WebSocket connection closes (and vice-versa).
Any message sent from the WebSocket client will be piped to the process's `STDIN` stream, followed by a `\n` newline.
Any text printed by the process to `STDOUT` shall be sent as a WebSocket message whenever a `\n` newline is encountered.
Download
--------
If you're on a Mac, you can install `websocketd` using [Homebrew](http://brew.sh/). Just run `brew install websocketd`. For other operating systems, or if you don't want to use Homebrew, check out the link below.
**[Download for Linux, OS X and Windows](https://github.com/joewalnes/websocketd/wiki/Download-and-install)**
Quickstart
----------
To get started, we'll create a WebSocket endpoint that will accept connections, then send back messages, counting to 10 with 1 second pause between each one, before disconnecting.
To show how simple it is, let's do it in Bash!
__count.sh__:
```sh
#!/bin/bash
for ((COUNT = 1; COUNT <= 10; COUNT++)); do
echo $COUNT
sleep 1
done
```
Before turning it into a WebSocket server, let's test it from the command line. The beauty of `websocketd` is that servers work equally well in the command line, or in shell scripts, as they do in the server - with no modifications required.
```sh
$ chmod +x count.sh
$ ./count.sh
1
2
3
4
5
6
7
8
9
10
```
Now let's turn it into a WebSocket server:
```sh
$ websocketd --port=8080 ./count.sh
```
Finally, let's create a web-page to test it.
__count.html__:
```html
<!DOCTYPE html>
<pre id="log"></pre>
<script>
// helper function: log message to screen
function log(msg) {
document.getElementById('log').textContent += msg + '\n';
}
// setup websocket with callbacks
var ws = new WebSocket('ws://localhost:8080/');
ws.onopen = function() {
log('CONNECT');
};
ws.onclose = function() {
log('DISCONNECT');
};
ws.onmessage = function(event) {
log('MESSAGE: ' + event.data);
};
</script>
```
Open this page in your web-browser. It will even work if you open it directly
from disk using a `file://` URL.
More Features
-------------
* Very simple install. Just [download](https://github.com/joewalnes/websocketd/wiki/Download-and-install) the single executable for Linux, Mac or Windows and run it. Minimal dependencies, no installers, no package managers, no external libraries. Suitable for development and production servers.
* Server side scripts can access details about the WebSocket HTTP request (e.g. remote host, query parameters, cookies, path, etc) via standard [CGI environment variables](https://github.com/joewalnes/websocketd/wiki/Environment-variables).
* As well as serving websocket daemons it also includes a static file server and classic CGI server for convenience.
* Command line help available via `websocketd --help`.
* Includes [WebSocket developer console](https://github.com/joewalnes/websocketd/wiki/Developer-console) to make it easy to test your scripts before you've built a JavaScript frontend.
* [Examples in many programming languages](https://github.com/joewalnes/websocketd/tree/master/examples) are available to help you getting started.
User Manual
-----------
**[More documentation in the user manual](https://github.com/joewalnes/websocketd/wiki)**
Example Projects
----------------
* [Plot real time Linux CPU/IO/Mem stats to a HTML5 dashboard using websocketd and vmstat](https://github.com/joewalnes/web-vmstats) _(for Linux)_
* [Arbitrary REPL in the browser using websocketd](https://github.com/rowanthorpe/ws-repl)
* [Retrieve SQL data from server with LiveCode and webSocketd](https://github.com/samansjukur/wslc)
* [List files from a configured folder](https://github.com/dbalakirev/directator) _(for Linux)_
* [Listen for gamepad events and report them to the system](https://github.com/experiment322/controlloid-server) _(this + android = gamepad emulator)_
Got more examples? Open a pull request.
My Other Projects
-----------------
* [ReconnectingWebSocket](https://github.com/joewalnes/reconnecting-websocket) - Simplest way to add some robustness to your WebSocket connections.
* [Smoothie Charts](http://smoothiecharts.org/) - JavaScript charts for streaming data.
* Visit [The Igloo Lab](http://theigloolab.com/) to see and subscribe to other thingies I make.
And [follow @joewalnes](https://twitter.com/joewalnes)!
================================================
FILE: config.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/joewalnes/websocketd/libwebsocketd"
)
type Config struct {
Addr []string // TCP addresses to listen on. e.g. ":1234", "1.2.3.4:1234" or "[::1]:1234"
MaxForks int // Number of allowable concurrent forks
LogLevel libwebsocketd.LogLevel
RedirPort int
CertFile, KeyFile string
*libwebsocketd.Config
}
type Arglist []string
func (al *Arglist) String() string {
return fmt.Sprintf("%v", []string(*al))
}
func (al *Arglist) Set(value string) error {
*al = append(*al, value)
return nil
}
// Borrowed from net/http/cgi
var defaultPassEnv = map[string]string{
"darwin": "PATH,DYLD_LIBRARY_PATH",
"freebsd": "PATH,LD_LIBRARY_PATH",
"hpux": "PATH,LD_LIBRARY_PATH,SHLIB_PATH",
"irix": "PATH,LD_LIBRARY_PATH,LD_LIBRARYN32_PATH,LD_LIBRARY64_PATH",
"linux": "PATH,LD_LIBRARY_PATH",
"openbsd": "PATH,LD_LIBRARY_PATH",
"solaris": "PATH,LD_LIBRARY_PATH,LD_LIBRARY_PATH_32,LD_LIBRARY_PATH_64",
"windows": "PATH,SystemRoot,COMSPEC,PATHEXT,WINDIR",
}
func parseCommandLine() *Config {
var mainConfig Config
var config libwebsocketd.Config
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
flag.CommandLine.Usage = func() {}
// If adding new command line options, also update the help text in help.go.
// The flag library's auto-generate help message isn't pretty enough.
addrlist := Arglist(make([]string, 0, 1)) // pre-reserve for 1 address
flag.Var(&addrlist, "address", "Interfaces to bind to (e.g. 127.0.0.1 or [::1]).")
// server config options
portFlag := flag.Int("port", 0, "HTTP port to listen on")
versionFlag := flag.Bool("version", false, "Print version and exit")
licenseFlag := flag.Bool("license", false, "Print license and exit")
logLevelFlag := flag.String("loglevel", "access", "Log level, one of: debug, trace, access, info, error, fatal")
sslFlag := flag.Bool("ssl", false, "Use TLS on listening socket (see also --sslcert and --sslkey)")
sslCert := flag.String("sslcert", "", "Should point to certificate PEM file when --ssl is used")
sslKey := flag.String("sslkey", "", "Should point to certificate private key file when --ssl is used")
maxForksFlag := flag.Int("maxforks", 0, "Max forks, zero means unlimited")
closeMsFlag := flag.Uint("closems", 0, "Time to start sending signals (0 never)")
redirPortFlag := flag.Int("redirport", 0, "HTTP port to redirect to canonical --port address")
// lib config options
binaryFlag := flag.Bool("binary", false, "Set websocketd to experimental binary mode (default is line by line)")
reverseLookupFlag := flag.Bool("reverselookup", false, "Perform reverse DNS lookups on remote clients")
scriptDirFlag := flag.String("dir", "", "Base directory for WebSocket scripts")
staticDirFlag := flag.String("staticdir", "", "Serve static content from this directory over HTTP")
cgiDirFlag := flag.String("cgidir", "", "Serve CGI scripts from this directory over HTTP")
devConsoleFlag := flag.Bool("devconsole", false, "Enable development console (cannot be used in conjunction with --staticdir)")
passEnvFlag := flag.String("passenv", defaultPassEnv[runtime.GOOS], "List of envvars to pass to subprocesses (others will be cleaned out)")
sameOriginFlag := flag.Bool("sameorigin", false, "Restrict upgrades if origin and host headers differ")
allowOriginsFlag := flag.String("origin", "", "Restrict upgrades if origin does not match the list")
headers := Arglist(make([]string, 0))
headersWs := Arglist(make([]string, 0))
headersHttp := Arglist(make([]string, 0))
flag.Var(&headers, "header", "Custom headers for any response.")
flag.Var(&headersWs, "header-ws", "Custom headers for successful WebSocket upgrade responses.")
flag.Var(&headersHttp, "header-http", "Custom headers for all but WebSocket upgrade HTTP responses.")
err := flag.CommandLine.Parse(os.Args[1:])
if err != nil {
if err == flag.ErrHelp {
PrintHelp()
os.Exit(0)
} else {
ShortHelp()
os.Exit(2)
}
}
port := *portFlag
if port == 0 {
if *sslFlag {
port = 443
} else {
port = 80
}
}
if socknum := len(addrlist); socknum != 0 {
mainConfig.Addr = make([]string, socknum)
for i, addrSingle := range addrlist {
mainConfig.Addr[i] = fmt.Sprintf("%s:%d", addrSingle, port)
}
} else {
mainConfig.Addr = []string{fmt.Sprintf(":%d", port)}
}
mainConfig.MaxForks = *maxForksFlag
mainConfig.RedirPort = *redirPortFlag
mainConfig.LogLevel = libwebsocketd.LevelFromString(*logLevelFlag)
if mainConfig.LogLevel == libwebsocketd.LogUnknown {
fmt.Printf("Incorrect loglevel flag '%s'. Use --help to see allowed values.\n", *logLevelFlag)
ShortHelp()
os.Exit(1)
}
config.Headers = []string(headers)
config.HeadersWs = []string(headersWs)
config.HeadersHTTP = []string(headersHttp)
config.CloseMs = *closeMsFlag
config.Binary = *binaryFlag
config.ReverseLookup = *reverseLookupFlag
config.Ssl = *sslFlag
config.ScriptDir = *scriptDirFlag
config.StaticDir = *staticDirFlag
config.CgiDir = *cgiDirFlag
config.DevConsole = *devConsoleFlag
config.StartupTime = time.Now()
config.ServerSoftware = fmt.Sprintf("websocketd/%s", Version())
config.HandshakeTimeout = time.Millisecond * 1500 // only default for now
if len(os.Args) == 1 {
fmt.Printf("Command line arguments are missing.\n")
ShortHelp()
os.Exit(1)
}
if *versionFlag {
fmt.Printf("%s %s\n", HelpProcessName(), Version())
os.Exit(0)
}
if *licenseFlag {
fmt.Printf("%s %s\n", HelpProcessName(), Version())
fmt.Printf("%s\n", libwebsocketd.License)
os.Exit(0)
}
// Reading SSL options
if config.Ssl {
if *sslCert == "" || *sslKey == "" {
fmt.Fprintf(os.Stderr, "Please specify both --sslcert and --sslkey when requesting --ssl.\n")
os.Exit(1)
}
} else {
if *sslCert != "" || *sslKey != "" {
fmt.Fprintf(os.Stderr, "You should not be using --ssl* flags when there is no --ssl option.\n")
os.Exit(1)
}
}
mainConfig.CertFile = *sslCert
mainConfig.KeyFile = *sslKey
// Building config.ParentEnv to avoid calling Environ all the time in the scripts
// (caller is responsible for wiping environment if desired)
config.ParentEnv = make([]string, 0)
newlineCleaner := strings.NewReplacer("\n", " ", "\r", " ")
for _, key := range strings.Split(*passEnvFlag, ",") {
if key != "HTTPS" {
if v := os.Getenv(key); v != "" {
// inevitably adding flavor of libwebsocketd appendEnv func.
// it's slightly nicer than in net/http/cgi implementation
if clean := strings.TrimSpace(newlineCleaner.Replace(v)); clean != "" {
config.ParentEnv = append(config.ParentEnv, fmt.Sprintf("%s=%s", key, clean))
}
}
}
}
if *allowOriginsFlag != "" {
config.AllowOrigins = strings.Split(*allowOriginsFlag, ",")
}
config.SameOrigin = *sameOriginFlag
args := flag.Args()
if len(args) < 1 && config.ScriptDir == "" && config.StaticDir == "" && config.CgiDir == "" {
fmt.Fprintf(os.Stderr, "Please specify COMMAND or provide --dir, --staticdir or --cgidir argument.\n")
ShortHelp()
os.Exit(1)
}
if len(args) > 0 {
if config.ScriptDir != "" {
fmt.Fprintf(os.Stderr, "Ambiguous. Provided COMMAND and --dir argument. Please only specify just one.\n")
ShortHelp()
os.Exit(1)
}
if path, err := exec.LookPath(args[0]); err == nil {
config.CommandName = path // This can be command in PATH that we are able to execute
config.CommandArgs = flag.Args()[1:]
config.UsingScriptDir = false
} else {
fmt.Fprintf(os.Stderr, "Unable to locate specified COMMAND '%s' in OS path.\n", args[0])
ShortHelp()
os.Exit(1)
}
}
if config.ScriptDir != "" {
scriptDir, err := filepath.Abs(config.ScriptDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not resolve absolute path to dir '%s'.\n", config.ScriptDir)
ShortHelp()
os.Exit(1)
}
inf, err := os.Stat(scriptDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not find your script dir '%s'.\n", config.ScriptDir)
ShortHelp()
os.Exit(1)
}
if !inf.IsDir() {
fmt.Fprintf(os.Stderr, "Did you mean to specify COMMAND instead of --dir '%s'?\n", config.ScriptDir)
ShortHelp()
os.Exit(1)
} else {
config.ScriptDir = scriptDir
config.UsingScriptDir = true
}
}
if config.CgiDir != "" {
if inf, err := os.Stat(config.CgiDir); err != nil || !inf.IsDir() {
fmt.Fprintf(os.Stderr, "Your CGI dir '%s' is not pointing to an accessible directory.\n", config.CgiDir)
ShortHelp()
os.Exit(1)
}
}
if config.StaticDir != "" {
if inf, err := os.Stat(config.StaticDir); err != nil || !inf.IsDir() {
fmt.Fprintf(os.Stderr, "Your static dir '%s' is not pointing to an accessible directory.\n", config.StaticDir)
ShortHelp()
os.Exit(1)
}
}
mainConfig.Config = &config
return &mainConfig
}
================================================
FILE: examples/bash/README.txt
================================================
This examples directory shows some examples written in Bash.
You can also test the command files by running from the command line.
================================================
FILE: examples/bash/chat.sh
================================================
#!/bin/bash
# Copyright 2013 Jeroen Janssens
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Run a simple chat server: websocketd --devconsole --port 8080 ./chat.sh
#
# Please note that this example requires GNU tail, which is not the default
# tail on OS X. Even though this script properly escapes the variables,
# please keep in mind that it is in general a bad idea to read
# untrusted data into variables and pass this onto the command line.
echo "Please enter your name:"; read USER
echo "[$(date)] ${USER} joined the chat" >> chat.log
echo "[$(date)] Welcome to the chat ${USER}!"
tail -n 0 -f chat.log --pid=$$ | grep --line-buffered -v "] ${USER}>" &
while read MSG; do echo "[$(date)] ${USER}> ${MSG}" >> chat.log; done
================================================
FILE: examples/bash/count.sh
================================================
#!/bin/bash
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Simple example script that counts to 10 at ~2Hz, then stops.
for ((COUNT = 1; COUNT <= 10; COUNT++))
do
echo $COUNT
sleep 0.5
done
================================================
FILE: examples/bash/dump-env.sh
================================================
#!/bin/bash
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Standard CGI(ish) environment variables, as defined in
# http://tools.ietf.org/html/rfc3875
NAMES="""
AUTH_TYPE
CONTENT_LENGTH
CONTENT_TYPE
GATEWAY_INTERFACE
PATH_INFO
PATH_TRANSLATED
QUERY_STRING
REMOTE_ADDR
REMOTE_HOST
REMOTE_IDENT
REMOTE_PORT
REMOTE_USER
REQUEST_METHOD
REQUEST_URI
SCRIPT_NAME
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
UNIQUE_ID
HTTPS
"""
for NAME in ${NAMES}
do
echo "${NAME}=${!NAME:-<unset>}"
done
# Additional HTTP headers
env | grep '^HTTP_'
================================================
FILE: examples/bash/greeter.sh
================================================
#!/bin/bash
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# For each line FOO received on STDIN, respond with "Hello FOO!".
while read LINE
do
echo "Hello $LINE!"
done
================================================
FILE: examples/bash/send-receive.sh
================================================
#!/bin/bash
while true; do
cnt=0
while read -t 0.01 _; do
((cnt++))
done
echo "$(date)" "($cnt line(s) received)"
sleep $((RANDOM % 10 + 1)) & wait
done
================================================
FILE: examples/c#/.gitignore
================================================
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/
# mstest test results
TestResults
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
build/
test/
deploy/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
*.vssscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*ncrunch*/*
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Publish Web Output
*.Publish.xml
# Windows Azure Build Output
csx
*.build.csdef
# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
================================================
FILE: examples/c#/Count/App.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
================================================
FILE: examples/c#/Count/Count.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Count</RootNamespace>
<AssemblyName>Count</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
================================================
FILE: examples/c#/Count/Program.cs
================================================
using System;
using System.Linq;
using System.Threading;
namespace Count
{
class Program
{
static void Main(string[] args)
{
foreach (var i in Enumerable.Range(1, 10))
{
Console.WriteLine(i);
Thread.Sleep(1000);
}
}
}
}
================================================
FILE: examples/c#/Count/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Count")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Count")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("38cb6838-4839-498f-b09f-d0e67a2e9974")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: examples/c#/Echo/App.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
================================================
FILE: examples/c#/Echo/Echo.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{65414388-1058-414C-910F-CBD58E2B064A}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Echo</RootNamespace>
<AssemblyName>Echo</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
================================================
FILE: examples/c#/Echo/Program.cs
================================================
using System;
namespace Echo
{
class Program
{
static void Main(string[] args)
{
while (true)
{
var msg = Console.ReadLine();
Console.WriteLine(msg);
}
}
}
}
================================================
FILE: examples/c#/Echo/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Echo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Echo")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6199e327-f4cd-438c-a6c5-87861f837fb1")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: examples/c#/Examples.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Echo", "Echo\Echo.csproj", "{65414388-1058-414C-910F-CBD58E2B064A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Count", "Count\Count.csproj", "{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.Build.0 = Release|Any CPU
{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
================================================
FILE: examples/c#/README.md
================================================
## Running the examples on Windows
1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH)
2. open and build the **Examples.sln** solution
3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it
4. double click the **run_count.cmd** to start the count example, go to the [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browser
================================================
FILE: examples/c#/run_count.cmd
================================================
websocketd --port=8080 --devconsole bin\Count.exe
================================================
FILE: examples/c#/run_echo.cmd
================================================
websocketd --port=8080 --devconsole bin\Echo.exe
================================================
FILE: examples/cgi-bin/README.txt
================================================
This examples directory shows how websocketd can also serve CGI scripts via HTTP.
$ websocketd --port=1234 --cgidir=examples/cgi-bin
# Then access http://localhost:1234/dump-env.sh
You can also test the command files by running from the command line.
================================================
FILE: examples/cgi-bin/dump-env.sh
================================================
#!/bin/sh
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Standard CGI(ish) environment variables, as defined in
# http://tools.ietf.org/html/rfc3875
NAMES="""
AUTH_TYPE
CONTENT_LENGTH
CONTENT_TYPE
GATEWAY_INTERFACE
PATH_INFO
PATH_TRANSLATED
QUERY_STRING
REMOTE_ADDR
REMOTE_HOST
REMOTE_IDENT
REMOTE_PORT
REMOTE_USER
REQUEST_METHOD
REQUEST_URI
SCRIPT_NAME
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
UNIQUE_ID
HTTPS
"""
echo "Content-type: text/plain"
echo
for NAME in ${NAMES}
do
eval "value=\${${NAME}}"
env -i "${NAME}=${value:-<unset>}"
done
# Additional HTTP headers
env | egrep '^(HTTP|SSL)_'
================================================
FILE: examples/f#/.gitignore
================================================
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/
# mstest test results
TestResults
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
build/
test/
deploy/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
*.vssscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*ncrunch*/*
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Publish Web Output
*.Publish.xml
# Windows Azure Build Output
csx
*.build.csdef
# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
================================================
FILE: examples/f#/Count/App.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="4.0.0.0" newVersion="4.3.0.0"/>
<bindingRedirect oldVersion="2.3.5.0" newVersion="4.3.0.0"/>
<bindingRedirect oldVersion="2.0.0.0" newVersion="4.3.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
================================================
FILE: examples/f#/Count/Count.fsproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>367c65fd-485b-45c0-9c0e-7ce455951eae</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Count</RootNamespace>
<AssemblyName>Count</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Name>Count</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>..\bin\Count.XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>..\bin\Count.XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.fs" />
<None Include="App.config" />
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets" Condition=" Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
================================================
FILE: examples/f#/Count/Program.fs
================================================
open System
open System.Threading
[<EntryPoint>]
let main argv =
[| 1..10 |] |> Array.iter (Console.WriteLine >> (fun _ -> Thread.Sleep(1000)))
0 // return an integer exit code
================================================
FILE: examples/f#/Echo/App.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="4.0.0.0" newVersion="4.3.0.0"/>
<bindingRedirect oldVersion="2.3.5.0" newVersion="4.3.0.0"/>
<bindingRedirect oldVersion="2.0.0.0" newVersion="4.3.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
================================================
FILE: examples/f#/Echo/Echo.fsproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>6f680332-caa0-447b-a87e-af272ded5701</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Echo</RootNamespace>
<AssemblyName>Echo</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Name>Echo</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>..\bin</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>..\bin\Echo.XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>..\bin</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>bin\Release\Echo.XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.fs" />
<None Include="App.config" />
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets" Condition=" Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
================================================
FILE: examples/f#/Echo/Program.fs
================================================
open System
[<EntryPoint>]
let main argv =
let rec recLoop () =
Console.ReadLine() |> Console.WriteLine
recLoop()
recLoop()
0 // return an integer exit code
================================================
FILE: examples/f#/Examples.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Echo", "Echo\Echo.fsproj", "{6F680332-CAA0-447B-A87E-AF272DED5701}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Count", "Count\Count.fsproj", "{367C65FD-485B-45C0-9C0E-7CE455951EAE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.Build.0 = Release|Any CPU
{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
================================================
FILE: examples/f#/README.md
================================================
## Running the examples on Windows
1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH)
2. open and build the **Examples.sln** solution
3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it
4. double click the **run_count.cmd** to start the count example, go to the [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browser
================================================
FILE: examples/f#/run_count.cmd
================================================
websocketd --port=8080 --devconsole bin\Count.exe
================================================
FILE: examples/f#/run_echo.cmd
================================================
websocketd --port=8080 --devconsole bin\Echo.exe
================================================
FILE: examples/hack/README.md
================================================
This examples directory shows some examples written in [Hack](https://hacklang.org).
### Requirements :
- [HHVM](https://github.com/facebook/hhvm) 3.30+
- [HSL ( Hack Standard Library )](https://github.com/hhvm/hsl) 3.30+
- [HSL Experimental](https://github.com/hhvm/hsl-experimental) 3.30+
You can also test the command files by running from the command line :
```
$ hhvm count.hh
1
2
3
4
5
6
7
8
9
10
```
================================================
FILE: examples/hack/count.hh
================================================
#!/usr/bin/hhvm
<?hh // strict
use namespace HH\Lib\Str;
use function HH\Lib\Experimental\IO\request_output;
// Simple example script that counts to 10 at ~2Hz, then stops.
<<__EntryPoint>>
async function count_to_ten(): Awaitable<noreturn> {
$output = request_output();
for ($count = 1; $count <= 10; $count++) {
await $output->writeAsync(
Str\format("%d\n",$count)
);
HH\Asio\usleep(500000);
}
// flush output
await $output->flushAsync();
exit(0);
}
================================================
FILE: examples/hack/dump-env.hh
================================================
#!/usr/bin/hhvm
<?hh // strict
use namespace HH\Lib\Str;
use function HH\Lib\Experimental\IO\request_output;
<<__EntryPoint>>
async function dumpEnv(): Awaitable<noreturn> {
// Standard CGI(ish) environment variables, as defined in
// http://tools.ietf.org/html/rfc3875
$names = keyset[
'AUTH_TYPE',
'CONTENT_LENGTH',
'CONTENT_TYPE',
'GATEWAY_INTERFACE',
'PATH_INFO',
'PATH_TRANSLATED',
'QUERY_STRING',
'REMOTE_ADDR',
'REMOTE_HOST',
'REMOTE_IDENT',
'REMOTE_PORT',
'REMOTE_USER',
'REQUEST_METHOD',
'REQUEST_URI',
'SCRIPT_NAME',
'SERVER_NAME',
'SERVER_PORT',
'SERVER_PROTOCOL',
'SERVER_SOFTWARE',
'UNIQUE_ID',
'HTTPS'
];
/* HH_IGNORE_ERROR[2050] using global variable */
$server = dict($_SERVER);
$ouput = request_output();
foreach($names as $name) {
await $output->writeAsync(
Str\format("%s = %s\n", $name, $server[$name] ?? '<unset>')
);
}
// Additional HTTP headers
foreach($server as $k => $v) {
if ($k is string && Str\starts_with($k, 'HTTP_')) {
await $output->writeAsync(
Str\format("%s = %s\n", $k, $v as string)
);
}
}
// flush output
await $output->flushAsync();
exit(0);
}
================================================
FILE: examples/hack/greeter.hh
================================================
#!/usr/bin/hhvm
<?hh // strict
use namespace HH\Lib\Str;
use namespace HH\Lib\Experimental\IO;
<<__EntryPoint>>
async function greeter(): Awaitable<noreturn> {
// For each line FOO received on STDIN, respond with "Hello FOO!".
$input = IO\request_input();
$output = IO\request_output();
while(!$input->isEndOfFile()) {
await $ouput->writeAsync(
Str\format("Hello %s!\n", await $input->readLineAsync())
);
}
// flush output
await $output->flushAsync();
exit(0);
}
================================================
FILE: examples/haskell/README.md
================================================
## Haskell examples
### Count
Start the server with
```
$ websocketd --port=8080 --devconsole --passenv PATH ./count.hs
```
The passing of `PATH` was required for me because a typical Haskell installation of `runhaskell` does not go into `/usr/bin` but more like `/usr/local/bin`.
### Greeter
The greeter server waits for a line of text to be sent, then sends back a greeting in response, and continues to wait for more lines to come.
```
$ websocketd --port=8080 --devconsole --passenv PATH ./greeter.hs
```
================================================
FILE: examples/haskell/count.hs
================================================
#!/usr/bin/env runhaskell
import Control.Monad (forM_)
import Control.Concurrent (threadDelay)
import System.IO (hFlush, stdout)
-- | Count from 1 to 10 with a sleep
main :: IO ()
main = forM_ [1 :: Int .. 10] $ \count -> do
print count
hFlush stdout
threadDelay 500000
================================================
FILE: examples/haskell/greeter.hs
================================================
#!/usr/bin/env runhaskell
import Control.Monad (unless)
import System.IO (hFlush, stdout, stdin, hIsEOF)
-- | For each line FOO received on STDIN, respond with "Hello FOO!".
main :: IO ()
main = do
eof <- hIsEOF stdin
unless eof $ do
line <- getLine
putStrLn $ "Hello " ++ line ++ "!"
hFlush stdout
main
================================================
FILE: examples/html/count.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>websocketd count example</title>
<style>
#count {
font: bold 150px arial;
margin: auto;
padding: 10px;
text-align: center;
}
</style>
</head>
<body>
<div id="count"></div>
<script>
var ws = new WebSocket('ws://' + (location.host ? location.host : "localhost:8080") + "/");
ws.onopen = function() {
document.body.style.backgroundColor = '#cfc';
};
ws.onclose = function() {
document.body.style.backgroundColor = null;
};
ws.onmessage = function(event) {
document.getElementById('count').textContent = event.data;
};
</script>
</body>
</html>
================================================
FILE: examples/java/Count/Count.java
================================================
public class Count {
public static void main(String[] args) {
for(int i = 1; i <= 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
================================================
FILE: examples/java/Count/count.sh
================================================
#!/bin/sh
javac Count.java
java Count
================================================
FILE: examples/java/Echo/Echo.java
================================================
import java.io.*;
public class Echo {
public static void main(String[] args) {
while(true) {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String message = in.readLine();
System.out.println(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
================================================
FILE: examples/java/Echo/echo.sh
================================================
#!/bin/sh
javac Echo.java
java Echo
================================================
FILE: examples/java/README.md
================================================
## Running the examples on Mac
1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your PATH
2. Echo Server: Run `websocketd --port=8080 --devconsole ./echo.sh` and then go to http://localhost:8080 to interact with it
3. Count Server: Run `websocketd --port=8080 ./count.sh` to start the server, then go to [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browser
================================================
FILE: examples/lua/README.md
================================================
The examples demonstrate the use of websocketd with lua. There are two examples in the directory both very basic.
1. Greeter.lua simply echos back any input made from the client
2. json_ws.lua echos back any input from the client *after* converting it into a json string
It is pretty simple to extend these examples into full fledged applications. All you need is an stdin input loop
```
local input = io.stdin:read()
while input do
-- do anything here
-- update the input
input = io.stdin:read()
end
```
any thing you `print` goes out to the websocket client
Libraries and third party modules can be used by the standard `require` statement in lua.
## Running the examples
##### 1. Download
[Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`.
##### 2. Start a server: greeter
Run `websocketd --port=8080 --devconsole lua ./greeter.lua` and then go to `http://localhost:8080` to interact with it
##### 3. Start a server: json_ws
Run `websocketd --port=8080 --devconsole lua ./json_ws.lua` and then go to `http://localhost:8080` to interact with it
If you are using luajit instead of lua you may run the examples like this
(this assumes that you've got luajit in your path)
`websocketd --port=8080 --devconsole luajit ./json_ws.lua` and then go to `http://localhost:8080`
================================================
FILE: examples/lua/greeter.lua
================================================
local input = io.stdin:read()
while input do
print(input)
io.stdout:flush()
input = io.stdin:read()
end
================================================
FILE: examples/lua/json.lua
================================================
--
-- json.lua
--
-- Copyright (c) 2015 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local json = { _version = "0.1.0" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if val[1] ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
return ( parse(str, next_char(str, 1, space_chars, true)) )
end
return json
================================================
FILE: examples/lua/json_ws.lua
================================================
local input = io.stdin:read()
local json = require("json")
while input do
print(json.encode({res="json",mess=input}))
io.stdout:flush()
input = io.stdin:read()
end
================================================
FILE: examples/nodejs/README.md
================================================
## Running the examples on Mac
##### 1. Download
[Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`.
##### 2. Start a server: getter
Run `websocketd --port=8080 --devconsole node greeter.js` and then go to `http://localhost:8080` to interact with it
##### 3. Start a server: counter
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
================================================
FILE: examples/nodejs/count.js
================================================
(function(){
var counter = 0;
var echo = function(){
if (counter === 10){
return;
}
setTimeout(function(){
counter++;
echo();
process.stdout.write(counter.toString() + "\n");
}, 500);
}
echo();
})();
================================================
FILE: examples/nodejs/greeter.js
================================================
// from node.js sample
// https://nodejs.org/api/process.html#process_process_stdin
process.stdin.setEncoding('utf8');
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write('data: ' + chunk);
}
});
================================================
FILE: examples/perl/README.txt
================================================
This examples directory shows some examples written in Perl.
You can also test the command files by running from the command line.
================================================
FILE: examples/perl/count.pl
================================================
#!/usr/bin/perl
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
use strict;
# Autoflush output
use IO::Handle;
STDOUT->autoflush(1);
use Time::HiRes qw(sleep);
# Simple example script that counts to 10 at ~2Hz, then stops.
for my $count (1 .. 10) {
print "$count\n";
sleep 0.5;
}
================================================
FILE: examples/perl/dump-env.pl
================================================
#!/usr/bin/perl
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
use strict;
# Autoflush output
use IO::Handle;
STDOUT->autoflush(1);
# Standard CGI(ish) environment variables, as defined in
# http://tools.ietf.org/html/rfc3875
my @names = qw(
AUTH_TYPE
CONTENT_LENGTH
CONTENT_TYPE
GATEWAY_INTERFACE
PATH_INFO
PATH_TRANSLATED
QUERY_STRING
REMOTE_ADDR
REMOTE_HOST
REMOTE_IDENT
REMOTE_PORT
REMOTE_USER
REQUEST_METHOD
REQUEST_URI
SCRIPT_NAME
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
UNIQUE_ID
HTTPS
);
for my $name (@names) {
my $value = $ENV{$name} || '<unset>';
print "$name=$value\n";
}
# Additional HTTP headers
for my $name (keys(%ENV)) {
if ($name =~ /^HTTP_/) {
my $value = $ENV{$name};
print "$name=$value\n";
}
}
================================================
FILE: examples/perl/greeter.pl
================================================
#!/usr/bin/perl
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
use strict;
# Autoflush output
use IO::Handle;
STDOUT->autoflush(1);
# For each line FOO received on STDIN, respond with "Hello FOO!".
while (<>) {
chomp; # remove \n
print "Hello $_!\n";
}
================================================
FILE: examples/php/README.txt
================================================
This examples directory shows some examples written in PHP.
This relies on the CLI verson of PHP being installed and in the path.
See http://www.php.net/manual/en/features.commandline.introduction.php
You can also test the command files by running from the command line.
================================================
FILE: examples/php/count.php
================================================
#!/usr/bin/php
<?php
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Simple example script that counts to 10 at ~2Hz, then stops.
for ($count = 1; $count <= 10; $count++) {
echo $count . "\n";
usleep(500000);
}
?>
================================================
FILE: examples/php/dump-env.php
================================================
#!/usr/bin/php
<?php
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Standard CGI(ish) environment variables, as defined in
// http://tools.ietf.org/html/rfc3875
$names = array(
'AUTH_TYPE',
'CONTENT_LENGTH',
'CONTENT_TYPE',
'GATEWAY_INTERFACE',
'PATH_INFO',
'PATH_TRANSLATED',
'QUERY_STRING',
'REMOTE_ADDR',
'REMOTE_HOST',
'REMOTE_IDENT',
'REMOTE_PORT',
'REMOTE_USER',
'REQUEST_METHOD',
'REQUEST_URI',
'SCRIPT_NAME',
'SERVER_NAME',
'SERVER_PORT',
'SERVER_PROTOCOL',
'SERVER_SOFTWARE',
'UNIQUE_ID',
'HTTPS'
);
foreach ($names as $name) {
$value = isset($_SERVER[$name]) ? $_SERVER[$name] : '<unset>';
echo $name . '=' . $value . "\n";
}
// Additional HTTP headers
foreach ($_SERVER as $name => $value) {
if (strpos($name, 'HTTP_') === 0) {
echo $name . '=' . $value . "\n";
}
}
================================================
FILE: examples/php/greeter.php
================================================
#!/usr/bin/php
<?php
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// For each line FOO received on STDIN, respond with "Hello FOO!".
$stdin = fopen('php://stdin', 'r');
while ($line = fgets($stdin)) {
echo 'Hello ' . trim($line) . "!\n";
}
?>
================================================
FILE: examples/python/README.txt
================================================
This examples directory shows some examples written in Python.
You can also test the command files by running from the command line.
================================================
FILE: examples/python/count.py
================================================
#!/usr/bin/python
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
from sys import stdout
from time import sleep
# Simple example script that counts to 10 at ~2Hz, then stops.
for count in range(0, 10):
print(count + 1)
stdout.flush() # Remember to flush
sleep(0.5)
================================================
FILE: examples/python/dump-env.py
================================================
#!/usr/bin/python
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
import os
from sys import stdout
# Standard CGI(ish) environment variables, as defined in
# http://tools.ietf.org/html/rfc3875
var_names = [
'AUTH_TYPE',
'CONTENT_LENGTH',
'CONTENT_TYPE',
'GATEWAY_INTERFACE',
'PATH_INFO',
'PATH_TRANSLATED',
'QUERY_STRING',
'REMOTE_ADDR',
'REMOTE_HOST',
'REMOTE_IDENT',
'REMOTE_PORT',
'REMOTE_USER',
'REQUEST_METHOD',
'REQUEST_URI',
'SCRIPT_NAME',
'SERVER_NAME',
'SERVER_PORT',
'SERVER_PROTOCOL',
'SERVER_SOFTWARE',
'UNIQUE_ID',
'HTTPS'
]
for var_name in var_names:
print('%s=%s' % (var_name, os.environ.get(var_name, '<unset>')))
stdout.flush() # Remember to flush
# Additional HTTP headers
for var_name in os.environ:
if var_name.startswith('HTTP_'):
print('%s=%s' % (var_name, os.environ[var_name]))
stdout.flush() # Remember to flush
================================================
FILE: examples/python/greeter.py
================================================
#!/usr/bin/python
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
from sys import stdin, stdout
# For each line FOO received on STDIN, respond with "Hello FOO!".
while True:
line = stdin.readline().strip()
print('Hello %s!' % line)
stdout.flush() # Remember to flush
================================================
FILE: examples/qjs/request-reply.js
================================================
#!/usr/bin/env -S qjs --module
import * as std from "std";
let line;
while ((line = std.in.getline()) != null) {
console.log("RCVD: " + line)
std.out.flush();
}
================================================
FILE: examples/ruby/README.txt
================================================
This examples directory shows some examples written in Ruby.
You can also test the command files by running from the command line.
================================================
FILE: examples/ruby/count.rb
================================================
#!/usr/bin/ruby
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Autoflush output
STDOUT.sync = true
# Simple example script that counts to 10 at ~2Hz, then stops.
(1..10).each do |count|
puts count
sleep(0.5)
end
================================================
FILE: examples/ruby/dump-env.rb
================================================
#!/usr/bin/ruby
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Autoflush output
STDOUT.sync = true
# Standard CGI(ish) environment variables, as defined in
# http://tools.ietf.org/html/rfc3875
names = [
'AUTH_TYPE',
'CONTENT_LENGTH',
'CONTENT_TYPE',
'GATEWAY_INTERFACE',
'PATH_INFO',
'PATH_TRANSLATED',
'QUERY_STRING',
'REMOTE_ADDR',
'REMOTE_HOST',
'REMOTE_IDENT',
'REMOTE_PORT',
'REMOTE_USER',
'REQUEST_METHOD',
'REQUEST_URI',
'SCRIPT_NAME',
'SERVER_NAME',
'SERVER_PORT',
'SERVER_PROTOCOL',
'SERVER_SOFTWARE',
'UNIQUE_ID',
'HTTPS',
]
names.each do |name|
value = ENV[name] || '<unset>'
puts "#{name}=#{value}"
end
# Additional HTTP headers
ENV.each do |name,value|
puts "#{name}=#{value}" if name.start_with?('HTTP_')
end
================================================
FILE: examples/ruby/greeter.rb
================================================
#!/usr/bin/ruby
# Copyright 2013 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Autoflush output
STDOUT.sync = true
# For each line FOO received on STDIN, respond with "Hello FOO!".
while 1
line = STDIN.readline.strip
puts "Hello #{line}!"
end
================================================
FILE: examples/rust/README.txt
================================================
This examples directory shows some examples written in Rust.
You can also test the command files by running from the command line.
================================================
FILE: examples/rust/count.rs
================================================
use std::io::{self, Write};
use std::{thread, time};
// Simple example script that counts to 10 at ~2Hz, then stops.
fn main() {
for i in 1..11 {
println!("{}", i);
io::stdout().flush().ok().expect("Could not flush stdout");
thread::sleep(time::Duration::from_millis(500));
}
}
================================================
FILE: examples/rust/dump-env.rs
================================================
// Standard CGI(ish) environment variables, as defined in
// http://tools.ietf.org/html/rfc3875
use std::env;
const NAMES: &'static [&'static str] = &[
"AUTH_TYPE",
"CONTENT_LENGTH",
"CONTENT_TYPE",
"GATEWAY_INTERFACE",
"PATH_INFO",
"PATH_TRANSLATED",
"QUERY_STRING",
"REMOTE_ADDR",
"REMOTE_HOST",
"REMOTE_IDENT",
"REMOTE_PORT",
"REMOTE_USER",
"REQUEST_METHOD",
"REQUEST_URI",
"SCRIPT_NAME",
"SERVER_NAME",
"SERVER_PORT",
"SERVER_PROTOCOL",
"SERVER_SOFTWARE",
"UNIQUE_ID",
"HTTPS",
];
fn main() {
for key in NAMES {
let value = env::var(key).unwrap_or(String::from("<unset>"));
println!("{}={}", key, value);
}
for (key, value) in env::vars() {
if key.starts_with("HTTP_") {
println!("{}={}", key, value);
}
}
}
================================================
FILE: examples/rust/greeter.rs
================================================
use std::io::{self, Write};
// For each line FOO received on STDIN, respond with "Hello FOO!".
fn main() {
loop {
let mut msg = String::new();
io::stdin()
.read_line(&mut msg)
.expect("Failed to read line");
let msg = msg.trim();
println!("Hello {}!", msg);
io::stdout().flush().ok().expect("Could not flush stdout");
}
}
================================================
FILE: examples/swift/README.md
================================================
## Swift examples
### Count
Run the following line and open "html/count.html" from the websocketd examples directory.
```
$ websocketd --port=8080 count.swift
```
### Greeter
Run the following line and open "http://localhost:8080" in your browser to interact with the greeter server.
```
$ websocketd --port=8080 --devconsole greeter.swift
```
================================================
FILE: examples/swift/count.swift
================================================
#!/usr/bin/env xcrun -sdk macosx swift
import AppKit
for index in 1...10 {
print(index)
// Flush output
fflush(__stdoutp)
NSThread.sleepForTimeInterval(0.5)
}
================================================
FILE: examples/swift/greeter.swift
================================================
#!/usr/bin/env xcrun -sdk macosx swift
import Foundation
while(true){
var stdin = NSFileHandle.fileHandleWithStandardInput().availableData
var line = NSString(data: stdin, encoding: NSUTF8StringEncoding)!
var name = line.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
print("Hello \(name)!")
fflush(__stdoutp)
}
================================================
FILE: examples/windows-jscript/README.txt
================================================
This examples directory shows some examples written in JScript
that can be run using Windows Script Hosting.
Note that each .js file, also requires a .cmd file to launch it.
The WebSocket should connect to ws://..../[example].cmd.
http://en.wikipedia.org/wiki/Windows_Script_Host
You can also test the command files by running from the command line.
================================================
FILE: examples/windows-jscript/count.cmd
================================================
@echo off
cscript /nologo %0\..\count.js
================================================
FILE: examples/windows-jscript/count.js
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Simple example script that counts to 10 at ~2Hz, then stops.
for (var i = 1; i <= 10; i++) {
WScript.echo(i);
WScript.sleep(500);
}
================================================
FILE: examples/windows-jscript/dump-env.cmd
================================================
@echo off
cscript /nologo %0\..\dump-env.js
================================================
FILE: examples/windows-jscript/dump-env.js
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Standard CGI(ish) environment variables, as defined in
// http://tools.ietf.org/html/rfc3875
var names = [
'AUTH_TYPE',
'CONTENT_LENGTH',
'CONTENT_TYPE',
'GATEWAY_INTERFACE',
'PATH_INFO',
'PATH_TRANSLATED',
'QUERY_STRING',
'REMOTE_ADDR',
'REMOTE_HOST',
'REMOTE_IDENT',
'REMOTE_PORT',
'REMOTE_USER',
'REQUEST_METHOD',
'REQUEST_URI',
'SCRIPT_NAME',
'SERVER_NAME',
'SERVER_PORT',
'SERVER_PROTOCOL',
'SERVER_SOFTWARE',
'UNIQUE_ID',
'HTTPS'
];
var shell = WScript.CreateObject("WScript.Shell");
var env = shell.Environment('PROCESS');
for (var i = 0; i < names.length; i++) {
var name = names[i];
var value = env(name) || '<unset>';
WScript.echo(name + '=' + value);
}
for(var en = new Enumerator(env); !en.atEnd(); en.moveNext()) {
var item = en.item();
if (item.indexOf('HTTP_') == 0) {
WScript.Echo(item);
}
}
================================================
FILE: examples/windows-jscript/greeter.cmd
================================================
@echo off
cscript /nologo %0\..\greeter.js
================================================
FILE: examples/windows-jscript/greeter.js
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// For each line FOO received on STDIN, respond with "Hello FOO!".
while (true) {
var input= WScript.stdIn.readLine();
WScript.echo('Hello ' + input+ '!');
}
================================================
FILE: examples/windows-vbscript/README.txt
================================================
This examples directory shows some examples written in VBScript
that can be run using Windows Script Hosting.
Note that each .vbs file, also requires a .cmd file to launch it.
The WebSocket should connect to ws://..../[example].cmd.
http://en.wikipedia.org/wiki/Windows_Script_Host
You can also test the command files by running from the command line.
================================================
FILE: examples/windows-vbscript/count.cmd
================================================
@echo off
cscript /nologo %0\..\count.vbs
================================================
FILE: examples/windows-vbscript/count.vbs
================================================
' Copyright 2013 Joe Walnes and the websocketd team.
' All rights reserved.
' Use of this source code is governed by a BSD-style
' license that can be found in the LICENSE file.
' Simple example script that counts to 10 at ~2Hz, then stops.
for i = 1 to 10
WScript.echo i
WScript.sleep 500
next
================================================
FILE: examples/windows-vbscript/dump-env.cmd
================================================
@echo off
cscript /nologo %0\..\dump-env.vbs
================================================
FILE: examples/windows-vbscript/dump-env.vbs
================================================
' Copyright 2013 Joe Walnes and the websocketd team.
' All rights reserved.
' Use of this source code is governed by a BSD-style
' license that can be found in the LICENSE file.
' Standard CGI(ish) environment variables, as defined in
' http://tools.ietf.org/html/rfc3875
names = Array(_
"AUTH_TYPE", _
"CONTENT_LENGTH", _
"CONTENT_TYPE", _
"GATEWAY_INTERFACE", _
"PATH_INFO", _
"PATH_TRANSLATED", _
"QUERY_STRING", _
"REMOTE_ADDR", _
"REMOTE_HOST", _
"REMOTE_IDENT", _
"REMOTE_PORT", _
"REMOTE_USER", _
"REQUEST_METHOD", _
"REQUEST_URI", _
"SCRIPT_NAME", _
"SERVER_NAME", _
"SERVER_PORT", _
"SERVER_PROTOCOL", _
"SERVER_SOFTWARE", _
"UNIQUE_ID", _
"HTTPS"_
)
set shell = WScript.CreateObject("WScript.Shell")
set env = shell.Environment("PROCESS")
for each name in names
value = env(name)
if value = "" then
value = "<unset>"
end if
WScript.echo name & "=" & value
next
for each item in env
if instr(1, item, "HTTP_", 1) = 1 then
WScript.Echo item
end if
next
================================================
FILE: examples/windows-vbscript/greeter.cmd
================================================
@echo off
cscript /nologo %0\..\greeter.vbs
================================================
FILE: examples/windows-vbscript/greeter.vbs
================================================
' Copyright 2013 Joe Walnes and the websocketd team.
' All rights reserved.
' Use of this source code is governed by a BSD-style
' license that can be found in the LICENSE file.
' For each line FOO received on STDIN, respond with "Hello FOO!".
while true
line = WScript.stdIn.readLine
WScript.echo "Hello " & line & "!"
wend
================================================
FILE: go.mod
================================================
module github.com/joewalnes/websocketd
go 1.15
require github.com/gorilla/websocket v1.4.0
================================================
FILE: go.sum
================================================
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
================================================
FILE: help.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
)
const (
help = `
{{binary}} ({{version}})
{{binary}} is a command line tool that will allow any executable program
that accepts input on stdin and produces output on stdout to be turned into
a WebSocket server.
Usage:
Export a single executable program a WebSocket server:
{{binary}} [options] COMMAND [command args]
Or, export an entire directory of executables as WebSocket endpoints:
{{binary}} [options] --dir=SOMEDIR
Options:
--port=PORT HTTP port to listen on.
--address=ADDRESS Address to bind to (multiple options allowed)
Use square brackets to specify IPv6 address.
Default: "" (all)
--sameorigin={true,false} Restrict (HTTP 403) protocol upgrades if the
Origin header does not match to requested HTTP
Host. Default: false.
--origin=host[:port][,host[:port]...]
Restrict (HTTP 403) protocol upgrades if the
Origin header does not match to one of the host
and port combinations listed. If the port is not
specified, any port number will match.
Default: "" (allow any origin)
--ssl Listen for HTTPS socket instead of HTTP.
--sslcert=FILE All three options must be used or all of
--sslkey=FILE them should be omitted.
--redirport=PORT Open alternative port and redirect HTTP traffic
from it to canonical address (mostly useful
for HTTPS-only configurations to redirect HTTP
traffic)
--passenv VAR[,VAR...] Lists environment variables allowed to be
passed to executed scripts. Does not work for
Windows since all the variables are kept there.
--binary={true,false} Switches communication to binary, process reads
send to browser as blobs and all reads from the
browser are immediately flushed to the process.
Default: false
--reverselookup={true,false} Perform DNS reverse lookups on remote clients.
Default: false
--dir=DIR Allow all scripts in the local directory
to be accessed as WebSockets. If using this,
option, then the standard program and args
options should not be specified.
--staticdir=DIR Serve static files in this directory over HTTP.
--cgidir=DIR Serve CGI scripts in this directory over HTTP.
--maxforks=N Limit number of processes that websocketd is
able to execute with WS and CGI handlers.
When maxforks reached the server will be
rejecting requests that require executing
another process (unlimited when 0 or negative).
Default: 0
--closems=milliseconds Specifies additional time process needs to gracefully
finish before websocketd will send termination signals
to it. Default: 0 (signals sent after 100ms, 250ms,
and 500ms of waiting)
--header="..." Set custom HTTP header to each answer. For
example: --header="Server: someserver/0.0.1"
--header-ws="...." Same as --header, just applies to only those
responses that indicate upgrade of TCP connection
to a WebSockets protocol.
--header-http="...." Same as --header, just applies to only to plain
HTTP responses that do not indicate WebSockets
upgrade
--help Print help and exit.
--version Print version and exit.
--license Print license and exit.
--devconsole Enable interactive development console.
This enables you to access the websocketd
server with a web-browser and use a
user interface to quickly test WebSocket
endpoints. For example, to test an
endpoint at ws://[host]/foo, you can
visit http://[host]/foo in your browser.
This flag cannot be used in conjunction
with --staticdir or --cgidir.
--loglevel=LEVEL Log level to use (default access).
From most to least verbose:
debug, trace, access, info, error, fatal
Full documentation at http://websocketd.com/
Copyright 2013 Joe Walnes and the websocketd team. All rights reserved.
BSD license: Run '{{binary}} --license' for details.
`
short = `
Usage:
Export a single executable program a WebSocket server:
{{binary}} [options] COMMAND [command args]
Or, export an entire directory of executables as WebSocket endpoints:
{{binary}} [options] --dir=SOMEDIR
Or, show extended help message using:
{{binary}} --help
`
)
func get_help_message(content string) string {
msg := strings.Trim(content, " \n")
msg = strings.Replace(msg, "{{binary}}", HelpProcessName(), -1)
return strings.Replace(msg, "{{version}}", Version(), -1)
}
func HelpProcessName() string {
binary := os.Args[0]
if strings.Contains(binary, "/go-build") { // this was run using "go run", let's use something appropriate
binary = "websocketd"
} else {
binary = filepath.Base(binary)
}
return binary
}
func PrintHelp() {
fmt.Fprintf(os.Stderr, "%s\n", get_help_message(help))
}
func ShortHelp() {
// Shown after some error
fmt.Fprintf(os.Stderr, "\n%s\n", get_help_message(short))
}
================================================
FILE: libwebsocketd/config.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"time"
)
type Config struct {
// base initiaization fields
StartupTime time.Time // Server startup time (used for dev console caching).
CommandName string // Command to execute.
CommandArgs []string // Additional args to pass to command.
ServerSoftware string // Value to pass to SERVER_SOFTWARE environment variable (e.g. websocketd/1.2.3).
CloseMs uint // Milliseconds to start sending signals
HandshakeTimeout time.Duration // time to finish handshake (default 1500ms)
// settings
Binary bool // Use binary communication (send data in chunks they are read from process)
ReverseLookup bool // Perform reverse DNS lookups on hostnames (useful, but slower).
Ssl bool // websocketd works with --ssl which means TLS is in use
ScriptDir string // Base directory for websocket scripts.
UsingScriptDir bool // Are we running with a script dir.
StaticDir string // If set, static files will be served from this dir over HTTP.
CgiDir string // If set, CGI scripts will be served from this dir over HTTP.
DevConsole bool // Enable dev console. This disables StaticDir and CgiDir.
AllowOrigins []string // List of allowed origin addresses for websocket upgrade.
SameOrigin bool // If set, requires websocket upgrades to be performed from same origin only.
Headers []string
HeadersWs []string
HeadersHTTP []string
// created environment
Env []string // Additional environment variables to pass to process ("key=value").
ParentEnv []string // Variables kept from os.Environ() before sanitizing it for subprocess.
}
================================================
FILE: libwebsocketd/console.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
// Although this isn't particularly elegant, it's the simplest
// way to embed the console content into the binary.
// Note that the console is served by a single HTML file containing
// all CSS and JS inline.
// We can get by without jQuery or Bootstrap for this one ;).
const (
defaultConsoleContent = `
<!--
websocketd console
Full documentation at http://websocketd.com/
{{license}}
-->
<!DOCTYPE html>
<meta charset="utf8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>websocketd console</title>
<style>
.template {
display: none !important;
}
body, input {
font-family: dejavu sans mono, Menlo, Monaco, Consolas, Lucida Console, tahoma, arial;
font-size: 13px;
}
body {
margin: 0;
}
.header {
background-color: #efefef;
padding: 2px;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 32px;
}
.header button {
font-size: 19px;
width: 30px;
margin: 2px 2px 0 2px;
padding: 0;
float: left;
}
.header .url-holder {
position: absolute;
left: 38px;
top: 4px;
right: 14px;
bottom: 9px;
}
.header .url {
border: 1px solid #999;
background-color: #fff;
width: 100%;
height: 100%;
border-radius: 2px;
padding-left: 4px;
padding-right: 4px;
}
.messages {
overflow-y: scroll;
position: absolute;
left: 0;
right: 0;
top: 36px;
bottom: 0;
border-top: 1px solid #ccc;
}
.message {
border-bottom: 1px solid #bbb;
padding: 2px;
}
.message-type {
font-weight: bold;
position: absolute;
width: 80px;
display: block;
}
.message-data {
margin-left: 90px;
display: block;
word-wrap: break-word;
white-space: pre;
}
.type-input,
.type-send {
background-color: #ffe;
}
.type-onmessage {
background-color: #eef;
}
.type-open,
.type-onopen {
background-color: #efe;
}
.type-close,
.type-onclose {
background-color: #fee;
}
.type-onerror,
.type-exception {
background-color: #333;
color: #f99;
}
.type-send .message-type,
.type-onmessage .message-type {
opacity: 0.2;
}
.type-input .message-type {
color: #090;
}
.send-input {
width: 100%;
border: 0;
padding: 0;
margin: -1px;
background-color: inherit;
}
.send-input:focus {
outline: none;
}
</style>
<header class="header">
<button class="disconnect" title="Disconnect" style="display:none">×</button>
<button class="connect" title="Connect" style="display:none">✔</button>
<div class="url-holder">
<input class="url" type="text" value="{{addr}}" spellcheck="false">
</div>
</header>
<section class="messages">
<div class="message template">
<span class="message-type"></span>
<span class="message-data"></span>
</div>
<div class="message type-input">
<span class="message-type">send »</span>
<span class="message-data"><input type="text" class="send-input" spellcheck="false"></span>
</div>
</section>
<script>
var ws = null;
function ready() {
select('.connect').style.display = 'block';
select('.disconnect').style.display = 'none';
select('.connect').addEventListener('click', function() {
connect(select('.url').value);
});
select('.disconnect').addEventListener('click', function() {
disconnect();
});
select('.url').focus();
select('.url').addEventListener('keydown', function(ev) {
var code = ev.which || ev.keyCode;
// Enter key pressed
if (code == 13) {
updatePageUrl();
connect(select('.url').value);
}
});
select('.url').addEventListener('change', updatePageUrl);
select('.send-input').addEventListener('keydown', function(ev) {
var code = ev.which || ev.keyCode;
// Enter key pressed
if (code == 13) {
var msg = select('.send-input').value;
select('.send-input').value = '';
send(msg);
}
// Up key pressed
if (code == 38) {
moveThroughSendHistory(1);
}
// Down key pressed
if (code == 40) {
moveThroughSendHistory(-1);
}
});
window.addEventListener('popstate', updateWebSocketUrl);
updateWebSocketUrl();
}
function updatePageUrl() {
var match = select('.url').value.match(new RegExp('^(ws)(s)?://([^/]*)(/.*)$'));
if (match) {
var pageUrlSuffix = match[4];
if (history.state != pageUrlSuffix) {
history.pushState(pageUrlSuffix, pageUrlSuffix, pageUrlSuffix);
}
}
}
function updateWebSocketUrl() {
var match = location.href.match(new RegExp('^(http)(s)?://([^/]*)(/.*)$'));
if (match) {
var wsUrl = 'ws' + (match[2] || '') + '://' + match[3] + match[4];
select('.url').value = wsUrl;
}
}
function appendMessage(type, data) {
var template = select('.message.template');
var el = template.parentElement.insertBefore(template.cloneNode(true), select('.message.type-input'));
el.classList.remove('template');
el.classList.add('type-' + type.toLowerCase());
el.querySelector('.message-type').textContent = type;
el.querySelector('.message-data').textContent = data || '';
el.querySelector('.message-data').innerHTML += ' ';
el.scrollIntoView(true);
}
function connect(url) {
function action() {
appendMessage('open', url);
try {
ws = new WebSocket(url);
} catch (ex) {
appendMessage('exception', 'Cannot connect: ' + ex);
return;
}
select('.connect').style.display = 'none';
select('.disconnect').style.display = 'block';
ws.addEventListener('open', function(ev) {
appendMessage('onopen');
});
ws.addEventListener('close', function(ev) {
select('.connect').style.display = 'block';
select('.disconnect').style.display = 'none';
appendMessage('onclose', '[Clean: ' + ev.wasClean + ', Code: ' + ev.code + ', Reason: ' + (ev.reason || 'none') + ']');
ws = null;
select('.url').focus();
});
ws.addEventListener('message', function(ev) {
if (typeof(ev.data) == "object") {
var rd = new FileReader();
rd.onload = function(ev){
appendMessage('onmessage', "BLOB: "+rd.result);
};
rd.readAsBinaryString(ev.data);
} else {
appendMessage('onmessage', ev.data);
}
});
ws.addEventListener('error', function(ev) {
appendMessage('onerror');
});
select('.send-input').focus();
}
if (ws) {
ws.addEventListener('close', function(ev) {
action();
});
disconnect();
} else {
action();
}
}
function disconnect() {
if (ws) {
appendMessage('close');
ws.close();
}
}
function send(msg) {
appendToSendHistory(msg);
appendMessage('send', msg);
if (ws) {
try {
ws.send(msg);
} catch (ex) {
appendMessage('exception', 'Cannot send: ' + ex);
}
} else {
appendMessage('exception', 'Cannot send: Not connected');
}
}
function select(selector) {
return document.querySelector(selector);
}
var maxSendHistorySize = 100;
currentSendHistoryPosition = -1,
sendHistoryRollback = '';
function appendToSendHistory(msg) {
currentSendHistoryPosition = -1;
sendHistoryRollback = '';
var sendHistory = JSON.parse(localStorage['websocketdconsole.sendhistory'] || '[]');
if (sendHistory[0] !== msg) {
sendHistory.unshift(msg);
while (sendHistory.length > maxSendHistorySize) {
sendHistory.pop();
}
localStorage['websocketdconsole.sendhistory'] = JSON.stringify(sendHistory);
}
}
function moveThroughSendHistory(offset) {
if (currentSendHistoryPosition == -1) {
sendHistoryRollback = select('.send-input').value;
}
var sendHistory = JSON.parse(localStorage['websocketdconsole.sendhistory'] || '[]');
currentSendHistoryPosition += offset;
currentSendHistoryPosition = Math.max(-1, Math.min(sendHistory.length - 1, currentSendHistoryPosition));
var el = select('.send-input');
el.value = currentSendHistoryPosition == -1
? sendHistoryRollback
: sendHistory[currentSendHistoryPosition];
setTimeout(function() {
el.setSelectionRange(el.value.length, el.value.length);
}, 0);
}
document.addEventListener("DOMContentLoaded", ready, false);
</script>
`
)
var ConsoleContent = defaultConsoleContent
================================================
FILE: libwebsocketd/endpoint.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
type Endpoint interface {
StartReading()
Terminate()
Output() chan []byte
Send([]byte) bool
}
func PipeEndpoints(e1, e2 Endpoint) {
e1.StartReading()
e2.StartReading()
defer e1.Terminate()
defer e2.Terminate()
for {
select {
case msgOne, ok := <-e1.Output():
if !ok || !e2.Send(msgOne) {
return
}
case msgTwo, ok := <-e2.Output():
if !ok || !e1.Send(msgTwo) {
return
}
}
}
}
================================================
FILE: libwebsocketd/endpoint_test.go
================================================
package libwebsocketd
import (
"strconv"
"testing"
"time"
)
var eol_tests = []string{
"", "\n", "\r\n", "ok\n", "ok\n",
"quite long string for our test\n",
"quite long string for our test\r\n",
}
var eol_answers = []string{
"", "", "", "ok", "ok",
"quite long string for our test", "quite long string for our test",
}
func TestTrimEOL(t *testing.T) {
for n := 0; n < len(eol_tests); n++ {
answ := trimEOL([]byte(eol_tests[n]))
if string(answ) != eol_answers[n] {
t.Errorf("Answer '%s' did not match predicted '%s'", answ, eol_answers[n])
}
}
}
func BenchmarkTrimEOL(b *testing.B) {
for n := 0; n < b.N; n++ {
trimEOL([]byte(eol_tests[n%len(eol_tests)]))
}
}
type TestEndpoint struct {
limit int
prefix string
c chan []byte
result []string
}
func (e *TestEndpoint) StartReading() {
go func() {
for i := 0; i < e.limit; i++ {
e.c <- []byte(e.prefix + strconv.Itoa(i))
}
time.Sleep(time.Millisecond) // should be enough for smaller channel to catch up with long one
close(e.c)
}()
}
func (e *TestEndpoint) Terminate() {
}
func (e *TestEndpoint) Output() chan []byte {
return e.c
}
func (e *TestEndpoint) Send(msg []byte) bool {
e.result = append(e.result, string(msg))
return true
}
func TestEndpointPipe(t *testing.T) {
one := &TestEndpoint{2, "one:", make(chan []byte), make([]string, 0)}
two := &TestEndpoint{4, "two:", make(chan []byte), make([]string, 0)}
PipeEndpoints(one, two)
if len(one.result) != 4 || len(two.result) != 2 {
t.Errorf("Invalid lengths, should be 4 and 2: %v %v", one.result, two.result)
} else if one.result[0] != "two:0" || two.result[0] != "one:0" {
t.Errorf("Invalid first results, should be two:0 and one:0: %#v %#v", one.result[0], two.result[0])
}
}
================================================
FILE: libwebsocketd/env.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"fmt"
"net/http"
"strings"
)
const (
gatewayInterface = "websocketd-CGI/0.1"
)
var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ")
var headerDashToUnderscore = strings.NewReplacer("-", "_")
func createEnv(handler *WebsocketdHandler, req *http.Request, log *LogScope) []string {
headers := req.Header
url := req.URL
serverName, serverPort, err := tellHostPort(req.Host, handler.server.Config.Ssl)
if err != nil {
// This does mean that we cannot detect port from Host: header... Just keep going with "", guessing is bad.
log.Debug("env", "Host port detection error: %s", err)
serverPort = ""
}
standardEnvCount := 20
if handler.server.Config.Ssl {
standardEnvCount += 1
}
parentLen := len(handler.server.Config.ParentEnv)
env := make([]string, 0, len(headers)+standardEnvCount+parentLen+len(handler.server.Config.Env))
// This variable could be rewritten from outside
env = appendEnv(env, "SERVER_SOFTWARE", handler.server.Config.ServerSoftware)
parentStarts := len(env)
env = append(env, handler.server.Config.ParentEnv...)
// IMPORTANT ---> Adding a header? Make sure standardEnvCount (above) is up to date.
// Standard CGI specification headers.
// As defined in http://tools.ietf.org/html/rfc3875
env = appendEnv(env, "REMOTE_ADDR", handler.RemoteInfo.Addr)
env = appendEnv(env, "REMOTE_HOST", handler.RemoteInfo.Host)
env = appendEnv(env, "SERVER_NAME", serverName)
env = appendEnv(env, "SERVER_PORT", serverPort)
env = appendEnv(env, "SERVER_PROTOCOL", req.Proto)
env = appendEnv(env, "GATEWAY_INTERFACE", gatewayInterface)
env = appendEnv(env, "REQUEST_METHOD", req.Method)
env = appendEnv(env, "SCRIPT_NAME", handler.URLInfo.ScriptPath)
env = appendEnv(env, "PATH_INFO", handler.URLInfo.PathInfo)
env = appendEnv(env, "PATH_TRANSLATED", url.Path)
env = appendEnv(env, "QUERY_STRING", url.RawQuery)
// Not supported, but we explicitly clear them so we don't get leaks from parent environment.
env = appendEnv(env, "AUTH_TYPE", "")
env = appendEnv(env, "CONTENT_LENGTH", "")
env = appendEnv(env, "CONTENT_TYPE", "")
env = appendEnv(env, "REMOTE_IDENT", "")
env = appendEnv(env, "REMOTE_USER", "")
// Non standard, but commonly used headers.
env = appendEnv(env, "UNIQUE_ID", handler.Id) // Based on Apache mod_unique_id.
env = appendEnv(env, "REMOTE_PORT", handler.RemoteInfo.Port)
env = appendEnv(env, "REQUEST_URI", url.RequestURI()) // e.g. /foo/blah?a=b
// The following variables are part of the CGI specification, but are optional
// and not set by websocketd:
//
// AUTH_TYPE, REMOTE_USER, REMOTE_IDENT
// -- Authentication left to the underlying programs.
//
// CONTENT_LENGTH, CONTENT_TYPE
// -- makes no sense for WebSocket connections.
//
// SSL_*
// -- SSL variables are not supported, HTTPS=on added for websocketd running with --ssl
if handler.server.Config.Ssl {
env = appendEnv(env, "HTTPS", "on")
}
if log.MinLevel == LogDebug {
for i, v := range env {
if i >= parentStarts && i < parentLen+parentStarts {
log.Debug("env", "Parent envvar: %v", v)
} else {
log.Debug("env", "Std. variable: %v", v)
}
}
}
for k, hdrs := range headers {
header := fmt.Sprintf("HTTP_%s", headerDashToUnderscore.Replace(k))
env = appendEnv(env, header, hdrs...)
log.Debug("env", "Header variable %s", env[len(env)-1])
}
for _, v := range handler.server.Config.Env {
env = append(env, v)
log.Debug("env", "External variable: %s", v)
}
return env
}
// Adapted from net/http/header.go
func appendEnv(env []string, k string, v ...string) []string {
if len(v) == 0 {
return env
}
vCleaned := make([]string, 0, len(v))
for _, val := range v {
vCleaned = append(vCleaned, strings.TrimSpace(headerNewlineToSpace.Replace(val)))
}
return append(env, fmt.Sprintf("%s=%s",
strings.ToUpper(k),
strings.Join(vCleaned, ", ")))
}
================================================
FILE: libwebsocketd/handler.go
================================================
package libwebsocketd
import (
"errors"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
)
var ScriptNotFoundError = errors.New("script not found")
// WebsocketdHandler is a single request information and processing structure, it handles WS requests out of all that daemon can handle (static, cgi, devconsole)
type WebsocketdHandler struct {
server *WebsocketdServer
Id string
*RemoteInfo
*URLInfo // TODO: I cannot find where it's used except in one single place as URLInfo.FilePath
Env []string
command string
}
// NewWebsocketdHandler constructs the struct and parses all required things in it...
func NewWebsocketdHandler(s *WebsocketdServer, req *http.Request, log *LogScope) (wsh *WebsocketdHandler, err error) {
wsh = &WebsocketdHandler{server: s, Id: generateId()}
log.Associate("id", wsh.Id)
wsh.RemoteInfo, err = GetRemoteInfo(req.RemoteAddr, s.Config.ReverseLookup)
if err != nil {
log.Error("session", "Could not understand remote address '%s': %s", req.RemoteAddr, err)
return nil, err
}
log.Associate("remote", wsh.RemoteInfo.Host)
wsh.URLInfo, err = GetURLInfo(req.URL.Path, s.Config)
if err != nil {
log.Access("session", "NOT FOUND: %s", err)
return nil, err
}
wsh.command = s.Config.CommandName
if s.Config.UsingScriptDir {
wsh.command = wsh.URLInfo.FilePath
}
log.Associate("command", wsh.command)
wsh.Env = createEnv(wsh, req, log)
return wsh, nil
}
func (wsh *WebsocketdHandler) accept(ws *websocket.Conn, log *LogScope) {
defer ws.Close()
log.Access("session", "CONNECT")
defer log.Access("session", "DISCONNECT")
launched, err := launchCmd(wsh.command, wsh.server.Config.CommandArgs, wsh.Env)
if err != nil {
log.Error("process", "Could not launch process %s %s (%s)", wsh.command, strings.Join(wsh.server.Config.CommandArgs, " "), err)
return
}
log.Associate("pid", strconv.Itoa(launched.cmd.Process.Pid))
binary := wsh.server.Config.Binary
process := NewProcessEndpoint(launched, binary, log)
if cms := wsh.server.Config.CloseMs; cms != 0 {
process.closetime += time.Duration(cms) * time.Millisecond
}
wsEndpoint := NewWebSocketEndpoint(ws, binary, log)
PipeEndpoints(process, wsEndpoint)
}
// RemoteInfo holds information about remote http client
type RemoteInfo struct {
Addr, Host, Port string
}
// GetRemoteInfo creates RemoteInfo structure and fills its fields appropriately
func GetRemoteInfo(remote string, doLookup bool) (*RemoteInfo, error) {
addr, port, err := net.SplitHostPort(remote)
if err != nil {
return nil, err
}
var host string
if doLookup {
hosts, err := net.LookupAddr(addr)
if err != nil || len(hosts) == 0 {
host = addr
} else {
host = hosts[0]
}
} else {
host = addr
}
return &RemoteInfo{Addr: addr, Host: host, Port: port}, nil
}
// URLInfo - structure carrying information about current request and it's mapping to filesystem
type URLInfo struct {
ScriptPath string
PathInfo string
FilePath string
}
// GetURLInfo is a function that parses path and provides URL info according to libwebsocketd.Config fields
func GetURLInfo(path string, config *Config) (*URLInfo, error) {
if !config.UsingScriptDir {
return &URLInfo{"/", path, ""}, nil
}
parts := strings.Split(path[1:], "/")
urlInfo := &URLInfo{}
for i, part := range parts {
urlInfo.ScriptPath = strings.Join([]string{urlInfo.ScriptPath, part}, "/")
urlInfo.FilePath = filepath.Join(config.ScriptDir, urlInfo.ScriptPath)
isLastPart := i == len(parts)-1
statInfo, err := os.Stat(urlInfo.FilePath)
// not a valid path
if err != nil {
return nil, ScriptNotFoundError
}
// at the end of url but is a dir
if isLastPart && statInfo.IsDir() {
return nil, ScriptNotFoundError
}
// we've hit a dir, carry on looking
if statInfo.IsDir() {
continue
}
// no extra args
if isLastPart {
return urlInfo, nil
}
// build path info from extra parts of url
urlInfo.PathInfo = "/" + strings.Join(parts[i+1:], "/")
return urlInfo, nil
}
panic(fmt.Sprintf("GetURLInfo cannot parse path %#v", path))
}
func generateId() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
================================================
FILE: libwebsocketd/handler_test.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestParsePathWithScriptDir(t *testing.T) {
baseDir, _ := ioutil.TempDir("", "websockets")
scriptDir := filepath.Join(baseDir, "foo", "bar")
scriptPath := filepath.Join(scriptDir, "baz.sh")
defer os.RemoveAll(baseDir)
if err := os.MkdirAll(scriptDir, os.ModePerm); err != nil {
t.Error("could not create ", scriptDir)
}
if _, err := os.Create(scriptPath); err != nil {
t.Error("could not create ", scriptPath)
}
config := new(Config)
config.UsingScriptDir = true
config.ScriptDir = baseDir
var res *URLInfo
var err error
// simple url
res, err = GetURLInfo("/foo/bar/baz.sh", config)
if err != nil {
t.Error(err)
}
if res.ScriptPath != "/foo/bar/baz.sh" {
t.Error("scriptPath")
}
if res.PathInfo != "" {
t.Error("GetURLInfo")
}
if res.FilePath != scriptPath {
t.Error("filePath")
}
// url with extra path info
res, err = GetURLInfo("/foo/bar/baz.sh/some/extra/stuff", config)
if err != nil {
t.Error(err)
}
if res.ScriptPath != "/foo/bar/baz.sh" {
t.Error("scriptPath")
}
if res.PathInfo != "/some/extra/stuff" {
t.Error("GetURLInfo")
}
if res.FilePath != scriptPath {
t.Error("filePath")
}
// non-existing file
_, err = GetURLInfo("/foo/bar/bang.sh", config)
if err == nil {
t.Error("non-existing file should fail")
}
if err != ScriptNotFoundError {
t.Error("should fail with script not found")
}
// non-existing dir
_, err = GetURLInfo("/hoohar/bang.sh", config)
if err == nil {
t.Error("non-existing dir should fail")
}
if err != ScriptNotFoundError {
t.Error("should fail with script not found")
}
}
func TestParsePathExplicitScript(t *testing.T) {
config := new(Config)
config.UsingScriptDir = false
res, err := GetURLInfo("/some/path", config)
if err != nil {
t.Error(err)
}
if res.ScriptPath != "/" {
t.Error("scriptPath")
}
if res.PathInfo != "/some/path" {
t.Error("GetURLInfo")
}
if res.FilePath != "" {
t.Error("filePath")
}
}
================================================
FILE: libwebsocketd/http.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"errors"
"fmt"
"net"
"net/http"
"net/http/cgi"
"net/textproto"
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"github.com/gorilla/websocket"
)
var ForkNotAllowedError = errors.New("too many forks active")
// WebsocketdServer presents http.Handler interface for requests libwebsocketd is handling.
type WebsocketdServer struct {
Config *Config
Log *LogScope
forks chan byte
}
// NewWebsocketdServer creates WebsocketdServer struct with pre-determined config, logscope and maxforks limit
func NewWebsocketdServer(config *Config, log *LogScope, maxforks int) *WebsocketdServer {
mux := &WebsocketdServer{
Config: config,
Log: log,
}
if maxforks > 0 {
mux.forks = make(chan byte, maxforks)
}
return mux
}
func splitMimeHeader(s string) (string, string) {
p := strings.IndexByte(s, ':')
if p < 0 {
return s, ""
}
key := textproto.CanonicalMIMEHeaderKey(s[:p])
for p = p + 1; p < len(s); p++ {
if s[p] != ' ' {
break
}
}
return key, s[p:]
}
func pushHeaders(h http.Header, hdrs []string) {
for _, hstr := range hdrs {
h.Add(splitMimeHeader(hstr))
}
}
// ServeHTTP muxes between WebSocket handler, CGI handler, DevConsole, Static HTML or 404.
func (h *WebsocketdServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log := h.Log.NewLevel(h.Log.LogFunc)
log.Associate("url", h.TellURL("http", req.Host, req.RequestURI))
if h.Config.CommandName != "" || h.Config.UsingScriptDir {
hdrs := req.Header
upgradeRe := regexp.MustCompile(`(?i)(^|[,\s])Upgrade($|[,\s])`)
// WebSocket, limited to size of h.forks
if strings.ToLower(hdrs.Get("Upgrade")) == "websocket" && upgradeRe.MatchString(hdrs.Get("Connection")) {
if h.noteForkCreated() == nil {
defer h.noteForkCompled()
// start figuring out if we even need to upgrade
handler, err := NewWebsocketdHandler(h, req, log)
if err != nil {
if err == ScriptNotFoundError {
log.Access("session", "NOT FOUND: %s", err)
http.Error(w, "404 Not Found", 404)
} else {
log.Access("session", "INTERNAL ERROR: %s", err)
http.Error(w, "500 Internal Server Error", 500)
}
return
}
var headers http.Header
if len(h.Config.Headers)+len(h.Config.HeadersWs) > 0 {
headers = http.Header(make(map[string][]string))
pushHeaders(headers, h.Config.Headers)
pushHeaders(headers, h.Config.HeadersWs)
}
upgrader := &websocket.Upgrader{
HandshakeTimeout: h.Config.HandshakeTimeout,
CheckOrigin: func(r *http.Request) bool {
// backporting previous checkorigin for use in gorilla/websocket for now
err := checkOrigin(req, h.Config, log)
return err == nil
},
}
conn, err := upgrader.Upgrade(w, req, headers)
if err != nil {
log.Access("session", "Unable to Upgrade: %s", err)
http.Error(w, "500 Internal Error", 500)
return
}
// old func was used in x/net/websocket style, we reuse it here for gorilla/websocket
handler.accept(conn, log)
return
} else {
log.Error("http", "Max of possible forks already active, upgrade rejected")
http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests)
}
return
}
}
pushHeaders(w.Header(), h.Config.HeadersHTTP)
// Dev console (if enabled)
if h.Config.DevConsole {
log.Access("http", "DEVCONSOLE")
content := ConsoleContent
content = strings.Replace(content, "{{license}}", License, -1)
content = strings.Replace(content, "{{addr}}", h.TellURL("ws", req.Host, req.RequestURI), -1)
http.ServeContent(w, req, ".html", h.Config.StartupTime, strings.NewReader(content))
return
}
// CGI scripts, limited to size of h.forks
if h.Config.CgiDir != "" {
filePath := path.Join(h.Config.CgiDir, fmt.Sprintf(".%s", filepath.FromSlash(req.URL.Path)))
if fi, err := os.Stat(filePath); err == nil && !fi.IsDir() {
log.Associate("cgiscript", filePath)
if h.noteForkCreated() == nil {
defer h.noteForkCompled()
// Make variables to supplement cgi... Environ it uses will show empty list.
envlen := len(h.Config.ParentEnv)
cgienv := make([]string, envlen+1)
if envlen > 0 {
copy(cgienv, h.Config.ParentEnv)
}
cgienv[envlen] = "SERVER_SOFTWARE=" + h.Config.ServerSoftware
cgiHandler := &cgi.Handler{
Path: filePath,
Env: []string{
"SERVER_SOFTWARE=" + h.Config.ServerSoftware,
},
}
log.Access("http", "CGI")
cgiHandler.ServeHTTP(w, req)
} else {
log.Error("http", "Fork not allowed since maxforks amount has been reached. CGI was not run.")
http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests)
}
return
}
}
// Static files
if h.Config.StaticDir != "" {
handler := http.FileServer(http.Dir(h.Config.StaticDir))
log.Access("http", "STATIC")
handler.ServeHTTP(w, req)
return
}
// 404
log.Access("http", "NOT FOUND")
http.NotFound(w, req)
}
var canonicalHostname string
// TellURL is a helper function that changes http to https or ws to wss in case if SSL is used
func (h *WebsocketdServer) TellURL(scheme, host, path string) string {
if len(host) > 0 && host[0] == ':' {
if canonicalHostname == "" {
var err error
canonicalHostname, err = os.Hostname()
if err != nil {
canonicalHostname = "UNKNOWN"
}
}
host = canonicalHostname + host
}
if h.Config.Ssl {
return scheme + "s://" + host + path
}
return scheme + "://" + host + path
}
func (h *WebsocketdServer) noteForkCreated() error {
// note that forks can be nil since the construct could've been created by
// someone who is not using NewWebsocketdServer
if h.forks != nil {
select {
case h.forks <- 1:
return nil
default:
return ForkNotAllowedError
}
} else {
return nil
}
}
func (h *WebsocketdServer) noteForkCompled() {
if h.forks != nil { // see comment in noteForkCreated
select {
case <-h.forks:
return
default:
// This could only happen if the completion handler called more times than creation handler above
// Code should be audited to not allow this to happen, it's desired to have test that would
// make sure this is impossible but it is not exist yet.
panic("Cannot deplet number of allowed forks, something is not right in code!")
}
}
}
func checkOrigin(req *http.Request, config *Config, log *LogScope) (err error) {
// CONVERT GORILLA:
// this is origin checking function, it's called from wshandshake which is from ServeHTTP main handler
// should be trivial to reuse in gorilla's upgrader.CheckOrigin function.
// Only difference is to parse request and fetching passed Origin header out of it instead of using
// pre-parsed wsconf.Origin
// check for origin to be correct in future
// handshaker triggers answering with 403 if error was returned
// We keep behavior of original handshaker that populates this field
origin := req.Header.Get("Origin")
if origin == "" || (origin == "null" && config.AllowOrigins == nil) {
// we don't want to trust string "null" if there is any
// enforcements are active
origin = "file:"
}
originParsed, err := url.ParseRequestURI(origin)
if err != nil {
log.Access("session", "Origin parsing error: %s", err)
return err
}
log.Associate("origin", originParsed.String())
// If some origin restrictions are present:
if config.SameOrigin || config.AllowOrigins != nil {
originServer, originPort, err := tellHostPort(originParsed.Host, originParsed.Scheme == "https")
if err != nil {
log.Access("session", "Origin hostname parsing error: %s", err)
return err
}
if config.SameOrigin {
localServer, localPort, err := tellHostPort(req.Host, req.TLS != nil)
if err != nil {
log.Access("session", "Request hostname parsing error: %s", err)
return err
}
if originServer != localServer || originPort != localPort {
log.Access("session", "Same origin policy mismatch")
return fmt.Errorf("same origin policy violated")
}
}
if config.AllowOrigins != nil {
matchFound := false
for _, allowed := range config.AllowOrigins {
if pos := strings.Index(allowed, "://"); pos > 0 {
// allowed schema has to match
allowedURL, err := url.Parse(allowed)
if err != nil {
continue // pass bad URLs in origin list
}
if allowedURL.Scheme != originParsed.Scheme {
continue // mismatch
}
allowed = allowed[pos+3:]
}
allowServer, allowPort, err := tellHostPort(allowed, false)
if err != nil {
continue // unparseable
}
if allowPort == "80" && allowed[len(allowed)-3:] != ":80" {
// any port is allowed, host names need to match
matchFound = allowServer == originServer
} else {
// exact match of host names and ports
matchFound = allowServer == originServer && allowPort == originPort
}
if matchFound {
break
}
}
if !matchFound {
log.Access("session", "Origin is not listed in allowed list")
return fmt.Errorf("origin list matches were not found")
}
}
}
return nil
}
func tellHostPort(host string, ssl bool) (server, port string, err error) {
server, port, err = net.SplitHostPort(host)
if err != nil {
if addrerr, ok := err.(*net.AddrError); ok && strings.Contains(addrerr.Err, "missing port") {
server = host
if ssl {
port = "443"
} else {
port = "80"
}
err = nil
}
}
return server, port, err
}
================================================
FILE: libwebsocketd/http_test.go
================================================
package libwebsocketd
import (
"bufio"
"crypto/tls"
"fmt"
"net/http"
"strings"
"testing"
)
var tellHostPortTests = []struct {
src string
ssl bool
server, port string
}{
{"localhost", false, "localhost", "80"},
{"localhost:8080", false, "localhost", "8080"},
{"localhost", true, "localhost", "443"},
{"localhost:8080", true, "localhost", "8080"},
}
func TestTellHostPort(t *testing.T) {
for _, testcase := range tellHostPortTests {
s, p, e := tellHostPort(testcase.src, testcase.ssl)
if testcase.server == "" {
if e == nil {
t.Errorf("test case for %#v failed, error was not returned", testcase.src)
}
} else if e != nil {
t.Errorf("test case for %#v failed, error should not happen", testcase.src)
}
if testcase.server != s || testcase.port != p {
t.Errorf("test case for %#v failed, server or port mismatch to expected values (%s:%s)", testcase.src, s, p)
}
}
}
var NoOriginsAllowed = []string{}
var NoOriginList []string = nil
const (
ReqHTTPS = iota
ReqHTTP
OriginMustBeSame
OriginCouldDiffer
ReturnsPass
ReturnsError
)
var CheckOriginTests = []struct {
host string
reqtls int
origin string
same int
allowed []string
getsErr int
name string
}{
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed"},
{"server.example.com", ReqHTTP, "http://example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin mismatch"},
{"server.example.com", ReqHTTP, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsPass, "same origin match"},
{"server.example.com", ReqHTTP, "https://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 1"},
{"server.example.com", ReqHTTPS, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 2"},
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginsAllowed, ReturnsError, "no origins allowed"},
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"server.example.com"}, ReturnsError, "no origin allowed matches (junk prefix)"},
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com.t"}, ReturnsError, "no origin allowed matches (junk suffix)"},
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed clean match"},
{"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed any port match"},
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:80"}, ReturnsPass, "origin allowed port match"},
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"},
{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"},
{"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com:81"}, ReturnsPass, "origin allowed port 81 match"},
{"server.example.com", ReqHTTP, "null", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even null"},
{"server.example.com", ReqHTTP, "", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even empty"},
}
// CONVERT GORILLA
// as method for origin checking changes to handle things without websocket.Config the test
// should be altered too
func TestCheckOrigin(t *testing.T) {
for _, testcase := range CheckOriginTests {
br := bufio.NewReader(strings.NewReader(fmt.Sprintf(`GET /chat HTTP/1.1
Host: %s
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: %s
Sec-WebSocket-Version: 13
`, testcase.host, testcase.origin)))
req, err := http.ReadRequest(br)
if err != nil {
t.Fatal("request", err)
}
log := new(LogScope)
log.LogFunc = func(*LogScope, LogLevel, string, string, string, ...interface{}) {}
config := new(Config)
if testcase.reqtls == ReqHTTPS { // Fake TLS
config.Ssl = true
req.TLS = &tls.ConnectionState{}
}
if testcase.same == OriginMustBeSame {
config.SameOrigin = true
}
if testcase.allowed != nil {
config.AllowOrigins = testcase.allowed
}
err = checkOrigin(req, config, log)
if testcase.getsErr == ReturnsError && err == nil {
t.Errorf("Test case %#v did not get an error", testcase.name)
} else if testcase.getsErr == ReturnsPass && err != nil {
t.Errorf("Test case %#v got error while expected to pass", testcase.name)
}
}
}
var mimetest = [][3]string{
{"Content-Type: text/plain", "Content-Type", "text/plain"},
{"Content-Type: ", "Content-Type", ""},
}
func TestSplitMimeHeader(t *testing.T) {
for _, tst := range mimetest {
s, v := splitMimeHeader(tst[0])
if tst[1] != s || tst[2] != v {
t.Errorf("%v and %v are not same as expexted %v and %v", s, v, tst[1], tst[2])
}
}
}
================================================
FILE: libwebsocketd/launcher.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"io"
"os/exec"
)
type LaunchedProcess struct {
cmd *exec.Cmd
stdin io.WriteCloser
stdout io.ReadCloser
stderr io.ReadCloser
}
func launchCmd(commandName string, commandArgs []string, env []string) (*LaunchedProcess, error) {
cmd := exec.Command(commandName, commandArgs...)
cmd.Env = env
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
return &LaunchedProcess{cmd, stdin, stdout, stderr}, err
}
================================================
FILE: libwebsocketd/license.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
const (
license = `
Copyright (c) 2013, Joe Walnes and the websocketd authors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
`
License = license
)
================================================
FILE: libwebsocketd/logscope.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"sync"
"time"
)
type LogLevel int
const (
LogDebug = iota
LogTrace
LogAccess
LogInfo
LogError
LogFatal
LogNone = 126
LogUnknown = 127
)
type LogFunc func(logScope *LogScope, level LogLevel, levelName string, category string, msg string, args ...interface{})
type LogScope struct {
Parent *LogScope // Parent scope
MinLevel LogLevel // Minimum log level to write out.
Mutex *sync.Mutex // Should be shared across all LogScopes that write to the same destination.
Associated []AssocPair // Additional data associated with scope
LogFunc LogFunc
}
type AssocPair struct {
Key string
Value string
}
func (l *LogScope) Associate(key string, value string) {
l.Associated = append(l.Associated, AssocPair{key, value})
}
func (l *LogScope) Debug(category string, msg string, args ...interface{}) {
l.LogFunc(l, LogDebug, "DEBUG", category, msg, args...)
}
func (l *LogScope) Trace(category string, msg string, args ...interface{}) {
l.LogFunc(l, LogTrace, "TRACE", category, msg, args...)
}
func (l *LogScope) Access(category string, msg string, args ...interface{}) {
l.LogFunc(l, LogAccess, "ACCESS", category, msg, args...)
}
func (l *LogScope) Info(category string, msg string, args ...interface{}) {
l.LogFunc(l, LogInfo, "INFO", category, msg, args...)
}
func (l *LogScope) Error(category string, msg string, args ...interface{}) {
l.LogFunc(l, LogError, "ERROR", category, msg, args...)
}
func (l *LogScope) Fatal(category string, msg string, args ...interface{}) {
l.LogFunc(l, LogFatal, "FATAL", category, msg, args...)
}
func (parent *LogScope) NewLevel(logFunc LogFunc) *LogScope {
return &LogScope{
Parent: parent,
MinLevel: parent.MinLevel,
Mutex: parent.Mutex,
Associated: make([]AssocPair, 0),
LogFunc: logFunc}
}
func RootLogScope(minLevel LogLevel, logFunc LogFunc) *LogScope {
return &LogScope{
Parent: nil,
MinLevel: minLevel,
Mutex: &sync.Mutex{},
Associated: make([]AssocPair, 0),
LogFunc: logFunc}
}
func Timestamp() string {
return time.Now().Format(time.RFC1123Z)
}
func LevelFromString(s string) LogLevel {
switch s {
case "debug":
return LogDebug
case "trace":
return LogTrace
case "access":
return LogAccess
case "info":
return LogInfo
case "error":
return LogError
case "fatal":
return LogFatal
case "none":
return LogNone
default:
return LogUnknown
}
}
================================================
FILE: libwebsocketd/process_endpoint.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"bufio"
"io"
"syscall"
"time"
)
type ProcessEndpoint struct {
process *LaunchedProcess
closetime time.Duration
output chan []byte
log *LogScope
bin bool
}
func NewProcessEndpoint(process *LaunchedProcess, bin bool, log *LogScope) *ProcessEndpoint {
return &ProcessEndpoint{
process: process,
output: make(chan []byte),
log: log,
bin: bin,
}
}
func (pe *ProcessEndpoint) Terminate() {
terminated := make(chan struct{})
go func() { pe.process.cmd.Wait(); terminated <- struct{}{} }()
// for some processes this is enough to finish them...
pe.process.stdin.Close()
// a bit verbose to create good debugging trail
select {
case <-terminated:
pe.log.Debug("process", "Process %v terminated after stdin was closed", pe.process.cmd.Process.Pid)
return // means process finished
case <-time.After(100*time.Millisecond + pe.closetime):
}
err := pe.process.cmd.Process.Signal(syscall.SIGINT)
if err != nil {
// process is done without this, great!
pe.log.Error("process", "SIGINT unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err)
}
select {
case <-terminated:
pe.log.Debug("process", "Process %v terminated after SIGINT", pe.process.cmd.Process.Pid)
return // means process finished
case <-time.After(250*time.Millisecond + pe.closetime):
}
err = pe.process.cmd.Process.Signal(syscall.SIGTERM)
if err != nil {
// process is done without this, great!
pe.log.Error("process", "SIGTERM unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err)
}
select {
case <-terminated:
pe.log.Debug("process", "Process %v terminated after SIGTERM", pe.process.cmd.Process.Pid)
return // means process finished
case <-time.After(500*time.Millisecond + pe.closetime):
}
err = pe.process.cmd.Process.Kill()
if err != nil {
pe.log.Error("process", "SIGKILL unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err)
return
}
select {
case <-terminated:
pe.log.Debug("process", "Process %v terminated after SIGKILL", pe.process.cmd.Process.Pid)
return // means process finished
case <-time.After(1000 * time.Millisecond):
}
pe.log.Error("process", "SIGKILL did not terminate %v!", pe.process.cmd.Process.Pid)
}
func (pe *ProcessEndpoint) Output() chan []byte {
return pe.output
}
func (pe *ProcessEndpoint) Send(msg []byte) bool {
pe.process.stdin.Write(msg)
return true
}
func (pe *ProcessEndpoint) StartReading() {
go pe.log_stderr()
if pe.bin {
go pe.process_binout()
} else {
go pe.process_txtout()
}
}
func (pe *ProcessEndpoint) process_txtout() {
bufin := bufio.NewReader(pe.process.stdout)
for {
buf, err := bufin.ReadBytes('\n')
if err != nil {
if err != io.EOF {
pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err)
} else {
pe.log.Debug("process", "Process STDOUT closed")
}
break
}
pe.output <- trimEOL(buf)
}
close(pe.output)
}
func (pe *ProcessEndpoint) process_binout() {
buf := make([]byte, 10*1024*1024)
for {
n, err := pe.process.stdout.Read(buf)
if err != nil {
if err != io.EOF {
pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err)
} else {
pe.log.Debug("process", "Process STDOUT closed")
}
break
}
pe.output <- append(make([]byte, 0, n), buf[:n]...) // cloned buffer
}
close(pe.output)
}
func (pe *ProcessEndpoint) log_stderr() {
bufstderr := bufio.NewReader(pe.process.stderr)
for {
buf, err := bufstderr.ReadSlice('\n')
if err != nil {
if err != io.EOF {
pe.log.Error("process", "Unexpected error while reading STDERR from process: %s", err)
} else {
pe.log.Debug("process", "Process STDERR closed")
}
break
}
pe.log.Error("stderr", "%s", string(trimEOL(buf)))
}
}
// trimEOL cuts unixy style \n and windowsy style \r\n suffix from the string
func trimEOL(b []byte) []byte {
lns := len(b)
if lns > 0 && b[lns-1] == '\n' {
lns--
if lns > 0 && b[lns-1] == '\r' {
lns--
}
}
return b[:lns]
}
================================================
FILE: libwebsocketd/websocket_endpoint.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package libwebsocketd
import (
"io"
"io/ioutil"
"github.com/gorilla/websocket"
)
// CONVERT GORILLA
// This file should be altered to use gorilla's websocket connection type and proper
// message dispatching methods
type WebSocketEndpoint struct {
ws *websocket.Conn
output chan []byte
log *LogScope
mtype int
}
func NewWebSocketEndpoint(ws *websocket.Conn, bin bool, log *LogScope) *WebSocketEndpoint {
endpoint := &WebSocketEndpoint{
ws: ws,
output: make(chan []byte),
log: log,
mtype: websocket.TextMessage,
}
if bin {
endpoint.mtype = websocket.BinaryMessage
}
return endpoint
}
func (we *WebSocketEndpoint) Terminate() {
we.log.Trace("websocket", "Terminated websocket connection")
}
func (we *WebSocketEndpoint) Output() chan []byte {
return we.output
}
func (we *WebSocketEndpoint) Send(msg []byte) bool {
w, err := we.ws.NextWriter(we.mtype)
if err == nil {
_, err = w.Write(msg)
}
w.Close() // could need error handling
if err != nil {
we.log.Trace("websocket", "Cannot send: %s", err)
return false
}
return true
}
func (we *WebSocketEndpoint) StartReading() {
go we.read_frames()
}
func (we *WebSocketEndpoint) read_frames() {
for {
mtype, rd, err := we.ws.NextReader()
if err != nil {
we.log.Debug("websocket", "Cannot receive: %s", err)
break
}
if mtype != we.mtype {
we.log.Debug("websocket", "Received message of type that we did not expect... Ignoring...")
}
p, err := ioutil.ReadAll(rd)
if err != nil && err != io.EOF {
we.log.Debug("websocket", "Cannot read received message: %s", err)
break
}
switch mtype {
case websocket.TextMessage:
we.output <- append(p, '\n')
case websocket.BinaryMessage:
we.output <- p
default:
we.log.Debug("websocket", "Received message of unknown type: %d", mtype)
}
}
close(we.output)
}
================================================
FILE: main.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"github.com/joewalnes/websocketd/libwebsocketd"
)
func logfunc(l *libwebsocketd.LogScope, level libwebsocketd.LogLevel, levelName string, category string, msg string, args ...interface{}) {
if level < l.MinLevel {
return
}
fullMsg := fmt.Sprintf(msg, args...)
assocDump := ""
for index, pair := range l.Associated {
if index > 0 {
assocDump += " "
}
assocDump += fmt.Sprintf("%s:'%s'", pair.Key, pair.Value)
}
l.Mutex.Lock()
fmt.Printf("%s | %-6s | %-10s | %s | %s\n", libwebsocketd.Timestamp(), levelName, category, assocDump, fullMsg)
l.Mutex.Unlock()
}
func main() {
config := parseCommandLine()
log := libwebsocketd.RootLogScope(config.LogLevel, logfunc)
if config.DevConsole {
if config.StaticDir != "" {
log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --staticdir. Pick one.")
os.Exit(4)
}
if config.CgiDir != "" {
log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --cgidir. Pick one.")
os.Exit(4)
}
}
if runtime.GOOS != "windows" { // windows relies on env variables to find its libs... e.g. socket stuff
os.Clearenv() // it's ok to wipe it clean, we already read env variables from passenv into config
}
handler := libwebsocketd.NewWebsocketdServer(config.Config, log, config.MaxForks)
http.Handle("/", handler)
if config.UsingScriptDir {
log.Info("server", "Serving from directory : %s", config.ScriptDir)
} else if config.CommandName != "" {
log.Info("server", "Serving using application : %s %s", config.CommandName, strings.Join(config.CommandArgs, " "))
}
if config.StaticDir != "" {
log.Info("server", "Serving static content from : %s", config.StaticDir)
}
if config.CgiDir != "" {
log.Info("server", "Serving CGI scripts from : %s", config.CgiDir)
}
rejects := make(chan error, 1)
for _, addrSingle := range config.Addr {
log.Info("server", "Starting WebSocket server : %s", handler.TellURL("ws", addrSingle, "/"))
if config.DevConsole {
log.Info("server", "Developer console enabled : %s", handler.TellURL("http", addrSingle, "/"))
} else if config.StaticDir != "" || config.CgiDir != "" {
log.Info("server", "Serving CGI or static files : %s", handler.TellURL("http", addrSingle, "/"))
}
// ListenAndServe is blocking function. Let's run it in
// go routine, reporting result to control channel.
// Since it's blocking it'll never return non-error.
go func(addr string) {
if config.Ssl {
rejects <- http.ListenAndServeTLS(addr, config.CertFile, config.KeyFile, nil)
} else {
rejects <- http.ListenAndServe(addr, nil)
}
}(addrSingle)
if config.RedirPort != 0 {
go func(addr string) {
pos := strings.IndexByte(addr, ':')
rediraddr := addr[:pos] + ":" + strconv.Itoa(config.RedirPort) // it would be silly to optimize this one
redir := &http.Server{Addr: rediraddr, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// redirect to same hostname as in request but different port and probably schema
uri := "https://"
if !config.Ssl {
uri = "http://"
}
if cpos := strings.IndexByte(r.Host, ':'); cpos > 0 {
uri += r.Host[:strings.IndexByte(r.Host, ':')] + addr[pos:] + "/"
} else {
uri += r.Host + addr[pos:] + "/"
}
http.Redirect(w, r, uri, http.StatusMovedPermanently)
})}
log.Info("server", "Starting redirect server : http://%s/", rediraddr)
rejects <- redir.ListenAndServe()
}(addrSingle)
}
}
err := <-rejects
if err != nil {
log.Fatal("server", "Can't start server: %s", err)
os.Exit(3)
}
}
================================================
FILE: release/.gitignore
================================================
bin
go-local
out
go-path
websocketd.exe
================================================
FILE: release/Makefile
================================================
# Copyright 2013-2019 Joe Walnes and the websocketd team.
# All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Uses Semantic Versioning scheme - http://semver.org/
VERSION_MAJOR=0
VERSION_MINOR=4
# Last part of version number (patch) is incremented automatically from Git tags
LAST_PATCH_VERSION:=$(shell git ls-remote git@github.com:joewalnes/websocketd.git \
| grep -e 'refs/tags/v[0-9\.]*$$' \
| sed -e 's|^.*refs/tags/v||' \
| grep -e "^$(VERSION_MAJOR)\.$(VERSION_MINOR)\.[0-9][0-9]*$$" \
| sed -e 's/^.*\.//' \
| sort -n \
| tail -n 1)
VERSION_PATCH:=$(or $(LAST_PATCH_VERSION),0)
RELEASE_VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH)
GO_VERSION=1.15.7
PLATFORMS=linux_amd64 linux_386 linux_arm linux_arm64 darwin_amd64 freebsd_amd64 freebsd_386 windows_386 windows_amd64 openbsd_386 openbsd_amd64 solaris_amd64
# Would NOT WORK on: ARM, WINDOWS
UNAME_SYS=$(shell uname -s | tr A-Z a-z)
UNAME_ARCH=$(shell uname -m)
ifeq ($(UNAME_ARCH),x86_64)
UNAME_ARCH=amd64
else
UNAME_ARCH=386
endif
ifeq ($(UNAME_SYS),gnu)
UNAME_SYS=linux
else ifeq ($(UNAME_SYS),sunos)
UNAME_SYS=solaris
endif
GO_DOWNLOAD_URL=https://dl.google.com/go/go$(GO_VERSION).$(UNAME_SYS)-$(UNAME_ARCH).tar.gz
GO_DIR=../go-$(GO_VERSION)
# Prevent any global environment polluting the builds
FLAGS_linux_amd64 = GOOS=linux GOARCH=amd64
FLAGS_linux_386 = GOOS=linux GOARCH=386
FLAGS_linux_arm = GOOS=linux GOARCH=arm GOARM=5 # ARM5 support for Raspberry Pi
FLAGS_linux_arm64 = GOOS=linux GOARCH=arm64 # no need for GOARM= (which is technically 8)
FLAGS_darwin_amd64 = GOOS=darwin GOARCH=amd64 CGO_ENABLED=0
FLAGS_darwin_386 = GOOS=darwin GOARCH=386 CGO_ENABLED=0
FLAGS_freebsd_amd64 = GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0
FLAGS_freebsd_386 = GOOS=freebsd GOARCH=386 CGO_ENABLED=0
FLAGS_windows_386 = GOOS=windows GOARCH=386 CGO_ENABLED=0
FLAGS_windows_amd64 = GOOS=windows GOARCH=amd64 CGO_ENABLED=0
FLAGS_openbsd_386 = GOOS=openbsd GOARCH=386 CGO_ENABLED=0
FLAGS_openbsd_amd64 = GOOS=openbsd GOARCH=amd64 CGO_ENABLED=0
FLAGS_solaris_amd64 = GOOS=solaris GOARCH=amd64 CGO_ENABLED=0
EXTENSION_windows_386 = .exe
EXTENSION_windows_amd64 = .exe
all: build
localgo: $(GO_DIR)/bin/go
$(GO_DIR)/bin/go:
mkdir -p $(GO_DIR)
rm -f $@
@echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR)
curl -s $(GO_DOWNLOAD_URL) | tar xzf - --strip-components=1 -C $(GO_DIR)
# Cross-compile final applications
out/$(RELEASE_VERSION)/%/websocketd: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go
rm -f $@
mkdir -p $(dir $@)
$(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd ..
out/$(RELEASE_VERSION)/%/websocketd.exe: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go
rm -f $@
mkdir -p $(dir $@)
$(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd.exe ..
out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-%.zip: out/$(RELEASE_VERSION)/%/websocketd
rm -f $@
zip -j $@ $< ../{README.md,LICENSE,CHANGES}
out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-windows_%.zip: out/$(RELEASE_VERSION)/windows_%/websocketd.exe
rm -f $@
zip -j $@ $< ../{README.md,LICENSE,CHANGES}
BINARIES = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/$(PLATFORM)/websocketd$(EXTENSION_$(PLATFORM)))
ZIPS = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-$(PLATFORM).zip)
DEBS = out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb
RPMS = out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm
binaries: $(BINARIES)
build: out/$(RELEASE_VERSION)/CHECKSUMS
out/$(RELEASE_VERSION)/CHECKSUMS: $(BINARIES) $(ZIPS) $(DEBS) $(RPMS)
sha256sum $^ | sed -e 's/out\/$(RELEASE_VERSION)\///' >$@
BASEFPM=--description "WebSockets server that converts STDIO scripts to powerful HTML5 applications." --url http://websocketd.com/ --license MIT --vendor "websocketd team <joe@walnes.com>" --maintainer "abc@alexsergeyev.com"
DEBFPM=""
RPMFPM=--rpm-os linux
deb: $(DEBS)
rpm: $(RPMS)
out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd
mkdir -p out/$(RELEASE_VERSION)/deb32/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/deb32/usr/bin/
cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb32/usr/share/doc/websocketd-$(RELEASE_VERSION)
cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb32/usr/share/man/man1/websocket.1.gz
fpm -f -s dir -t deb -a i386 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/deb32/ -p out/$(RELEASE_VERSION)/websocketd-VERSION_ARCH.deb --deb-no-default-config-files $(BASEFPM) $(DEB_FPM) usr/
rm -rf out/$(RELEASE_VERSION)/deb32/
out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd
mkdir -p out/$(RELEASE_VERSION)/deb64/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/deb64/usr/bin/
cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb64/usr/share/doc/websocketd-$(RELEASE_VERSION)
cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb64/usr/share/man/man1/websocket.1.gz
fpm -f -s dir -t deb -a amd64 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/deb64/ -p out/$(RELEASE_VERSION)/websocketd-VERSION_ARCH.deb --deb-no-default-config-files $(BASEFPM) $(DEB_FPM) usr/
rm -rf out/$(RELEASE_VERSION)/deb64/
out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd
mkdir -p out/$(RELEASE_VERSION)/rpm64/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/rpm64/usr/bin/
cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm64/usr/share/doc/websocketd-$(RELEASE_VERSION)
cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm64/usr/share/man/man1/websocket.1.gz
fpm -f -s dir -t rpm -a x86_64 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/rpm64/ -p out/$(RELEASE_VERSION)/websocketd.VERSION.ARCH.rpm $(BASEFPM) $(RPMFPM) usr/
rm -rf out/$(RELEASE_VERSION)/rpm64/
out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd
mkdir -p out/$(RELEASE_VERSION)/rpm32/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/rpm32/usr/bin/
cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm32/usr/share/doc/websocketd-$(RELEASE_VERSION)
cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm32/usr/share/man/man1/websocket.1.gz
fpm -f -s dir -t rpm -a i386 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/rpm32/ -p out/$(RELEASE_VERSION)/websocketd.VERSION.ARCH.rpm $(BASEFPM) $(RPMFPM) usr/
rm -rf out/$(RELEASE_VERSION)/rpm32/
# Clean up
clobber: clean
rm -rf $(GO_DIR)
clean:
rm -rf out
.PHONY: all build deb rpm localgo clobber clean
================================================
FILE: release/README
================================================
Release scripts for websocketd
==============================
Perform a fully automated repeatable release of websocketd.
This is three-stage process in normal release cycle that's performed by
repository maintainers.
* Update CHANGES and Tag
* Build
* Release
Those that do not have permissions to push tags could still use this build
chain to build their own customized versions or packages.
## Step 1: Update CHANGES and Tag
First edit and commit CHANGES file. List all significant updates between releases.
Annotated tags require for release:
git tag -a vx.x.x
To see currently tagged version run tag with -l option.
## Step 2: Build
release/Makefile contains all required to download pre-verified for build release of Go and cross-compile
websocketd for all platforms that we release for.
cd release
make
Is generally recommended but other build options are available:
* go-compile: build cross-platform golang pakages
* binaries: only create websocketd binaries, do not generate zip distributions
* deb, rpm: only create particular kind of packages
* clean-go: remove build files for go
* clean-out: remove built binaries, zip and linux packages
* clean: remove everything (go and release files)
Building requires to have gcc and other build tools installed. Building rpm/deb files would fail without fpm ("gem install fpm" itself requires ruby devel tools).
Create CHECKSUMS.asc file using gpg and key that other developers shared with you:
gpg -u KEYID --clearsign CHECKSUMS
(key has to be installed in your gpg environment first)
## Step 3: Release
In order to make release official following steps required:
# push tags (assuming joewalnes repo is origin):
go push --tags origin master:master
Upload files to github release (to be automated). Use these instructions for now: https://github.com/blog/1547-release-your-software
================================================
FILE: release/websocketd.man
================================================
.\" Manpage for websocketd.
.\" Contact abc@alexsergeyev.com to correct errors or typos.
.TH websocketd 8 "28 Sep 2014" "0.0" "websocketd man page"
.SH NAME
websocketd \- turns any program that uses STDIN/STDOUT into a WebSocket server.
.SH SYNOPSIS
websocketd [options] COMMAND [command args]
or
websocketd [options] --dir=SOMEDIR
.SH DESCRIPTION
\fBwebsocketd\fR is a command line tool that will allow any executable program
that accepts input on stdin and produces output on stdout to be turned into
a WebSocket server.
To learn more about websocketd visit \fIhttp://websocketd.com\fR and project WIKI
on GitHub!
.SH OPTIONS
A summary of the options supported by websocketd is included below.
.PP
\-\-port=PORT
.RS 4
HTTP port to listen on.
.RE
.PP
\-\-address=ADDRESS
.RS 4
Address to bind to (multiple options allowed). Use square brackets to specify IPv6 address. Default: "" (all)
.RE
.PP
\-\-sameorigin={true,false}
.RS 4
Restrict (HTTP 403) protocol upgrades if the Origin header does not match to requested HTTP Host. Default: false.
.RE
.PP
--origin=host[:port][,host[:port]...]
.RS 4
Restrict (HTTP 403) protocol upgrades if the Origin header does not match to one of the host and port combinations listed. If the port is not specified, any port number will match. Default: "" (allow any origin)
.RE
.PP
\-\-ssl \-\-sslcert=FILE \-\-sslkey=FILE
.RS 4
Listen for HTTPS socket instead of HTTP. All three options must be used or all of them should be omitted.
.RE
.PP
\-\-passenv VAR[,VAR...]
.RS 4
Lists environment variables allowed to be passed to executed scripts.
.RE
.PP
\-\-reverselookup={true,false}
.RS 4
Perform DNS reverse lookups on remote clients. Default: true
.RE
.PP
\-\-dir=DIR
.RS 4
Allow all scripts in the local directory to be accessed as WebSockets. If using this, option, then the standard program and args options should not be specified.
.RE
.PP
\-\-staticdir=DIR
.RS 4
Serve static files in this directory over HTTP.
.RE
.PP
\-\-cgidir=DIR
.RS 4
Serve CGI scripts in this directory over HTTP.
.RE
.PP
\-\-help
.RS 4
Print help and exit.
.RE
.PP
\-\-version
.RS 4
Print version and exit.
.RE
.PP
\-\-license
.RS 4
Print license and exit.
.RE
.PP
\-\-devconsole
.RS 4
Enable interactive development console. This enables you to access the websocketd server with a web-browser and use a user interface to quickly test WebSocket endpoints. For example, to test an endpoint at ws://[host]/foo, you can visit http://[host]/foo in your browser. This flag cannot be used in conjunction with \-\-staticdir or \-\-cgidir.
.RE
.PP
\-\-loglevel=LEVEL
.RS 4
Log level to use (default access). From most to least verbose: debug, trace, access, info, error, fatal
.RE
.SH SEE ALSO
.RS 2
* full documentation at \fIhttp://websocketd.com\fR
.RE
.RS 2
* project source at \fIhttps://github.com/joewalnes/websocketd\fR
.RE
.SH BUGS
The only known condition so far is that certain applications in programming languages that enforce implicit STDOUT buffering (Perl, Python, etc.) would be producing unexpected data passing
delays when run under \fBwebsocketd\fR. Such issues could be solved by editing the source code of those applications (prohibiting buffering) or modifying their environment to trick them
into autoflush mode (e.g. pseudo-terminal wrapper "unbuffer").
Active issues in development are discussed on GitHub: \fIhttps://github.com/joewalnes/websocketd/issues\fR.
Please use that page to share your concerns and ideas about \fBwebsocketd\fR, authors would greatly appreciate your help!
.SH AUTHOR
Copyright 2013-2014 Joe Walnes and the websocketd team. All rights reserved.
BSD license: Run 'websocketd \-\-license' for details.
================================================
FILE: version.go
================================================
// Copyright 2013 Joe Walnes and the websocketd team.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"runtime"
)
// This value can be set for releases at build time using:
// go {build|run} -ldflags "-X main.version 1.2.3 -X main.buildinfo timestamp-@githubuser-platform".
// If unset, Version() shall return "DEVBUILD".
var version string = "DEVBUILD"
var buildinfo string = "--"
func Version() string {
return fmt.Sprintf("%s (%s %s-%s) %s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH, buildinfo)
}
gitextract_mkgu5c8j/ ├── .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
SYMBOL INDEX (116 symbols across 26 files)
FILE: config.go
type Config (line 21) | type Config struct
type Arglist (line 30) | type Arglist
method String (line 32) | func (al *Arglist) String() string {
method Set (line 36) | func (al *Arglist) Set(value string) error {
function parseCommandLine (line 53) | func parseCommandLine() *Config {
FILE: examples/c#/Count/Program.cs
class Program (line 7) | class Program
method Main (line 9) | static void Main(string[] args)
FILE: examples/c#/Echo/Program.cs
class Program (line 5) | class Program
method Main (line 7) | static void Main(string[] args)
FILE: examples/hack/dump-env.hh
function foreach (line 40) | foreach($names as $name) {
function foreach (line 47) | foreach($server as $k => $v) {
FILE: examples/java/Count/Count.java
class Count (line 1) | public class Count {
method main (line 2) | public static void main(String[] args) {
FILE: examples/java/Echo/Echo.java
class Echo (line 2) | public class Echo {
method main (line 3) | public static void main(String[] args) {
FILE: examples/rust/count.rs
function main (line 5) | fn main() {
FILE: examples/rust/dump-env.rs
constant NAMES (line 6) | const NAMES: &'static [&'static str] = &[
function main (line 30) | fn main() {
FILE: examples/rust/greeter.rs
function main (line 4) | fn main() {
FILE: help.go
constant help (line 16) | help = `
constant short (line 129) | short = `
function get_help_message (line 143) | func get_help_message(content string) string {
function HelpProcessName (line 149) | func HelpProcessName() string {
function PrintHelp (line 159) | func PrintHelp() {
function ShortHelp (line 163) | func ShortHelp() {
FILE: libwebsocketd/config.go
type Config (line 12) | type Config struct
FILE: libwebsocketd/console.go
constant defaultConsoleContent (line 16) | defaultConsoleContent = `
FILE: libwebsocketd/endpoint.go
type Endpoint (line 8) | type Endpoint interface
function PipeEndpoints (line 15) | func PipeEndpoints(e1, e2 Endpoint) {
FILE: libwebsocketd/endpoint_test.go
function TestTrimEOL (line 20) | func TestTrimEOL(t *testing.T) {
function BenchmarkTrimEOL (line 29) | func BenchmarkTrimEOL(b *testing.B) {
type TestEndpoint (line 35) | type TestEndpoint struct
method StartReading (line 42) | func (e *TestEndpoint) StartReading() {
method Terminate (line 52) | func (e *TestEndpoint) Terminate() {
method Output (line 55) | func (e *TestEndpoint) Output() chan []byte {
method Send (line 59) | func (e *TestEndpoint) Send(msg []byte) bool {
function TestEndpointPipe (line 64) | func TestEndpointPipe(t *testing.T) {
FILE: libwebsocketd/env.go
constant gatewayInterface (line 15) | gatewayInterface = "websocketd-CGI/0.1"
function createEnv (line 21) | func createEnv(handler *WebsocketdHandler, req *http.Request, log *LogSc...
function appendEnv (line 116) | func appendEnv(env []string, k string, v ...string) []string {
FILE: libwebsocketd/handler.go
type WebsocketdHandler (line 20) | type WebsocketdHandler struct
method accept (line 60) | func (wsh *WebsocketdHandler) accept(ws *websocket.Conn, log *LogScope) {
function NewWebsocketdHandler (line 32) | func NewWebsocketdHandler(s *WebsocketdServer, req *http.Request, log *L...
type RemoteInfo (line 85) | type RemoteInfo struct
function GetRemoteInfo (line 90) | func GetRemoteInfo(remote string, doLookup bool) (*RemoteInfo, error) {
type URLInfo (line 112) | type URLInfo struct
function GetURLInfo (line 119) | func GetURLInfo(path string, config *Config) (*URLInfo, error) {
function generateId (line 160) | func generateId() string {
FILE: libwebsocketd/handler_test.go
function TestParsePathWithScriptDir (line 15) | func TestParsePathWithScriptDir(t *testing.T) {
function TestParsePathExplicitScript (line 85) | func TestParsePathExplicitScript(t *testing.T) {
FILE: libwebsocketd/http.go
type WebsocketdServer (line 28) | type WebsocketdServer struct
method ServeHTTP (line 68) | func (h *WebsocketdServer) ServeHTTP(w http.ResponseWriter, req *http....
method TellURL (line 187) | func (h *WebsocketdServer) TellURL(scheme, host, path string) string {
method noteForkCreated (line 204) | func (h *WebsocketdServer) noteForkCreated() error {
method noteForkCompled (line 219) | func (h *WebsocketdServer) noteForkCompled() {
function NewWebsocketdServer (line 35) | func NewWebsocketdServer(config *Config, log *LogScope, maxforks int) *W...
function splitMimeHeader (line 46) | func splitMimeHeader(s string) (string, string) {
function pushHeaders (line 61) | func pushHeaders(h http.Header, hdrs []string) {
function checkOrigin (line 233) | func checkOrigin(req *http.Request, config *Config, log *LogScope) (err ...
function tellHostPort (line 314) | func tellHostPort(host string, ssl bool) (server, port string, err error) {
FILE: libwebsocketd/http_test.go
function TestTellHostPort (line 23) | func TestTellHostPort(t *testing.T) {
constant ReqHTTPS (line 43) | ReqHTTPS = iota
constant ReqHTTP (line 44) | ReqHTTP
constant OriginMustBeSame (line 45) | OriginMustBeSame
constant OriginCouldDiffer (line 46) | OriginCouldDiffer
constant ReturnsPass (line 47) | ReturnsPass
constant ReturnsError (line 48) | ReturnsError
function TestCheckOrigin (line 82) | func TestCheckOrigin(t *testing.T) {
function TestSplitMimeHeader (line 129) | func TestSplitMimeHeader(t *testing.T) {
FILE: libwebsocketd/launcher.go
type LaunchedProcess (line 13) | type LaunchedProcess struct
function launchCmd (line 20) | func launchCmd(commandName string, commandArgs []string, env []string) (...
FILE: libwebsocketd/license.go
constant license (line 9) | license = `
constant License (line 33) | License = license
FILE: libwebsocketd/logscope.go
type LogLevel (line 13) | type LogLevel
constant LogDebug (line 16) | LogDebug = iota
constant LogTrace (line 17) | LogTrace
constant LogAccess (line 18) | LogAccess
constant LogInfo (line 19) | LogInfo
constant LogError (line 20) | LogError
constant LogFatal (line 21) | LogFatal
constant LogNone (line 23) | LogNone = 126
constant LogUnknown (line 24) | LogUnknown = 127
type LogFunc (line 27) | type LogFunc
type LogScope (line 29) | type LogScope struct
method Associate (line 42) | func (l *LogScope) Associate(key string, value string) {
method Debug (line 46) | func (l *LogScope) Debug(category string, msg string, args ...interfac...
method Trace (line 50) | func (l *LogScope) Trace(category string, msg string, args ...interfac...
method Access (line 54) | func (l *LogScope) Access(category string, msg string, args ...interfa...
method Info (line 58) | func (l *LogScope) Info(category string, msg string, args ...interface...
method Error (line 62) | func (l *LogScope) Error(category string, msg string, args ...interfac...
method Fatal (line 66) | func (l *LogScope) Fatal(category string, msg string, args ...interfac...
method NewLevel (line 70) | func (parent *LogScope) NewLevel(logFunc LogFunc) *LogScope {
type AssocPair (line 37) | type AssocPair struct
function RootLogScope (line 79) | func RootLogScope(minLevel LogLevel, logFunc LogFunc) *LogScope {
function Timestamp (line 88) | func Timestamp() string {
function LevelFromString (line 92) | func LevelFromString(s string) LogLevel {
FILE: libwebsocketd/process_endpoint.go
type ProcessEndpoint (line 15) | type ProcessEndpoint struct
method Terminate (line 32) | func (pe *ProcessEndpoint) Terminate() {
method Output (line 89) | func (pe *ProcessEndpoint) Output() chan []byte {
method Send (line 93) | func (pe *ProcessEndpoint) Send(msg []byte) bool {
method StartReading (line 98) | func (pe *ProcessEndpoint) StartReading() {
method process_txtout (line 107) | func (pe *ProcessEndpoint) process_txtout() {
method process_binout (line 124) | func (pe *ProcessEndpoint) process_binout() {
method log_stderr (line 141) | func (pe *ProcessEndpoint) log_stderr() {
function NewProcessEndpoint (line 23) | func NewProcessEndpoint(process *LaunchedProcess, bin bool, log *LogScop...
function trimEOL (line 158) | func trimEOL(b []byte) []byte {
FILE: libwebsocketd/websocket_endpoint.go
type WebSocketEndpoint (line 19) | type WebSocketEndpoint struct
method Terminate (line 39) | func (we *WebSocketEndpoint) Terminate() {
method Output (line 43) | func (we *WebSocketEndpoint) Output() chan []byte {
method Send (line 47) | func (we *WebSocketEndpoint) Send(msg []byte) bool {
method StartReading (line 62) | func (we *WebSocketEndpoint) StartReading() {
method read_frames (line 66) | func (we *WebSocketEndpoint) read_frames() {
function NewWebSocketEndpoint (line 26) | func NewWebSocketEndpoint(ws *websocket.Conn, bin bool, log *LogScope) *...
FILE: main.go
function logfunc (line 19) | func logfunc(l *libwebsocketd.LogScope, level libwebsocketd.LogLevel, le...
function main (line 38) | func main() {
FILE: version.go
function Version (line 19) | func Version() string {
Condensed preview — 120 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (168K chars).
[
{
"path": ".gitignore",
"chars": 16,
"preview": "websocketd\ngo-*\n"
},
{
"path": "AUTHORS",
"chars": 178,
"preview": "websocketd authors\n==================\n\nJoe Walnes <joe@walnes.com>\nGareth Jones <gareth.e.jones@gmail.com>\nAjit George <"
},
{
"path": "CHANGES",
"chars": 1746,
"preview": "Version 0.4.1 (Jan 24, 2021)\n\n* Minor changes only\n* Updated to Go 1.15.7\n\nVersion 0.3.1 (Jan 28, 2019)\n\n* Minor improv"
},
{
"path": "LICENSE",
"chars": 1327,
"preview": "Copyright (c) 2014, Joe Walnes and the websocketd authors.\nAll rights reserved.\n\nRedistribution and use in source and bi"
},
{
"path": "Makefile",
"chars": 1307,
"preview": "# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code is governed by a B"
},
{
"path": "README.md",
"chars": 5132,
"preview": "websocketd\n==========\n\n`websocketd` is a small command-line tool that will wrap an existing command-line interface progr"
},
{
"path": "config.go",
"chars": 9037,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "examples/bash/README.txt",
"chars": 131,
"preview": "This examples directory shows some examples written in Bash.\n\nYou can also test the command files by running from the co"
},
{
"path": "examples/bash/chat.sh",
"chars": 812,
"preview": "#!/bin/bash\n\n# Copyright 2013 Jeroen Janssens\n# All rights reserved.\n# Use of this source code is governed by a BSD-styl"
},
{
"path": "examples/bash/count.sh",
"chars": 329,
"preview": "#!/bin/bash\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code is go"
},
{
"path": "examples/bash/dump-env.sh",
"chars": 720,
"preview": "#!/bin/bash\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code is go"
},
{
"path": "examples/bash/greeter.sh",
"chars": 302,
"preview": "#!/bin/bash\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code is go"
},
{
"path": "examples/bash/send-receive.sh",
"chars": 163,
"preview": "#!/bin/bash\n\n\nwhile true; do\n\tcnt=0\n\twhile read -t 0.01 _; do\n\t\t((cnt++))\n\tdone\n\n\techo \"$(date)\" \"($cnt line(s) received"
},
{
"path": "examples/c#/.gitignore",
"chars": 1452,
"preview": "# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)\n[Bb]in/\n[Oo]bj/\n\n# mstest test results\nTestResu"
},
{
"path": "examples/c#/Count/App.config",
"chars": 180,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n <startup> \n <supportedRuntime version=\"v4.0\" sku=\".N"
},
{
"path": "examples/c#/Count/Count.csproj",
"chars": 2484,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microso"
},
{
"path": "examples/c#/Count/Program.cs",
"chars": 325,
"preview": "using System;\nusing System.Linq;\nusing System.Threading;\n\nnamespace Count\n{\n class Program\n {\n static void"
},
{
"path": "examples/c#/Count/Properties/AssemblyInfo.cs",
"chars": 1300,
"preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Infor"
},
{
"path": "examples/c#/Echo/App.config",
"chars": 180,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n <startup> \n <supportedRuntime version=\"v4.0\" sku=\".N"
},
{
"path": "examples/c#/Echo/Echo.csproj",
"chars": 2482,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microso"
},
{
"path": "examples/c#/Echo/Program.cs",
"chars": 263,
"preview": "using System;\n\nnamespace Echo\n{\n class Program\n {\n static void Main(string[] args)\n {\n w"
},
{
"path": "examples/c#/Echo/Properties/AssemblyInfo.cs",
"chars": 1298,
"preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Infor"
},
{
"path": "examples/c#/Examples.sln",
"chars": 1343,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 2012\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C"
},
{
"path": "examples/c#/README.md",
"chars": 552,
"preview": "## Running the examples on Windows\n\n1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-"
},
{
"path": "examples/c#/run_count.cmd",
"chars": 49,
"preview": "websocketd --port=8080 --devconsole bin\\Count.exe"
},
{
"path": "examples/c#/run_echo.cmd",
"chars": 48,
"preview": "websocketd --port=8080 --devconsole bin\\Echo.exe"
},
{
"path": "examples/cgi-bin/README.txt",
"chars": 255,
"preview": "This examples directory shows how websocketd can also serve CGI scripts via HTTP.\n\n$ websocketd --port=1234 --cgidir=exa"
},
{
"path": "examples/cgi-bin/dump-env.sh",
"chars": 791,
"preview": "#!/bin/sh\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code is gove"
},
{
"path": "examples/f#/.gitignore",
"chars": 1452,
"preview": "# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)\n[Bb]in/\n[Oo]bj/\n\n# mstest test results\nTestResu"
},
{
"path": "examples/f#/Count/App.config",
"chars": 682,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n <startup> \n <supportedRuntime version=\"v4.0\" sku=\".N"
},
{
"path": "examples/f#/Count/Count.fsproj",
"chars": 2870,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microso"
},
{
"path": "examples/f#/Count/Program.fs",
"chars": 189,
"preview": "open System\nopen System.Threading\n\n[<EntryPoint>]\nlet main argv = \n [| 1..10 |] |> Array.iter (Console.WriteLine >> "
},
{
"path": "examples/f#/Echo/App.config",
"chars": 682,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n <startup> \n <supportedRuntime version=\"v4.0\" sku=\".N"
},
{
"path": "examples/f#/Echo/Echo.fsproj",
"chars": 2868,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microso"
},
{
"path": "examples/f#/Echo/Program.fs",
"chars": 194,
"preview": "open System\n\n[<EntryPoint>]\nlet main argv = \n let rec recLoop () =\n Console.ReadLine() |> Console.WriteLine\n "
},
{
"path": "examples/f#/Examples.sln",
"chars": 1343,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 2012\nProject(\"{F2A71F9B-5D33-465A-A702-920"
},
{
"path": "examples/f#/README.md",
"chars": 552,
"preview": "## Running the examples on Windows\n\n1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-"
},
{
"path": "examples/f#/run_count.cmd",
"chars": 49,
"preview": "websocketd --port=8080 --devconsole bin\\Count.exe"
},
{
"path": "examples/f#/run_echo.cmd",
"chars": 48,
"preview": "websocketd --port=8080 --devconsole bin\\Echo.exe"
},
{
"path": "examples/hack/README.md",
"chars": 411,
"preview": "This examples directory shows some examples written in [Hack](https://hacklang.org).\n\n### Requirements :\n\n- [HHVM](https"
},
{
"path": "examples/hack/count.hh",
"chars": 488,
"preview": "#!/usr/bin/hhvm\n<?hh // strict\n\nuse namespace HH\\Lib\\Str;\nuse function HH\\Lib\\Experimental\\IO\\request_output;\n\n// Simple"
},
{
"path": "examples/hack/dump-env.hh",
"chars": 1257,
"preview": "#!/usr/bin/hhvm\n<?hh // strict\n\nuse namespace HH\\Lib\\Str;\nuse function HH\\Lib\\Experimental\\IO\\request_output;\n\n<<__Entry"
},
{
"path": "examples/hack/greeter.hh",
"chars": 503,
"preview": "#!/usr/bin/hhvm\n<?hh // strict\n\nuse namespace HH\\Lib\\Str;\nuse namespace HH\\Lib\\Experimental\\IO;\n\n<<__EntryPoint>>\nasync "
},
{
"path": "examples/haskell/README.md",
"chars": 516,
"preview": "## Haskell examples\n\n### Count\n\nStart the server with\n\n```\n$ websocketd --port=8080 --devconsole --passenv PATH ./count."
},
{
"path": "examples/haskell/count.hs",
"chars": 278,
"preview": "#!/usr/bin/env runhaskell\n\nimport Control.Monad (forM_)\nimport Control.Concurrent (threadDelay)\nimport System.IO (hFlush"
},
{
"path": "examples/haskell/greeter.hs",
"chars": 326,
"preview": "#!/usr/bin/env runhaskell\n\nimport Control.Monad (unless)\nimport System.IO (hFlush, stdout, stdin, hIsEOF)\n\n-- | For each"
},
{
"path": "examples/html/count.html",
"chars": 723,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>websocketd count example</title>\n <style>\n #count {\n font: bol"
},
{
"path": "examples/java/Count/Count.java",
"chars": 309,
"preview": "public class Count {\n public static void main(String[] args) {\n for(int i = 1; i <= 10; i++) {\n Sys"
},
{
"path": "examples/java/Count/count.sh",
"chars": 37,
"preview": "#!/bin/sh\njavac Count.java\njava Count"
},
{
"path": "examples/java/Echo/Echo.java",
"chars": 412,
"preview": "import java.io.*;\npublic class Echo {\n public static void main(String[] args) {\n while(true) {\n try"
},
{
"path": "examples/java/Echo/echo.sh",
"chars": 35,
"preview": "#!/bin/sh\njavac Echo.java\njava Echo"
},
{
"path": "examples/java/README.md",
"chars": 516,
"preview": "## Running the examples on Mac\n\n1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-inst"
},
{
"path": "examples/lua/README.md",
"chars": 1356,
"preview": "The examples demonstrate the use of websocketd with lua. There are two examples in the directory both very basic.\n\n1. Gr"
},
{
"path": "examples/lua/greeter.lua",
"chars": 114,
"preview": "local input = io.stdin:read()\nwhile input do\n print(input)\n io.stdout:flush()\n input = io.stdin:read()\nend\n\n"
},
{
"path": "examples/lua/json.lua",
"chars": 8951,
"preview": "--\n-- json.lua\n--\n-- Copyright (c) 2015 rxi\n--\n-- This library is free software; you can redistribute it and/or modify i"
},
{
"path": "examples/lua/json_ws.lua",
"chars": 173,
"preview": "local input = io.stdin:read()\nlocal json = require(\"json\")\nwhile input do\n print(json.encode({res=\"json\",mess=input}))"
},
{
"path": "examples/nodejs/README.md",
"chars": 573,
"preview": "## Running the examples on Mac\n\n##### 1. Download\n\n[Install](https://github.com/joewalnes/websocketd/wiki/Download-and-i"
},
{
"path": "examples/nodejs/count.js",
"chars": 226,
"preview": "(function(){\n\tvar counter = 0;\n\tvar echo = function(){\n\t\tif (counter === 10){\n\t\t\treturn;\n\t\t}\n\n\t\tsetTimeout(function(){\n\t"
},
{
"path": "examples/nodejs/greeter.js",
"chars": 273,
"preview": "// from node.js sample\n// https://nodejs.org/api/process.html#process_process_stdin\nprocess.stdin.setEncoding('utf8');\n\n"
},
{
"path": "examples/perl/README.txt",
"chars": 131,
"preview": "This examples directory shows some examples written in Perl.\n\nYou can also test the command files by running from the co"
},
{
"path": "examples/perl/count.pl",
"chars": 417,
"preview": "#!/usr/bin/perl\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code i"
},
{
"path": "examples/perl/dump-env.pl",
"chars": 919,
"preview": "#!/usr/bin/perl\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code i"
},
{
"path": "examples/perl/greeter.pl",
"chars": 392,
"preview": "#!/usr/bin/perl\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code i"
},
{
"path": "examples/php/README.txt",
"chars": 272,
"preview": "This examples directory shows some examples written in PHP.\n\nThis relies on the CLI verson of PHP being installed and in"
},
{
"path": "examples/php/count.php",
"chars": 356,
"preview": "#!/usr/bin/php\n<?php\n\n// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this sourc"
},
{
"path": "examples/php/dump-env.php",
"chars": 972,
"preview": "#!/usr/bin/php\n<?php\n\n// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this sourc"
},
{
"path": "examples/php/greeter.php",
"chars": 383,
"preview": "#!/usr/bin/php\n<?php\n\n// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this sourc"
},
{
"path": "examples/python/README.txt",
"chars": 133,
"preview": "This examples directory shows some examples written in Python.\n\nYou can also test the command files by running from the "
},
{
"path": "examples/python/count.py",
"chars": 404,
"preview": "#!/usr/bin/python\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code"
},
{
"path": "examples/python/dump-env.py",
"chars": 1027,
"preview": "#!/usr/bin/python\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code"
},
{
"path": "examples/python/greeter.py",
"chars": 406,
"preview": "#!/usr/bin/python\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code"
},
{
"path": "examples/qjs/request-reply.js",
"chars": 166,
"preview": "#!/usr/bin/env -S qjs --module\nimport * as std from \"std\";\n\nlet line;\nwhile ((line = std.in.getline()) != null) {\n cons"
},
{
"path": "examples/ruby/README.txt",
"chars": 131,
"preview": "This examples directory shows some examples written in Ruby.\n\nYou can also test the command files by running from the co"
},
{
"path": "examples/ruby/count.rb",
"chars": 351,
"preview": "#!/usr/bin/ruby\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code i"
},
{
"path": "examples/ruby/dump-env.rb",
"chars": 909,
"preview": "#!/usr/bin/ruby\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code i"
},
{
"path": "examples/ruby/greeter.rb",
"chars": 367,
"preview": "#!/usr/bin/ruby\n\n# Copyright 2013 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code i"
},
{
"path": "examples/rust/README.txt",
"chars": 131,
"preview": "This examples directory shows some examples written in Rust.\n\nYou can also test the command files by running from the co"
},
{
"path": "examples/rust/count.rs",
"chars": 295,
"preview": "use std::io::{self, Write};\nuse std::{thread, time};\n\n// Simple example script that counts to 10 at ~2Hz, then stops.\nfn"
},
{
"path": "examples/rust/dump-env.rs",
"chars": 787,
"preview": "// Standard CGI(ish) environment variables, as defined in\n// http://tools.ietf.org/html/rfc3875\n\nuse std::env;\n\nconst NA"
},
{
"path": "examples/rust/greeter.rs",
"chars": 359,
"preview": "use std::io::{self, Write};\n\n// For each line FOO received on STDIN, respond with \"Hello FOO!\".\nfn main() {\n loop {\n "
},
{
"path": "examples/swift/README.md",
"chars": 351,
"preview": "## Swift examples\n\n### Count\n\nRun the following line and open \"html/count.html\" from the websocketd examples directory.\n"
},
{
"path": "examples/swift/count.swift",
"chars": 174,
"preview": "#!/usr/bin/env xcrun -sdk macosx swift\n\nimport AppKit\n\nfor index in 1...10 {\n print(index)\n \n // Flush output\n fflus"
},
{
"path": "examples/swift/greeter.swift",
"chars": 349,
"preview": "#!/usr/bin/env xcrun -sdk macosx swift\n\nimport Foundation\n\nwhile(true){\n var stdin = NSFileHandle.fileHandleWithStandar"
},
{
"path": "examples/windows-jscript/README.txt",
"chars": 360,
"preview": "This examples directory shows some examples written in JScript\r\nthat can be run using Windows Script Hosting.\r\n\r\nNote th"
},
{
"path": "examples/windows-jscript/count.cmd",
"chars": 43,
"preview": "@echo off\r\ncscript /nologo %0\\..\\count.js\r\n"
},
{
"path": "examples/windows-jscript/count.js",
"chars": 332,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\r\n// All rights reserved.\r\n// Use of this source code is governed b"
},
{
"path": "examples/windows-jscript/dump-env.cmd",
"chars": 46,
"preview": "@echo off\r\ncscript /nologo %0\\..\\dump-env.js\r\n"
},
{
"path": "examples/windows-jscript/dump-env.js",
"chars": 1101,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\r\n// All rights reserved.\r\n// Use of this source code is governed b"
},
{
"path": "examples/windows-jscript/greeter.cmd",
"chars": 45,
"preview": "@echo off\r\ncscript /nologo %0\\..\\greeter.js\r\n"
},
{
"path": "examples/windows-jscript/greeter.js",
"chars": 357,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\r\n// All rights reserved.\r\n// Use of this source code is governed b"
},
{
"path": "examples/windows-vbscript/README.txt",
"chars": 362,
"preview": "This examples directory shows some examples written in VBScript\r\nthat can be run using Windows Script Hosting.\r\n\r\nNote t"
},
{
"path": "examples/windows-vbscript/count.cmd",
"chars": 44,
"preview": "@echo off\r\ncscript /nologo %0\\..\\count.vbs\r\n"
},
{
"path": "examples/windows-vbscript/count.vbs",
"chars": 312,
"preview": "' Copyright 2013 Joe Walnes and the websocketd team.\r\n' All rights reserved.\r\n' Use of this source code is governed by a"
},
{
"path": "examples/windows-vbscript/dump-env.cmd",
"chars": 47,
"preview": "@echo off\r\ncscript /nologo %0\\..\\dump-env.vbs\r\n"
},
{
"path": "examples/windows-vbscript/dump-env.vbs",
"chars": 1076,
"preview": "' Copyright 2013 Joe Walnes and the websocketd team.\r\n' All rights reserved.\r\n' Use of this source code is governed by a"
},
{
"path": "examples/windows-vbscript/greeter.cmd",
"chars": 46,
"preview": "@echo off\r\ncscript /nologo %0\\..\\greeter.vbs\r\n"
},
{
"path": "examples/windows-vbscript/greeter.vbs",
"chars": 342,
"preview": "' Copyright 2013 Joe Walnes and the websocketd team.\r\n' All rights reserved.\r\n' Use of this source code is governed by a"
},
{
"path": "go.mod",
"chars": 93,
"preview": "module github.com/joewalnes/websocketd\n\ngo 1.15\n\nrequire github.com/gorilla/websocket v1.4.0\n"
},
{
"path": "go.sum",
"chars": 175,
"preview": "github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=\ngithub.com/gorilla/websocket v1.4.0/"
},
{
"path": "help.go",
"chars": 6663,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/config.go",
"chars": 1875,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/console.go",
"chars": 8200,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/endpoint.go",
"chars": 621,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/endpoint_test.go",
"chars": 1749,
"preview": "package libwebsocketd\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar eol_tests = []string{\n\t\"\", \"\\n\", \"\\r\\n\", \"ok\\n\", \"o"
},
{
"path": "libwebsocketd/env.go",
"chars": 4102,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/handler.go",
"chars": 4208,
"preview": "package libwebsocketd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\""
},
{
"path": "libwebsocketd/handler_test.go",
"chars": 2209,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/http.go",
"chars": 9594,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/http_test.go",
"chars": 5095,
"preview": "package libwebsocketd\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n)\n\nvar tellHostPortTests"
},
{
"path": "libwebsocketd/launcher.go",
"chars": 850,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/license.go",
"chars": 1574,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/logscope.go",
"chars": 2630,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/process_endpoint.go",
"chars": 4214,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "libwebsocketd/websocket_endpoint.go",
"chars": 2036,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "main.go",
"chars": 3885,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
},
{
"path": "release/.gitignore",
"chars": 40,
"preview": "bin\ngo-local\nout\ngo-path\nwebsocketd.exe\n"
},
{
"path": "release/Makefile",
"chars": 7523,
"preview": "# Copyright 2013-2019 Joe Walnes and the websocketd team.\n# All rights reserved.\n# Use of this source code is governed b"
},
{
"path": "release/README",
"chars": 1885,
"preview": "Release scripts for websocketd\n==============================\n\nPerform a fully automated repeatable release of websocket"
},
{
"path": "release/websocketd.man",
"chars": 3666,
"preview": ".\\\" Manpage for websocketd.\n.\\\" Contact abc@alexsergeyev.com to correct errors or typos.\n.TH websocketd 8 \"28 Sep 2014\" "
},
{
"path": "version.go",
"chars": 633,
"preview": "// Copyright 2013 Joe Walnes and the websocketd team.\n// All rights reserved.\n// Use of this source code is governed by "
}
]
About this extraction
This page contains the full source code of the joewalnes/websocketd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 120 files (147.3 KB), approximately 44.5k tokens, and a symbol index with 116 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.