Full Code of joewalnes/websocketd for AI

master 8d6da3004585 cached
120 files
147.3 KB
44.5k tokens
116 symbols
1 requests
Download .txt
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">&times;</button>
	<button class="connect" title="Connect" style="display:none">&#x2714;</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 &#xbb;</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 += '&nbsp;';
		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)
}
Download .txt
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
Download .txt
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.

Copied to clipboard!