├── .gitignore ├── examples ├── c# │ ├── run_echo.cmd │ ├── run_count.cmd │ ├── Count │ │ ├── App.config │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── Count.csproj │ ├── Echo │ │ ├── App.config │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── Echo.csproj │ ├── README.md │ ├── Examples.sln │ └── .gitignore ├── f# │ ├── run_echo.cmd │ ├── run_count.cmd │ ├── Count │ │ ├── Program.fs │ │ ├── App.config │ │ └── Count.fsproj │ ├── Echo │ │ ├── Program.fs │ │ ├── App.config │ │ └── Echo.fsproj │ ├── README.md │ ├── Examples.sln │ └── .gitignore ├── java │ ├── Count │ │ ├── count.sh │ │ └── Count.java │ ├── Echo │ │ ├── echo.sh │ │ └── Echo.java │ └── README.md ├── windows-jscript │ ├── count.cmd │ ├── dump-env.cmd │ ├── greeter.cmd │ ├── count.js │ ├── README.txt │ ├── greeter.js │ └── dump-env.js ├── windows-vbscript │ ├── count.cmd │ ├── dump-env.cmd │ ├── greeter.cmd │ ├── count.vbs │ ├── README.txt │ ├── greeter.vbs │ └── dump-env.vbs ├── bash │ ├── README.txt │ ├── send-receive.sh │ ├── greeter.sh │ ├── count.sh │ ├── chat.sh │ └── dump-env.sh ├── perl │ ├── README.txt │ ├── greeter.pl │ ├── count.pl │ └── dump-env.pl ├── ruby │ ├── README.txt │ ├── count.rb │ ├── greeter.rb │ └── dump-env.rb ├── rust │ ├── README.txt │ ├── count.rs │ ├── greeter.rs │ └── dump-env.rs ├── lua │ ├── greeter.lua │ ├── json_ws.lua │ ├── README.md │ └── json.lua ├── python │ ├── README.txt │ ├── greeter.py │ ├── count.py │ └── dump-env.py ├── qjs │ └── request-reply.js ├── swift │ ├── count.swift │ ├── greeter.swift │ └── README.md ├── cgi-bin │ ├── README.txt │ └── dump-env.sh ├── php │ ├── README.txt │ ├── count.php │ ├── greeter.php │ └── dump-env.php ├── nodejs │ ├── count.js │ ├── greeter.js │ └── README.md ├── haskell │ ├── count.hs │ ├── greeter.hs │ └── README.md ├── hack │ ├── README.md │ ├── count.hh │ ├── greeter.hh │ └── dump-env.hh └── html │ └── count.html ├── release ├── .gitignore ├── README ├── websocketd.man └── Makefile ├── go.mod ├── go.sum ├── AUTHORS ├── version.go ├── libwebsocketd ├── endpoint.go ├── launcher.go ├── license.go ├── config.go ├── endpoint_test.go ├── websocket_endpoint.go ├── handler_test.go ├── logscope.go ├── env.go ├── handler.go ├── process_endpoint.go ├── http_test.go ├── console.go └── http.go ├── LICENSE ├── Makefile ├── CHANGES ├── main.go ├── README.md ├── help.go └── config.go /.gitignore: -------------------------------------------------------------------------------- 1 | websocketd 2 | go-* 3 | -------------------------------------------------------------------------------- /examples/c#/run_echo.cmd: -------------------------------------------------------------------------------- 1 | websocketd --port=8080 --devconsole bin\Echo.exe -------------------------------------------------------------------------------- /examples/f#/run_echo.cmd: -------------------------------------------------------------------------------- 1 | websocketd --port=8080 --devconsole bin\Echo.exe -------------------------------------------------------------------------------- /examples/java/Count/count.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | javac Count.java 3 | java Count -------------------------------------------------------------------------------- /examples/java/Echo/echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | javac Echo.java 3 | java Echo -------------------------------------------------------------------------------- /examples/c#/run_count.cmd: -------------------------------------------------------------------------------- 1 | websocketd --port=8080 --devconsole bin\Count.exe -------------------------------------------------------------------------------- /examples/f#/run_count.cmd: -------------------------------------------------------------------------------- 1 | websocketd --port=8080 --devconsole bin\Count.exe -------------------------------------------------------------------------------- /release/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | go-local 3 | out 4 | go-path 5 | websocketd.exe 6 | -------------------------------------------------------------------------------- /examples/windows-jscript/count.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\count.js 3 | -------------------------------------------------------------------------------- /examples/windows-vbscript/count.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\count.vbs 3 | -------------------------------------------------------------------------------- /examples/windows-jscript/dump-env.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\dump-env.js 3 | -------------------------------------------------------------------------------- /examples/windows-jscript/greeter.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\greeter.js 3 | -------------------------------------------------------------------------------- /examples/windows-vbscript/dump-env.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\dump-env.vbs 3 | -------------------------------------------------------------------------------- /examples/windows-vbscript/greeter.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cscript /nologo %0\..\greeter.vbs 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/joewalnes/websocketd 2 | 3 | go 1.15 4 | 5 | require github.com/gorilla/websocket v1.4.0 6 | -------------------------------------------------------------------------------- /examples/bash/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Bash. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/perl/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Perl. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/ruby/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Ruby. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/rust/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Rust. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/lua/greeter.lua: -------------------------------------------------------------------------------- 1 | local input = io.stdin:read() 2 | while input do 3 | print(input) 4 | io.stdout:flush() 5 | input = io.stdin:read() 6 | end 7 | 8 | -------------------------------------------------------------------------------- /examples/python/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in Python. 2 | 3 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 2 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | websocketd authors 2 | ================== 3 | 4 | Joe Walnes 5 | Gareth Jones 6 | Ajit George 7 | Alex Sergeyev 8 | -------------------------------------------------------------------------------- /examples/c#/Count/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/c#/Echo/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/lua/json_ws.lua: -------------------------------------------------------------------------------- 1 | local input = io.stdin:read() 2 | local json = require("json") 3 | while input do 4 | print(json.encode({res="json",mess=input})) 5 | io.stdout:flush() 6 | input = io.stdin:read() 7 | end 8 | -------------------------------------------------------------------------------- /examples/qjs/request-reply.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S qjs --module 2 | import * as std from "std"; 3 | 4 | let line; 5 | while ((line = std.in.getline()) != null) { 6 | console.log("RCVD: " + line) 7 | std.out.flush(); 8 | } 9 | -------------------------------------------------------------------------------- /examples/f#/Count/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | open System.Threading 3 | 4 | [] 5 | let main argv = 6 | [| 1..10 |] |> Array.iter (Console.WriteLine >> (fun _ -> Thread.Sleep(1000))) 7 | 8 | 0 // return an integer exit code 9 | -------------------------------------------------------------------------------- /examples/bash/send-receive.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | while true; do 5 | cnt=0 6 | while read -t 0.01 _; do 7 | ((cnt++)) 8 | done 9 | 10 | echo "$(date)" "($cnt line(s) received)" 11 | sleep $((RANDOM % 10 + 1)) & wait 12 | done 13 | -------------------------------------------------------------------------------- /examples/swift/count.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xcrun -sdk macosx swift 2 | 3 | import AppKit 4 | 5 | for index in 1...10 { 6 | print(index) 7 | 8 | // Flush output 9 | fflush(__stdoutp) 10 | 11 | NSThread.sleepForTimeInterval(0.5) 12 | } -------------------------------------------------------------------------------- /examples/f#/Echo/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | [] 4 | let main argv = 5 | let rec recLoop () = 6 | Console.ReadLine() |> Console.WriteLine 7 | recLoop() 8 | 9 | recLoop() 10 | 11 | 0 // return an integer exit code 12 | -------------------------------------------------------------------------------- /examples/cgi-bin/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows how websocketd can also serve CGI scripts via HTTP. 2 | 3 | $ websocketd --port=1234 --cgidir=examples/cgi-bin 4 | # Then access http://localhost:1234/dump-env.sh 5 | 6 | 7 | You can also test the command files by running from the command line. 8 | 9 | -------------------------------------------------------------------------------- /examples/php/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in PHP. 2 | 3 | This relies on the CLI verson of PHP being installed and in the path. 4 | See http://www.php.net/manual/en/features.commandline.introduction.php 5 | 6 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/nodejs/count.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var counter = 0; 3 | var echo = function(){ 4 | if (counter === 10){ 5 | return; 6 | } 7 | 8 | setTimeout(function(){ 9 | counter++; 10 | echo(); 11 | process.stdout.write(counter.toString() + "\n"); 12 | }, 500); 13 | } 14 | 15 | echo(); 16 | })(); 17 | -------------------------------------------------------------------------------- /examples/nodejs/greeter.js: -------------------------------------------------------------------------------- 1 | // from node.js sample 2 | // https://nodejs.org/api/process.html#process_process_stdin 3 | process.stdin.setEncoding('utf8'); 4 | 5 | process.stdin.on('readable', function() { 6 | var chunk = process.stdin.read(); 7 | if (chunk !== null) { 8 | process.stdout.write('data: ' + chunk); 9 | } 10 | }); -------------------------------------------------------------------------------- /examples/c#/Echo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Echo 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | while (true) 10 | { 11 | var msg = Console.ReadLine(); 12 | Console.WriteLine(msg); 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/haskell/count.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | 3 | import Control.Monad (forM_) 4 | import Control.Concurrent (threadDelay) 5 | import System.IO (hFlush, stdout) 6 | 7 | -- | Count from 1 to 10 with a sleep 8 | main :: IO () 9 | main = forM_ [1 :: Int .. 10] $ \count -> do 10 | print count 11 | hFlush stdout 12 | threadDelay 500000 13 | -------------------------------------------------------------------------------- /examples/rust/count.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | use std::{thread, time}; 3 | 4 | // Simple example script that counts to 10 at ~2Hz, then stops. 5 | fn main() { 6 | for i in 1..11 { 7 | println!("{}", i); 8 | io::stdout().flush().ok().expect("Could not flush stdout"); 9 | thread::sleep(time::Duration::from_millis(500)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/bash/greeter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # For each line FOO received on STDIN, respond with "Hello FOO!". 9 | while read LINE 10 | do 11 | echo "Hello $LINE!" 12 | done -------------------------------------------------------------------------------- /examples/java/Count/Count.java: -------------------------------------------------------------------------------- 1 | public class Count { 2 | public static void main(String[] args) { 3 | for(int i = 1; i <= 10; i++) { 4 | System.out.println(i); 5 | try { 6 | Thread.sleep(1000); 7 | } catch (InterruptedException e) { 8 | e.printStackTrace(); 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/windows-vbscript/count.vbs: -------------------------------------------------------------------------------- 1 | ' Copyright 2013 Joe Walnes and the websocketd team. 2 | ' All rights reserved. 3 | ' Use of this source code is governed by a BSD-style 4 | ' license that can be found in the LICENSE file. 5 | 6 | 7 | ' Simple example script that counts to 10 at ~2Hz, then stops. 8 | for i = 1 to 10 9 | WScript.echo i 10 | WScript.sleep 500 11 | next 12 | -------------------------------------------------------------------------------- /examples/bash/count.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Simple example script that counts to 10 at ~2Hz, then stops. 9 | for ((COUNT = 1; COUNT <= 10; COUNT++)) 10 | do 11 | echo $COUNT 12 | sleep 0.5 13 | done 14 | -------------------------------------------------------------------------------- /examples/windows-jscript/count.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Simple example script that counts to 10 at ~2Hz, then stops. 7 | for (var i = 1; i <= 10; i++) { 8 | WScript.echo(i); 9 | WScript.sleep(500); 10 | } 11 | -------------------------------------------------------------------------------- /examples/haskell/greeter.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | 3 | import Control.Monad (unless) 4 | import System.IO (hFlush, stdout, stdin, hIsEOF) 5 | 6 | -- | For each line FOO received on STDIN, respond with "Hello FOO!". 7 | main :: IO () 8 | main = do 9 | eof <- hIsEOF stdin 10 | unless eof $ do 11 | line <- getLine 12 | putStrLn $ "Hello " ++ line ++ "!" 13 | hFlush stdout 14 | main 15 | -------------------------------------------------------------------------------- /examples/windows-jscript/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in JScript 2 | that can be run using Windows Script Hosting. 3 | 4 | Note that each .js file, also requires a .cmd file to launch it. 5 | The WebSocket should connect to ws://..../[example].cmd. 6 | 7 | http://en.wikipedia.org/wiki/Windows_Script_Host 8 | 9 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/swift/greeter.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xcrun -sdk macosx swift 2 | 3 | import Foundation 4 | 5 | while(true){ 6 | var stdin = NSFileHandle.fileHandleWithStandardInput().availableData 7 | var line = NSString(data: stdin, encoding: NSUTF8StringEncoding)! 8 | var name = line.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) 9 | print("Hello \(name)!") 10 | fflush(__stdoutp) 11 | } 12 | -------------------------------------------------------------------------------- /examples/windows-vbscript/README.txt: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in VBScript 2 | that can be run using Windows Script Hosting. 3 | 4 | Note that each .vbs file, also requires a .cmd file to launch it. 5 | The WebSocket should connect to ws://..../[example].cmd. 6 | 7 | http://en.wikipedia.org/wiki/Windows_Script_Host 8 | 9 | You can also test the command files by running from the command line. -------------------------------------------------------------------------------- /examples/c#/Count/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | 5 | namespace Count 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | foreach (var i in Enumerable.Range(1, 10)) 12 | { 13 | Console.WriteLine(i); 14 | Thread.Sleep(1000); 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /examples/windows-vbscript/greeter.vbs: -------------------------------------------------------------------------------- 1 | ' Copyright 2013 Joe Walnes and the websocketd team. 2 | ' All rights reserved. 3 | ' Use of this source code is governed by a BSD-style 4 | ' license that can be found in the LICENSE file. 5 | 6 | 7 | ' For each line FOO received on STDIN, respond with "Hello FOO!". 8 | while true 9 | line = WScript.stdIn.readLine 10 | WScript.echo "Hello " & line & "!" 11 | wend 12 | -------------------------------------------------------------------------------- /examples/windows-jscript/greeter.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | 7 | // For each line FOO received on STDIN, respond with "Hello FOO!". 8 | while (true) { 9 | var input= WScript.stdIn.readLine(); 10 | WScript.echo('Hello ' + input+ '!'); 11 | } 12 | -------------------------------------------------------------------------------- /examples/ruby/count.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Autoflush output 9 | STDOUT.sync = true 10 | 11 | # Simple example script that counts to 10 at ~2Hz, then stops. 12 | (1..10).each do |count| 13 | puts count 14 | sleep(0.5) 15 | end 16 | -------------------------------------------------------------------------------- /examples/php/count.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | -------------------------------------------------------------------------------- /examples/rust/greeter.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | // For each line FOO received on STDIN, respond with "Hello FOO!". 4 | fn main() { 5 | loop { 6 | let mut msg = String::new(); 7 | io::stdin() 8 | .read_line(&mut msg) 9 | .expect("Failed to read line"); 10 | let msg = msg.trim(); 11 | println!("Hello {}!", msg); 12 | io::stdout().flush().ok().expect("Could not flush stdout"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/swift/README.md: -------------------------------------------------------------------------------- 1 | ## Swift examples 2 | 3 | ### Count 4 | 5 | Run the following line and open "html/count.html" from the websocketd examples directory. 6 | 7 | ``` 8 | $ websocketd --port=8080 count.swift 9 | ``` 10 | 11 | ### Greeter 12 | 13 | Run the following line and open "http://localhost:8080" in your browser to interact with the greeter server. 14 | 15 | ``` 16 | $ websocketd --port=8080 --devconsole greeter.swift 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/ruby/greeter.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Autoflush output 9 | STDOUT.sync = true 10 | 11 | # For each line FOO received on STDIN, respond with "Hello FOO!". 12 | while 1 13 | line = STDIN.readline.strip 14 | puts "Hello #{line}!" 15 | end 16 | -------------------------------------------------------------------------------- /examples/php/greeter.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | -------------------------------------------------------------------------------- /examples/python/greeter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | from sys import stdin, stdout 9 | 10 | # For each line FOO received on STDIN, respond with "Hello FOO!". 11 | while True: 12 | line = stdin.readline().strip() 13 | print('Hello %s!' % line) 14 | stdout.flush() # Remember to flush 15 | -------------------------------------------------------------------------------- /examples/java/Echo/Echo.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | public class Echo { 3 | public static void main(String[] args) { 4 | while(true) { 5 | try { 6 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 7 | String message = in.readLine(); 8 | System.out.println(message); 9 | } catch (IOException e) { 10 | e.printStackTrace(); 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /examples/perl/greeter.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | use strict; 9 | 10 | # Autoflush output 11 | use IO::Handle; 12 | STDOUT->autoflush(1); 13 | 14 | # For each line FOO received on STDIN, respond with "Hello FOO!". 15 | while (<>) { 16 | chomp; # remove \n 17 | print "Hello $_!\n"; 18 | } 19 | -------------------------------------------------------------------------------- /examples/python/count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | from sys import stdout 9 | from time import sleep 10 | 11 | # Simple example script that counts to 10 at ~2Hz, then stops. 12 | for count in range(0, 10): 13 | print(count + 1) 14 | stdout.flush() # Remember to flush 15 | sleep(0.5) 16 | -------------------------------------------------------------------------------- /examples/perl/count.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | use strict; 9 | 10 | # Autoflush output 11 | use IO::Handle; 12 | STDOUT->autoflush(1); 13 | 14 | use Time::HiRes qw(sleep); 15 | 16 | # Simple example script that counts to 10 at ~2Hz, then stops. 17 | for my $count (1 .. 10) { 18 | print "$count\n"; 19 | sleep 0.5; 20 | } 21 | -------------------------------------------------------------------------------- /examples/hack/README.md: -------------------------------------------------------------------------------- 1 | This examples directory shows some examples written in [Hack](https://hacklang.org). 2 | 3 | ### Requirements : 4 | 5 | - [HHVM](https://github.com/facebook/hhvm) 3.30+ 6 | - [HSL ( Hack Standard Library )](https://github.com/hhvm/hsl) 3.30+ 7 | - [HSL Experimental](https://github.com/hhvm/hsl-experimental) 3.30+ 8 | 9 | You can also test the command files by running from the command line : 10 | 11 | ``` 12 | $ hhvm count.hh 13 | 1 14 | 2 15 | 3 16 | 4 17 | 5 18 | 6 19 | 7 20 | 8 21 | 9 22 | 10 23 | ``` 24 | -------------------------------------------------------------------------------- /examples/java/README.md: -------------------------------------------------------------------------------- 1 | ## Running the examples on Mac 2 | 3 | 1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your PATH 4 | 2. Echo Server: Run `websocketd --port=8080 --devconsole ./echo.sh` and then go to http://localhost:8080 to interact with it 5 | 3. Count Server: Run `websocketd --port=8080 ./count.sh` to start the server, then go to [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browser -------------------------------------------------------------------------------- /examples/c#/README.md: -------------------------------------------------------------------------------- 1 | ## Running the examples on Windows 2 | 3 | 1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH) 4 | 2. open and build the **Examples.sln** solution 5 | 3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it 6 | 4. double click the **run_count.cmd** to start the count example, go to the [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browser -------------------------------------------------------------------------------- /examples/f#/README.md: -------------------------------------------------------------------------------- 1 | ## Running the examples on Windows 2 | 3 | 1. [download and install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd (don't forget to add to your PATH) 4 | 2. open and build the **Examples.sln** solution 5 | 3. double click the **run_echo.cmd** to start an echo example, go to http://localhost:8080 to interact with it 6 | 4. double click the **run_count.cmd** to start the count example, go to the [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) folder and double click **count.html** to open in a browser -------------------------------------------------------------------------------- /examples/hack/count.hh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/hhvm 2 | > 10 | async function count_to_ten(): Awaitable { 11 | $output = request_output(); 12 | for ($count = 1; $count <= 10; $count++) { 13 | await $output->writeAsync( 14 | Str\format("%d\n",$count) 15 | ); 16 | 17 | HH\Asio\usleep(500000); 18 | } 19 | 20 | // flush output 21 | await $output->flushAsync(); 22 | 23 | exit(0); 24 | } 25 | -------------------------------------------------------------------------------- /examples/hack/greeter.hh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/hhvm 2 | > 8 | async function greeter(): Awaitable { 9 | // For each line FOO received on STDIN, respond with "Hello FOO!". 10 | $input = IO\request_input(); 11 | $output = IO\request_output(); 12 | while(!$input->isEndOfFile()) { 13 | await $ouput->writeAsync( 14 | Str\format("Hello %s!\n", await $input->readLineAsync()) 15 | ); 16 | } 17 | 18 | // flush output 19 | await $output->flushAsync(); 20 | 21 | exit(0); 22 | } 23 | -------------------------------------------------------------------------------- /examples/haskell/README.md: -------------------------------------------------------------------------------- 1 | ## Haskell examples 2 | 3 | ### Count 4 | 5 | Start the server with 6 | 7 | ``` 8 | $ websocketd --port=8080 --devconsole --passenv PATH ./count.hs 9 | ``` 10 | 11 | The passing of `PATH` was required for me because a typical Haskell installation of `runhaskell` does not go into `/usr/bin` but more like `/usr/local/bin`. 12 | 13 | ### Greeter 14 | 15 | The greeter server waits for a line of text to be sent, then sends back a greeting in response, and continues to wait for more lines to come. 16 | 17 | ``` 18 | $ websocketd --port=8080 --devconsole --passenv PATH ./greeter.hs 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/nodejs/README.md: -------------------------------------------------------------------------------- 1 | ## Running the examples on Mac 2 | 3 | ##### 1. Download 4 | 5 | [Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`. 6 | 7 | ##### 2. Start a server: getter 8 | 9 | Run `websocketd --port=8080 --devconsole node greeter.js` and then go to `http://localhost:8080` to interact with it 10 | 11 | ##### 3. Start a server: counter 12 | 13 | Run `websocketd --port=8080 node count.js` to start the server, then go to [examples/html](https://github.com/joewalnes/websocketd/tree/master/examples/html) directory and double click **count.html** to open in a browser 14 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "runtime" 11 | ) 12 | 13 | // This value can be set for releases at build time using: 14 | // go {build|run} -ldflags "-X main.version 1.2.3 -X main.buildinfo timestamp-@githubuser-platform". 15 | // If unset, Version() shall return "DEVBUILD". 16 | var version string = "DEVBUILD" 17 | var buildinfo string = "--" 18 | 19 | func Version() string { 20 | return fmt.Sprintf("%s (%s %s-%s) %s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH, buildinfo) 21 | } 22 | -------------------------------------------------------------------------------- /examples/f#/Count/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/f#/Echo/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /libwebsocketd/endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | type Endpoint interface { 9 | StartReading() 10 | Terminate() 11 | Output() chan []byte 12 | Send([]byte) bool 13 | } 14 | 15 | func PipeEndpoints(e1, e2 Endpoint) { 16 | e1.StartReading() 17 | e2.StartReading() 18 | 19 | defer e1.Terminate() 20 | defer e2.Terminate() 21 | for { 22 | select { 23 | case msgOne, ok := <-e1.Output(): 24 | if !ok || !e2.Send(msgOne) { 25 | return 26 | } 27 | case msgTwo, ok := <-e2.Output(): 28 | if !ok || !e1.Send(msgTwo) { 29 | return 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/html/count.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | websocketd count example 5 | 13 | 14 | 15 | 16 |
17 | 18 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/bash/chat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2013 Jeroen Janssens 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Run a simple chat server: websocketd --devconsole --port 8080 ./chat.sh 9 | # 10 | # Please note that this example requires GNU tail, which is not the default 11 | # tail on OS X. Even though this script properly escapes the variables, 12 | # please keep in mind that it is in general a bad idea to read 13 | # untrusted data into variables and pass this onto the command line. 14 | 15 | echo "Please enter your name:"; read USER 16 | echo "[$(date)] ${USER} joined the chat" >> chat.log 17 | echo "[$(date)] Welcome to the chat ${USER}!" 18 | tail -n 0 -f chat.log --pid=$$ | grep --line-buffered -v "] ${USER}>" & 19 | while read MSG; do echo "[$(date)] ${USER}> ${MSG}" >> chat.log; done 20 | -------------------------------------------------------------------------------- /examples/bash/dump-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Standard CGI(ish) environment variables, as defined in 9 | # http://tools.ietf.org/html/rfc3875 10 | NAMES=""" 11 | AUTH_TYPE 12 | CONTENT_LENGTH 13 | CONTENT_TYPE 14 | GATEWAY_INTERFACE 15 | PATH_INFO 16 | PATH_TRANSLATED 17 | QUERY_STRING 18 | REMOTE_ADDR 19 | REMOTE_HOST 20 | REMOTE_IDENT 21 | REMOTE_PORT 22 | REMOTE_USER 23 | REQUEST_METHOD 24 | REQUEST_URI 25 | SCRIPT_NAME 26 | SERVER_NAME 27 | SERVER_PORT 28 | SERVER_PROTOCOL 29 | SERVER_SOFTWARE 30 | UNIQUE_ID 31 | HTTPS 32 | """ 33 | 34 | for NAME in ${NAMES} 35 | do 36 | echo "${NAME}=${!NAME:-}" 37 | done 38 | 39 | # Additional HTTP headers 40 | env | grep '^HTTP_' 41 | -------------------------------------------------------------------------------- /examples/rust/dump-env.rs: -------------------------------------------------------------------------------- 1 | // Standard CGI(ish) environment variables, as defined in 2 | // http://tools.ietf.org/html/rfc3875 3 | 4 | use std::env; 5 | 6 | const NAMES: &'static [&'static str] = &[ 7 | "AUTH_TYPE", 8 | "CONTENT_LENGTH", 9 | "CONTENT_TYPE", 10 | "GATEWAY_INTERFACE", 11 | "PATH_INFO", 12 | "PATH_TRANSLATED", 13 | "QUERY_STRING", 14 | "REMOTE_ADDR", 15 | "REMOTE_HOST", 16 | "REMOTE_IDENT", 17 | "REMOTE_PORT", 18 | "REMOTE_USER", 19 | "REQUEST_METHOD", 20 | "REQUEST_URI", 21 | "SCRIPT_NAME", 22 | "SERVER_NAME", 23 | "SERVER_PORT", 24 | "SERVER_PROTOCOL", 25 | "SERVER_SOFTWARE", 26 | "UNIQUE_ID", 27 | "HTTPS", 28 | ]; 29 | 30 | fn main() { 31 | for key in NAMES { 32 | let value = env::var(key).unwrap_or(String::from("")); 33 | println!("{}={}", key, value); 34 | } 35 | for (key, value) in env::vars() { 36 | if key.starts_with("HTTP_") { 37 | println!("{}={}", key, value); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/cgi-bin/dump-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Standard CGI(ish) environment variables, as defined in 9 | # http://tools.ietf.org/html/rfc3875 10 | NAMES=""" 11 | AUTH_TYPE 12 | CONTENT_LENGTH 13 | CONTENT_TYPE 14 | GATEWAY_INTERFACE 15 | PATH_INFO 16 | PATH_TRANSLATED 17 | QUERY_STRING 18 | REMOTE_ADDR 19 | REMOTE_HOST 20 | REMOTE_IDENT 21 | REMOTE_PORT 22 | REMOTE_USER 23 | REQUEST_METHOD 24 | REQUEST_URI 25 | SCRIPT_NAME 26 | SERVER_NAME 27 | SERVER_PORT 28 | SERVER_PROTOCOL 29 | SERVER_SOFTWARE 30 | UNIQUE_ID 31 | HTTPS 32 | """ 33 | 34 | echo "Content-type: text/plain" 35 | echo 36 | 37 | for NAME in ${NAMES} 38 | do 39 | eval "value=\${${NAME}}" 40 | env -i "${NAME}=${value:-}" 41 | done 42 | 43 | # Additional HTTP headers 44 | env | egrep '^(HTTP|SSL)_' 45 | -------------------------------------------------------------------------------- /libwebsocketd/launcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "io" 10 | "os/exec" 11 | ) 12 | 13 | type LaunchedProcess struct { 14 | cmd *exec.Cmd 15 | stdin io.WriteCloser 16 | stdout io.ReadCloser 17 | stderr io.ReadCloser 18 | } 19 | 20 | func launchCmd(commandName string, commandArgs []string, env []string) (*LaunchedProcess, error) { 21 | cmd := exec.Command(commandName, commandArgs...) 22 | cmd.Env = env 23 | 24 | stdout, err := cmd.StdoutPipe() 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | stderr, err := cmd.StderrPipe() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | stdin, err := cmd.StdinPipe() 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | err = cmd.Start() 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return &LaunchedProcess{cmd, stdin, stdout, stderr}, err 45 | } 46 | -------------------------------------------------------------------------------- /examples/ruby/dump-env.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | # Autoflush output 9 | STDOUT.sync = true 10 | 11 | # Standard CGI(ish) environment variables, as defined in 12 | # http://tools.ietf.org/html/rfc3875 13 | names = [ 14 | 'AUTH_TYPE', 15 | 'CONTENT_LENGTH', 16 | 'CONTENT_TYPE', 17 | 'GATEWAY_INTERFACE', 18 | 'PATH_INFO', 19 | 'PATH_TRANSLATED', 20 | 'QUERY_STRING', 21 | 'REMOTE_ADDR', 22 | 'REMOTE_HOST', 23 | 'REMOTE_IDENT', 24 | 'REMOTE_PORT', 25 | 'REMOTE_USER', 26 | 'REQUEST_METHOD', 27 | 'REQUEST_URI', 28 | 'SCRIPT_NAME', 29 | 'SERVER_NAME', 30 | 'SERVER_PORT', 31 | 'SERVER_PROTOCOL', 32 | 'SERVER_SOFTWARE', 33 | 'UNIQUE_ID', 34 | 'HTTPS', 35 | ] 36 | 37 | names.each do |name| 38 | value = ENV[name] || '' 39 | puts "#{name}=#{value}" 40 | end 41 | 42 | # Additional HTTP headers 43 | ENV.each do |name,value| 44 | puts "#{name}=#{value}" if name.start_with?('HTTP_') 45 | end 46 | -------------------------------------------------------------------------------- /examples/perl/dump-env.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | use strict; 9 | 10 | # Autoflush output 11 | use IO::Handle; 12 | STDOUT->autoflush(1); 13 | 14 | # Standard CGI(ish) environment variables, as defined in 15 | # http://tools.ietf.org/html/rfc3875 16 | my @names = qw( 17 | AUTH_TYPE 18 | CONTENT_LENGTH 19 | CONTENT_TYPE 20 | GATEWAY_INTERFACE 21 | PATH_INFO 22 | PATH_TRANSLATED 23 | QUERY_STRING 24 | REMOTE_ADDR 25 | REMOTE_HOST 26 | REMOTE_IDENT 27 | REMOTE_PORT 28 | REMOTE_USER 29 | REQUEST_METHOD 30 | REQUEST_URI 31 | SCRIPT_NAME 32 | SERVER_NAME 33 | SERVER_PORT 34 | SERVER_PROTOCOL 35 | SERVER_SOFTWARE 36 | UNIQUE_ID 37 | HTTPS 38 | ); 39 | 40 | for my $name (@names) { 41 | my $value = $ENV{$name} || ''; 42 | print "$name=$value\n"; 43 | } 44 | 45 | # Additional HTTP headers 46 | for my $name (keys(%ENV)) { 47 | if ($name =~ /^HTTP_/) { 48 | my $value = $ENV{$name}; 49 | print "$name=$value\n"; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/php/dump-env.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | '; 37 | echo $name . '=' . $value . "\n"; 38 | } 39 | 40 | // Additional HTTP headers 41 | foreach ($_SERVER as $name => $value) { 42 | if (strpos($name, 'HTTP_') === 0) { 43 | echo $name . '=' . $value . "\n"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/python/dump-env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013 Joe Walnes and the websocketd team. 4 | # All rights reserved. 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file. 7 | 8 | import os 9 | from sys import stdout 10 | 11 | # Standard CGI(ish) environment variables, as defined in 12 | # http://tools.ietf.org/html/rfc3875 13 | var_names = [ 14 | 'AUTH_TYPE', 15 | 'CONTENT_LENGTH', 16 | 'CONTENT_TYPE', 17 | 'GATEWAY_INTERFACE', 18 | 'PATH_INFO', 19 | 'PATH_TRANSLATED', 20 | 'QUERY_STRING', 21 | 'REMOTE_ADDR', 22 | 'REMOTE_HOST', 23 | 'REMOTE_IDENT', 24 | 'REMOTE_PORT', 25 | 'REMOTE_USER', 26 | 'REQUEST_METHOD', 27 | 'REQUEST_URI', 28 | 'SCRIPT_NAME', 29 | 'SERVER_NAME', 30 | 'SERVER_PORT', 31 | 'SERVER_PROTOCOL', 32 | 'SERVER_SOFTWARE', 33 | 'UNIQUE_ID', 34 | 'HTTPS' 35 | ] 36 | for var_name in var_names: 37 | print('%s=%s' % (var_name, os.environ.get(var_name, ''))) 38 | stdout.flush() # Remember to flush 39 | 40 | # Additional HTTP headers 41 | for var_name in os.environ: 42 | if var_name.startswith('HTTP_'): 43 | print('%s=%s' % (var_name, os.environ[var_name])) 44 | stdout.flush() # Remember to flush 45 | -------------------------------------------------------------------------------- /examples/windows-vbscript/dump-env.vbs: -------------------------------------------------------------------------------- 1 | ' Copyright 2013 Joe Walnes and the websocketd team. 2 | ' All rights reserved. 3 | ' Use of this source code is governed by a BSD-style 4 | ' license that can be found in the LICENSE file. 5 | 6 | 7 | ' Standard CGI(ish) environment variables, as defined in 8 | ' http://tools.ietf.org/html/rfc3875 9 | names = Array(_ 10 | "AUTH_TYPE", _ 11 | "CONTENT_LENGTH", _ 12 | "CONTENT_TYPE", _ 13 | "GATEWAY_INTERFACE", _ 14 | "PATH_INFO", _ 15 | "PATH_TRANSLATED", _ 16 | "QUERY_STRING", _ 17 | "REMOTE_ADDR", _ 18 | "REMOTE_HOST", _ 19 | "REMOTE_IDENT", _ 20 | "REMOTE_PORT", _ 21 | "REMOTE_USER", _ 22 | "REQUEST_METHOD", _ 23 | "REQUEST_URI", _ 24 | "SCRIPT_NAME", _ 25 | "SERVER_NAME", _ 26 | "SERVER_PORT", _ 27 | "SERVER_PROTOCOL", _ 28 | "SERVER_SOFTWARE", _ 29 | "UNIQUE_ID", _ 30 | "HTTPS"_ 31 | ) 32 | 33 | set shell = WScript.CreateObject("WScript.Shell") 34 | set env = shell.Environment("PROCESS") 35 | 36 | for each name in names 37 | value = env(name) 38 | if value = "" then 39 | value = "" 40 | end if 41 | WScript.echo name & "=" & value 42 | next 43 | 44 | for each item in env 45 | if instr(1, item, "HTTP_", 1) = 1 then 46 | WScript.Echo item 47 | end if 48 | next 49 | -------------------------------------------------------------------------------- /examples/windows-jscript/dump-env.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | 7 | // Standard CGI(ish) environment variables, as defined in 8 | // http://tools.ietf.org/html/rfc3875 9 | var names = [ 10 | 'AUTH_TYPE', 11 | 'CONTENT_LENGTH', 12 | 'CONTENT_TYPE', 13 | 'GATEWAY_INTERFACE', 14 | 'PATH_INFO', 15 | 'PATH_TRANSLATED', 16 | 'QUERY_STRING', 17 | 'REMOTE_ADDR', 18 | 'REMOTE_HOST', 19 | 'REMOTE_IDENT', 20 | 'REMOTE_PORT', 21 | 'REMOTE_USER', 22 | 'REQUEST_METHOD', 23 | 'REQUEST_URI', 24 | 'SCRIPT_NAME', 25 | 'SERVER_NAME', 26 | 'SERVER_PORT', 27 | 'SERVER_PROTOCOL', 28 | 'SERVER_SOFTWARE', 29 | 'UNIQUE_ID', 30 | 'HTTPS' 31 | ]; 32 | 33 | var shell = WScript.CreateObject("WScript.Shell"); 34 | var env = shell.Environment('PROCESS'); 35 | 36 | for (var i = 0; i < names.length; i++) { 37 | var name = names[i]; 38 | var value = env(name) || ''; 39 | WScript.echo(name + '=' + value); 40 | } 41 | 42 | for(var en = new Enumerator(env); !en.atEnd(); en.moveNext()) { 43 | var item = en.item(); 44 | if (item.indexOf('HTTP_') == 0) { 45 | WScript.Echo(item); 46 | } 47 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Joe Walnes and the websocketd authors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /examples/c#/Examples.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Echo", "Echo\Echo.csproj", "{65414388-1058-414C-910F-CBD58E2B064A}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Count", "Count\Count.csproj", "{FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {65414388-1058-414C-910F-CBD58E2B064A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {65414388-1058-414C-910F-CBD58E2B064A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {FD04ED82-2C8C-4D5C-9327-FB73482DD3D1}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /examples/f#/Examples.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Echo", "Echo\Echo.fsproj", "{6F680332-CAA0-447B-A87E-AF272DED5701}" 5 | EndProject 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Count", "Count\Count.fsproj", "{367C65FD-485B-45C0-9C0E-7CE455951EAE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6F680332-CAA0-447B-A87E-AF272DED5701}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6F680332-CAA0-447B-A87E-AF272DED5701}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {367C65FD-485B-45C0-9C0E-7CE455951EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {367C65FD-485B-45C0-9C0E-7CE455951EAE}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /examples/c#/Echo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Echo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyProduct("Echo")] 12 | [assembly: AssemblyTrademark("")] 13 | [assembly: AssemblyCulture("")] 14 | 15 | // Setting ComVisible to false makes the types in this assembly not visible 16 | // to COM components. If you need to access a type in this assembly from 17 | // COM, set the ComVisible attribute to true on that type. 18 | [assembly: ComVisible(false)] 19 | 20 | // The following GUID is for the ID of the typelib if this project is exposed to COM 21 | [assembly: Guid("6199e327-f4cd-438c-a6c5-87861f837fb1")] 22 | 23 | // Version information for an assembly consists of the following four values: 24 | // 25 | // Major Version 26 | // Minor Version 27 | // Build Number 28 | // Revision 29 | // 30 | // You can specify all the values or you can default the Build and Revision Numbers 31 | // by using the '*' as shown below: 32 | // [assembly: AssemblyVersion("1.0.*")] 33 | [assembly: AssemblyVersion("1.0.0.0")] 34 | [assembly: AssemblyFileVersion("1.0.0.0")] 35 | -------------------------------------------------------------------------------- /examples/c#/Count/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Count")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyProduct("Count")] 12 | [assembly: AssemblyTrademark("")] 13 | [assembly: AssemblyCulture("")] 14 | 15 | // Setting ComVisible to false makes the types in this assembly not visible 16 | // to COM components. If you need to access a type in this assembly from 17 | // COM, set the ComVisible attribute to true on that type. 18 | [assembly: ComVisible(false)] 19 | 20 | // The following GUID is for the ID of the typelib if this project is exposed to COM 21 | [assembly: Guid("38cb6838-4839-498f-b09f-d0e67a2e9974")] 22 | 23 | // Version information for an assembly consists of the following four values: 24 | // 25 | // Major Version 26 | // Minor Version 27 | // Build Number 28 | // Revision 29 | // 30 | // You can specify all the values or you can default the Build and Revision Numbers 31 | // by using the '*' as shown below: 32 | // [assembly: AssemblyVersion("1.0.*")] 33 | [assembly: AssemblyVersion("1.0.0.0")] 34 | [assembly: AssemblyFileVersion("1.0.0.0")] 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Joe Walnes and the websocketd team. 2 | # All rights reserved. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | 6 | # Self contained Go build file that will download and install (locally) the correct 7 | # version of Go, and build our programs. Go does not need to be installed on the 8 | # system (and if it already is, it will be ignored). 9 | 10 | # To manually invoke the locally installed Go, use ./go 11 | 12 | # Go installation config. 13 | GO_VER=1.11.5 14 | SYSTEM_NAME:=$(shell uname -s | tr '[:upper:]' '[:lower:]') 15 | SYSTEM_ARCH:=$(shell uname -m) 16 | GO_ARCH:=$(if $(filter x86_64, $(SYSTEM_ARCH)),amd64,386) 17 | GO_VERSION:=$(GO_VER).$(SYSTEM_NAME)-$(GO_ARCH) 18 | GO_DOWNLOAD_URL:=https://dl.google.com/go/go$(GO_VERSION).tar.gz 19 | GO_DIR:=go-$(GO_VER) 20 | 21 | # Build websocketd binary 22 | websocketd: $(GO_DIR)/bin/go $(wildcard *.go) $(wildcard libwebsocketd/*.go) 23 | $(GO_DIR)/bin/go build 24 | 25 | localgo: $(GO_DIR)/bin/go 26 | 27 | # Download and unpack Go distribution. 28 | $(GO_DIR)/bin/go: 29 | mkdir -p $(GO_DIR) 30 | rm -f $@ 31 | @echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR) 32 | curl -s $(GO_DOWNLOAD_URL) | tar xfz - --strip-components=1 -C $(GO_DIR) 33 | 34 | # Clean up binary 35 | clean: 36 | rm -rf websocketd 37 | 38 | .PHONY: clean 39 | 40 | # Also clean up downloaded Go 41 | clobber: clean 42 | rm -rf $(wildcard go-v*) 43 | 44 | .PHONY: clobber 45 | -------------------------------------------------------------------------------- /examples/hack/dump-env.hh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/hhvm 2 | > 8 | async function dumpEnv(): Awaitable { 9 | // Standard CGI(ish) environment variables, as defined in 10 | // http://tools.ietf.org/html/rfc3875 11 | $names = keyset[ 12 | 'AUTH_TYPE', 13 | 'CONTENT_LENGTH', 14 | 'CONTENT_TYPE', 15 | 'GATEWAY_INTERFACE', 16 | 'PATH_INFO', 17 | 'PATH_TRANSLATED', 18 | 'QUERY_STRING', 19 | 'REMOTE_ADDR', 20 | 'REMOTE_HOST', 21 | 'REMOTE_IDENT', 22 | 'REMOTE_PORT', 23 | 'REMOTE_USER', 24 | 'REQUEST_METHOD', 25 | 'REQUEST_URI', 26 | 'SCRIPT_NAME', 27 | 'SERVER_NAME', 28 | 'SERVER_PORT', 29 | 'SERVER_PROTOCOL', 30 | 'SERVER_SOFTWARE', 31 | 'UNIQUE_ID', 32 | 'HTTPS' 33 | ]; 34 | 35 | /* HH_IGNORE_ERROR[2050] using global variable */ 36 | $server = dict($_SERVER); 37 | 38 | $ouput = request_output(); 39 | 40 | foreach($names as $name) { 41 | await $output->writeAsync( 42 | Str\format("%s = %s\n", $name, $server[$name] ?? '') 43 | ); 44 | } 45 | 46 | // Additional HTTP headers 47 | foreach($server as $k => $v) { 48 | if ($k is string && Str\starts_with($k, 'HTTP_')) { 49 | await $output->writeAsync( 50 | Str\format("%s = %s\n", $k, $v as string) 51 | ); 52 | } 53 | } 54 | 55 | // flush output 56 | await $output->flushAsync(); 57 | 58 | exit(0); 59 | } 60 | -------------------------------------------------------------------------------- /examples/lua/README.md: -------------------------------------------------------------------------------- 1 | The examples demonstrate the use of websocketd with lua. There are two examples in the directory both very basic. 2 | 3 | 1. Greeter.lua simply echos back any input made from the client 4 | 2. json_ws.lua echos back any input from the client *after* converting it into a json string 5 | 6 | It is pretty simple to extend these examples into full fledged applications. All you need is an stdin input loop 7 | 8 | ``` 9 | local input = io.stdin:read() 10 | 11 | while input do 12 | -- do anything here 13 | 14 | -- update the input 15 | input = io.stdin:read() 16 | 17 | end 18 | 19 | 20 | ``` 21 | 22 | any thing you `print` goes out to the websocket client 23 | 24 | Libraries and third party modules can be used by the standard `require` statement in lua. 25 | 26 | ## Running the examples 27 | 28 | 29 | 30 | ##### 1. Download 31 | 32 | [Install](https://github.com/joewalnes/websocketd/wiki/Download-and-install) websocketd and add it to your `PATH`. 33 | 34 | ##### 2. Start a server: greeter 35 | 36 | Run `websocketd --port=8080 --devconsole lua ./greeter.lua` and then go to `http://localhost:8080` to interact with it 37 | 38 | ##### 3. Start a server: json_ws 39 | 40 | Run `websocketd --port=8080 --devconsole lua ./json_ws.lua` and then go to `http://localhost:8080` to interact with it 41 | 42 | If you are using luajit instead of lua you may run the examples like this 43 | (this assumes that you've got luajit in your path) 44 | 45 | `websocketd --port=8080 --devconsole luajit ./json_ws.lua` and then go to `http://localhost:8080` -------------------------------------------------------------------------------- /libwebsocketd/license.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | const ( 9 | license = ` 10 | Copyright (c) 2013, Joe Walnes and the websocketd authors. 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | 16 | 1. Redistributions of source code must retain the above copyright notice, this 17 | list of conditions and the following disclaimer. 18 | 2. Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 29 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | ` 33 | License = license 34 | ) 35 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Version 0.4.1 (Jan 24, 2021) 2 | 3 | * Minor changes only 4 | * Updated to Go 1.15.7 5 | 6 | Version 0.3.1 (Jan 28, 2019) 7 | 8 | * Minor improvements to websocketd itself 9 | * Use of go modules, gorilla websockets set to 1.4.0 10 | * Binaries build code switched to 1.11.5 (improving underlying protocol handlers) 11 | 12 | Version 0.3.0 (??, 2017) 13 | 14 | * Migration of underlying websocket server to Gorilla Websocket lib. 15 | * Binaries build code switched to 1.9.2 16 | 17 | Version 0.2.12 (Feb 17, 2016) 18 | 19 | * Update of underlying go standard libraries change how SSL works. SSL3 is no longer supported. 20 | * Support of commands that do not provide text IO (using them as binary websocket frames) 21 | * Minor changes in examples and --help output 22 | 23 | Version 0.2.11 (Jul 1, 2015) 24 | 25 | * PATH env variable is now passed to process by default 26 | * new --header* flags could generate custom HTTP headers for all websocketd-generated answers 27 | * fixed bug causing process to hang when WebSockets client disconnect is detected 28 | * minor changes for console app (default url building logic and tab char printing) 29 | * multiple changes of examples. 30 | 31 | 32 | Version 0.2.10 (Feb 16, 2015) 33 | 34 | * fixes for null-origin situations (#75, #96) 35 | * better bash examples (#103) 36 | * changelog and checksums for released files (#101, #105) 37 | 38 | 39 | Version 0.2.9 (May 19, 2014) 40 | 41 | * ability to listen multiple IP addresses (#40, #43) 42 | * proper support for TLS (#17) 43 | * resource limits enforcement (a.k.a. maxforks feature, #46) 44 | * passenv option to limit environment variables visible by running commands (#4) 45 | * fix for problem of closing upgraded websocket connection when script is not found (#29) 46 | * websocket origin restrictions via command line option (#20) 47 | * minor update for help flag behavior 48 | * minor fix for devconsole 49 | 50 | Version 0.2.8 (Jan 11, 2014) 51 | 52 | * ... -------------------------------------------------------------------------------- /examples/c#/.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | build/ 20 | test/ 21 | deploy/ 22 | x64/ 23 | *_i.c 24 | *_p.c 25 | *.ilk 26 | *.meta 27 | *.obj 28 | *.pch 29 | *.pdb 30 | *.pgc 31 | *.pgd 32 | *.rsp 33 | *.sbr 34 | *.tlb 35 | *.tli 36 | *.tlh 37 | *.tmp 38 | *.vspscc 39 | *.vssscc 40 | .builds 41 | 42 | # Visual C++ cache files 43 | ipch/ 44 | *.aps 45 | *.ncb 46 | *.opensdf 47 | *.sdf 48 | 49 | # Visual Studio profiler 50 | *.psess 51 | *.vsp 52 | 53 | # Guidance Automation Toolkit 54 | *.gpState 55 | 56 | # ReSharper is a .NET coding add-in 57 | _ReSharper* 58 | 59 | # NCrunch 60 | *ncrunch*/* 61 | *.ncrunch* 62 | .*crunch*.local.xml 63 | 64 | # Installshield output folder 65 | [Ee]xpress 66 | 67 | # DocProject is a documentation generator add-in 68 | DocProject/buildhelp/ 69 | DocProject/Help/*.HxT 70 | DocProject/Help/*.HxC 71 | DocProject/Help/*.hhc 72 | DocProject/Help/*.hhk 73 | DocProject/Help/*.hhp 74 | DocProject/Help/Html2 75 | DocProject/Help/html 76 | 77 | # Click-Once directory 78 | publish 79 | 80 | # Publish Web Output 81 | *.Publish.xml 82 | 83 | # Windows Azure Build Output 84 | csx 85 | *.build.csdef 86 | 87 | # Others 88 | [Bb]in 89 | [Oo]bj 90 | sql 91 | TestResults 92 | [Tt]est[Rr]esult* 93 | *.Cache 94 | ClientBin 95 | [Ss]tyle[Cc]op.* 96 | ~$* 97 | *.dbmdl 98 | Generated_Code #added for RIA/Silverlight projects 99 | 100 | # Backup & report files from converting an old project file to a newer 101 | # Visual Studio version. Backup files are not needed, because we have git ;-) 102 | _UpgradeReport_Files/ 103 | Backup*/ 104 | UpgradeLog*.XML -------------------------------------------------------------------------------- /examples/f#/.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | build/ 20 | test/ 21 | deploy/ 22 | x64/ 23 | *_i.c 24 | *_p.c 25 | *.ilk 26 | *.meta 27 | *.obj 28 | *.pch 29 | *.pdb 30 | *.pgc 31 | *.pgd 32 | *.rsp 33 | *.sbr 34 | *.tlb 35 | *.tli 36 | *.tlh 37 | *.tmp 38 | *.vspscc 39 | *.vssscc 40 | .builds 41 | 42 | # Visual C++ cache files 43 | ipch/ 44 | *.aps 45 | *.ncb 46 | *.opensdf 47 | *.sdf 48 | 49 | # Visual Studio profiler 50 | *.psess 51 | *.vsp 52 | 53 | # Guidance Automation Toolkit 54 | *.gpState 55 | 56 | # ReSharper is a .NET coding add-in 57 | _ReSharper* 58 | 59 | # NCrunch 60 | *ncrunch*/* 61 | *.ncrunch* 62 | .*crunch*.local.xml 63 | 64 | # Installshield output folder 65 | [Ee]xpress 66 | 67 | # DocProject is a documentation generator add-in 68 | DocProject/buildhelp/ 69 | DocProject/Help/*.HxT 70 | DocProject/Help/*.HxC 71 | DocProject/Help/*.hhc 72 | DocProject/Help/*.hhk 73 | DocProject/Help/*.hhp 74 | DocProject/Help/Html2 75 | DocProject/Help/html 76 | 77 | # Click-Once directory 78 | publish 79 | 80 | # Publish Web Output 81 | *.Publish.xml 82 | 83 | # Windows Azure Build Output 84 | csx 85 | *.build.csdef 86 | 87 | # Others 88 | [Bb]in 89 | [Oo]bj 90 | sql 91 | TestResults 92 | [Tt]est[Rr]esult* 93 | *.Cache 94 | ClientBin 95 | [Ss]tyle[Cc]op.* 96 | ~$* 97 | *.dbmdl 98 | Generated_Code #added for RIA/Silverlight projects 99 | 100 | # Backup & report files from converting an old project file to a newer 101 | # Visual Studio version. Backup files are not needed, because we have git ;-) 102 | _UpgradeReport_Files/ 103 | Backup*/ 104 | UpgradeLog*.XML -------------------------------------------------------------------------------- /libwebsocketd/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "time" 10 | ) 11 | 12 | type Config struct { 13 | // base initiaization fields 14 | StartupTime time.Time // Server startup time (used for dev console caching). 15 | CommandName string // Command to execute. 16 | CommandArgs []string // Additional args to pass to command. 17 | ServerSoftware string // Value to pass to SERVER_SOFTWARE environment variable (e.g. websocketd/1.2.3). 18 | CloseMs uint // Milliseconds to start sending signals 19 | 20 | HandshakeTimeout time.Duration // time to finish handshake (default 1500ms) 21 | 22 | // settings 23 | Binary bool // Use binary communication (send data in chunks they are read from process) 24 | ReverseLookup bool // Perform reverse DNS lookups on hostnames (useful, but slower). 25 | Ssl bool // websocketd works with --ssl which means TLS is in use 26 | ScriptDir string // Base directory for websocket scripts. 27 | UsingScriptDir bool // Are we running with a script dir. 28 | StaticDir string // If set, static files will be served from this dir over HTTP. 29 | CgiDir string // If set, CGI scripts will be served from this dir over HTTP. 30 | DevConsole bool // Enable dev console. This disables StaticDir and CgiDir. 31 | AllowOrigins []string // List of allowed origin addresses for websocket upgrade. 32 | SameOrigin bool // If set, requires websocket upgrades to be performed from same origin only. 33 | Headers []string 34 | HeadersWs []string 35 | HeadersHTTP []string 36 | 37 | // created environment 38 | Env []string // Additional environment variables to pass to process ("key=value"). 39 | ParentEnv []string // Variables kept from os.Environ() before sanitizing it for subprocess. 40 | } 41 | -------------------------------------------------------------------------------- /libwebsocketd/endpoint_test.go: -------------------------------------------------------------------------------- 1 | package libwebsocketd 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var eol_tests = []string{ 10 | "", "\n", "\r\n", "ok\n", "ok\n", 11 | "quite long string for our test\n", 12 | "quite long string for our test\r\n", 13 | } 14 | 15 | var eol_answers = []string{ 16 | "", "", "", "ok", "ok", 17 | "quite long string for our test", "quite long string for our test", 18 | } 19 | 20 | func TestTrimEOL(t *testing.T) { 21 | for n := 0; n < len(eol_tests); n++ { 22 | answ := trimEOL([]byte(eol_tests[n])) 23 | if string(answ) != eol_answers[n] { 24 | t.Errorf("Answer '%s' did not match predicted '%s'", answ, eol_answers[n]) 25 | } 26 | } 27 | } 28 | 29 | func BenchmarkTrimEOL(b *testing.B) { 30 | for n := 0; n < b.N; n++ { 31 | trimEOL([]byte(eol_tests[n%len(eol_tests)])) 32 | } 33 | } 34 | 35 | type TestEndpoint struct { 36 | limit int 37 | prefix string 38 | c chan []byte 39 | result []string 40 | } 41 | 42 | func (e *TestEndpoint) StartReading() { 43 | go func() { 44 | for i := 0; i < e.limit; i++ { 45 | e.c <- []byte(e.prefix + strconv.Itoa(i)) 46 | } 47 | time.Sleep(time.Millisecond) // should be enough for smaller channel to catch up with long one 48 | close(e.c) 49 | }() 50 | } 51 | 52 | func (e *TestEndpoint) Terminate() { 53 | } 54 | 55 | func (e *TestEndpoint) Output() chan []byte { 56 | return e.c 57 | } 58 | 59 | func (e *TestEndpoint) Send(msg []byte) bool { 60 | e.result = append(e.result, string(msg)) 61 | return true 62 | } 63 | 64 | func TestEndpointPipe(t *testing.T) { 65 | one := &TestEndpoint{2, "one:", make(chan []byte), make([]string, 0)} 66 | two := &TestEndpoint{4, "two:", make(chan []byte), make([]string, 0)} 67 | PipeEndpoints(one, two) 68 | if len(one.result) != 4 || len(two.result) != 2 { 69 | t.Errorf("Invalid lengths, should be 4 and 2: %v %v", one.result, two.result) 70 | } else if one.result[0] != "two:0" || two.result[0] != "one:0" { 71 | t.Errorf("Invalid first results, should be two:0 and one:0: %#v %#v", one.result[0], two.result[0]) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /release/README: -------------------------------------------------------------------------------- 1 | Release scripts for websocketd 2 | ============================== 3 | 4 | Perform a fully automated repeatable release of websocketd. 5 | 6 | This is three-stage process in normal release cycle that's performed by 7 | repository maintainers. 8 | 9 | * Update CHANGES and Tag 10 | * Build 11 | * Release 12 | 13 | Those that do not have permissions to push tags could still use this build 14 | chain to build their own customized versions or packages. 15 | 16 | ## Step 1: Update CHANGES and Tag 17 | 18 | First edit and commit CHANGES file. List all significant updates between releases. 19 | 20 | Annotated tags require for release: 21 | 22 | git tag -a vx.x.x 23 | 24 | To see currently tagged version run tag with -l option. 25 | 26 | ## Step 2: Build 27 | 28 | release/Makefile contains all required to download pre-verified for build release of Go and cross-compile 29 | websocketd for all platforms that we release for. 30 | 31 | cd release 32 | make 33 | 34 | Is generally recommended but other build options are available: 35 | 36 | * go-compile: build cross-platform golang pakages 37 | * binaries: only create websocketd binaries, do not generate zip distributions 38 | * deb, rpm: only create particular kind of packages 39 | * clean-go: remove build files for go 40 | * clean-out: remove built binaries, zip and linux packages 41 | * clean: remove everything (go and release files) 42 | 43 | Building requires to have gcc and other build tools installed. Building rpm/deb files would fail without fpm ("gem install fpm" itself requires ruby devel tools). 44 | 45 | Create CHECKSUMS.asc file using gpg and key that other developers shared with you: 46 | 47 | gpg -u KEYID --clearsign CHECKSUMS 48 | 49 | (key has to be installed in your gpg environment first) 50 | 51 | 52 | ## Step 3: Release 53 | 54 | In order to make release official following steps required: 55 | 56 | # push tags (assuming joewalnes repo is origin): 57 | go push --tags origin master:master 58 | 59 | Upload files to github release (to be automated). Use these instructions for now: https://github.com/blog/1547-release-your-software 60 | 61 | 62 | -------------------------------------------------------------------------------- /libwebsocketd/websocket_endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "io" 10 | "io/ioutil" 11 | 12 | "github.com/gorilla/websocket" 13 | ) 14 | 15 | // CONVERT GORILLA 16 | // This file should be altered to use gorilla's websocket connection type and proper 17 | // message dispatching methods 18 | 19 | type WebSocketEndpoint struct { 20 | ws *websocket.Conn 21 | output chan []byte 22 | log *LogScope 23 | mtype int 24 | } 25 | 26 | func NewWebSocketEndpoint(ws *websocket.Conn, bin bool, log *LogScope) *WebSocketEndpoint { 27 | endpoint := &WebSocketEndpoint{ 28 | ws: ws, 29 | output: make(chan []byte), 30 | log: log, 31 | mtype: websocket.TextMessage, 32 | } 33 | if bin { 34 | endpoint.mtype = websocket.BinaryMessage 35 | } 36 | return endpoint 37 | } 38 | 39 | func (we *WebSocketEndpoint) Terminate() { 40 | we.log.Trace("websocket", "Terminated websocket connection") 41 | } 42 | 43 | func (we *WebSocketEndpoint) Output() chan []byte { 44 | return we.output 45 | } 46 | 47 | func (we *WebSocketEndpoint) Send(msg []byte) bool { 48 | w, err := we.ws.NextWriter(we.mtype) 49 | if err == nil { 50 | _, err = w.Write(msg) 51 | } 52 | w.Close() // could need error handling 53 | 54 | if err != nil { 55 | we.log.Trace("websocket", "Cannot send: %s", err) 56 | return false 57 | } 58 | 59 | return true 60 | } 61 | 62 | func (we *WebSocketEndpoint) StartReading() { 63 | go we.read_frames() 64 | } 65 | 66 | func (we *WebSocketEndpoint) read_frames() { 67 | for { 68 | mtype, rd, err := we.ws.NextReader() 69 | if err != nil { 70 | we.log.Debug("websocket", "Cannot receive: %s", err) 71 | break 72 | } 73 | if mtype != we.mtype { 74 | we.log.Debug("websocket", "Received message of type that we did not expect... Ignoring...") 75 | } 76 | 77 | p, err := ioutil.ReadAll(rd) 78 | if err != nil && err != io.EOF { 79 | we.log.Debug("websocket", "Cannot read received message: %s", err) 80 | break 81 | } 82 | switch mtype { 83 | case websocket.TextMessage: 84 | we.output <- append(p, '\n') 85 | case websocket.BinaryMessage: 86 | we.output <- p 87 | default: 88 | we.log.Debug("websocket", "Received message of unknown type: %d", mtype) 89 | } 90 | } 91 | close(we.output) 92 | } 93 | -------------------------------------------------------------------------------- /examples/c#/Echo/Echo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {65414388-1058-414C-910F-CBD58E2B064A} 8 | Exe 9 | Properties 10 | Echo 11 | Echo 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | ..\bin\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | ..\bin\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /examples/c#/Count/Count.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FD04ED82-2C8C-4D5C-9327-FB73482DD3D1} 8 | Exe 9 | Properties 10 | Count 11 | Count 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | ..\bin\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | ..\bin\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /libwebsocketd/handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "io/ioutil" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | ) 14 | 15 | func TestParsePathWithScriptDir(t *testing.T) { 16 | baseDir, _ := ioutil.TempDir("", "websockets") 17 | scriptDir := filepath.Join(baseDir, "foo", "bar") 18 | scriptPath := filepath.Join(scriptDir, "baz.sh") 19 | 20 | defer os.RemoveAll(baseDir) 21 | 22 | if err := os.MkdirAll(scriptDir, os.ModePerm); err != nil { 23 | t.Error("could not create ", scriptDir) 24 | } 25 | if _, err := os.Create(scriptPath); err != nil { 26 | t.Error("could not create ", scriptPath) 27 | } 28 | 29 | config := new(Config) 30 | config.UsingScriptDir = true 31 | config.ScriptDir = baseDir 32 | 33 | var res *URLInfo 34 | var err error 35 | 36 | // simple url 37 | res, err = GetURLInfo("/foo/bar/baz.sh", config) 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | if res.ScriptPath != "/foo/bar/baz.sh" { 42 | t.Error("scriptPath") 43 | } 44 | if res.PathInfo != "" { 45 | t.Error("GetURLInfo") 46 | } 47 | if res.FilePath != scriptPath { 48 | t.Error("filePath") 49 | } 50 | 51 | // url with extra path info 52 | res, err = GetURLInfo("/foo/bar/baz.sh/some/extra/stuff", config) 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | if res.ScriptPath != "/foo/bar/baz.sh" { 57 | t.Error("scriptPath") 58 | } 59 | if res.PathInfo != "/some/extra/stuff" { 60 | t.Error("GetURLInfo") 61 | } 62 | if res.FilePath != scriptPath { 63 | t.Error("filePath") 64 | } 65 | 66 | // non-existing file 67 | _, err = GetURLInfo("/foo/bar/bang.sh", config) 68 | if err == nil { 69 | t.Error("non-existing file should fail") 70 | } 71 | if err != ScriptNotFoundError { 72 | t.Error("should fail with script not found") 73 | } 74 | 75 | // non-existing dir 76 | _, err = GetURLInfo("/hoohar/bang.sh", config) 77 | if err == nil { 78 | t.Error("non-existing dir should fail") 79 | } 80 | if err != ScriptNotFoundError { 81 | t.Error("should fail with script not found") 82 | } 83 | } 84 | 85 | func TestParsePathExplicitScript(t *testing.T) { 86 | config := new(Config) 87 | config.UsingScriptDir = false 88 | 89 | res, err := GetURLInfo("/some/path", config) 90 | if err != nil { 91 | t.Error(err) 92 | } 93 | if res.ScriptPath != "/" { 94 | t.Error("scriptPath") 95 | } 96 | if res.PathInfo != "/some/path" { 97 | t.Error("GetURLInfo") 98 | } 99 | if res.FilePath != "" { 100 | t.Error("filePath") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/f#/Echo/Echo.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 6f680332-caa0-447b-a87e-af272ded5701 9 | Exe 10 | Echo 11 | Echo 12 | v4.5 13 | Echo 14 | 15 | 16 | true 17 | full 18 | false 19 | false 20 | ..\bin 21 | DEBUG;TRACE 22 | 3 23 | AnyCPU 24 | ..\bin\Echo.XML 25 | true 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | ..\bin 32 | TRACE 33 | 3 34 | AnyCPU 35 | bin\Release\Echo.XML 36 | true 37 | 38 | 39 | 40 | 41 | True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 11 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /examples/f#/Count/Count.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 367c65fd-485b-45c0-9c0e-7ce455951eae 9 | Exe 10 | Count 11 | Count 12 | v4.5 13 | Count 14 | 15 | 16 | true 17 | full 18 | false 19 | false 20 | ..\bin\ 21 | DEBUG;TRACE 22 | 3 23 | AnyCPU 24 | ..\bin\Count.XML 25 | true 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | ..\bin\ 32 | TRACE 33 | 3 34 | AnyCPU 35 | ..\bin\Count.XML 36 | true 37 | 38 | 39 | 40 | 41 | True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 11 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /libwebsocketd/logscope.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type LogLevel int 14 | 15 | const ( 16 | LogDebug = iota 17 | LogTrace 18 | LogAccess 19 | LogInfo 20 | LogError 21 | LogFatal 22 | 23 | LogNone = 126 24 | LogUnknown = 127 25 | ) 26 | 27 | type LogFunc func(logScope *LogScope, level LogLevel, levelName string, category string, msg string, args ...interface{}) 28 | 29 | type LogScope struct { 30 | Parent *LogScope // Parent scope 31 | MinLevel LogLevel // Minimum log level to write out. 32 | Mutex *sync.Mutex // Should be shared across all LogScopes that write to the same destination. 33 | Associated []AssocPair // Additional data associated with scope 34 | LogFunc LogFunc 35 | } 36 | 37 | type AssocPair struct { 38 | Key string 39 | Value string 40 | } 41 | 42 | func (l *LogScope) Associate(key string, value string) { 43 | l.Associated = append(l.Associated, AssocPair{key, value}) 44 | } 45 | 46 | func (l *LogScope) Debug(category string, msg string, args ...interface{}) { 47 | l.LogFunc(l, LogDebug, "DEBUG", category, msg, args...) 48 | } 49 | 50 | func (l *LogScope) Trace(category string, msg string, args ...interface{}) { 51 | l.LogFunc(l, LogTrace, "TRACE", category, msg, args...) 52 | } 53 | 54 | func (l *LogScope) Access(category string, msg string, args ...interface{}) { 55 | l.LogFunc(l, LogAccess, "ACCESS", category, msg, args...) 56 | } 57 | 58 | func (l *LogScope) Info(category string, msg string, args ...interface{}) { 59 | l.LogFunc(l, LogInfo, "INFO", category, msg, args...) 60 | } 61 | 62 | func (l *LogScope) Error(category string, msg string, args ...interface{}) { 63 | l.LogFunc(l, LogError, "ERROR", category, msg, args...) 64 | } 65 | 66 | func (l *LogScope) Fatal(category string, msg string, args ...interface{}) { 67 | l.LogFunc(l, LogFatal, "FATAL", category, msg, args...) 68 | } 69 | 70 | func (parent *LogScope) NewLevel(logFunc LogFunc) *LogScope { 71 | return &LogScope{ 72 | Parent: parent, 73 | MinLevel: parent.MinLevel, 74 | Mutex: parent.Mutex, 75 | Associated: make([]AssocPair, 0), 76 | LogFunc: logFunc} 77 | } 78 | 79 | func RootLogScope(minLevel LogLevel, logFunc LogFunc) *LogScope { 80 | return &LogScope{ 81 | Parent: nil, 82 | MinLevel: minLevel, 83 | Mutex: &sync.Mutex{}, 84 | Associated: make([]AssocPair, 0), 85 | LogFunc: logFunc} 86 | } 87 | 88 | func Timestamp() string { 89 | return time.Now().Format(time.RFC1123Z) 90 | } 91 | 92 | func LevelFromString(s string) LogLevel { 93 | switch s { 94 | case "debug": 95 | return LogDebug 96 | case "trace": 97 | return LogTrace 98 | case "access": 99 | return LogAccess 100 | case "info": 101 | return LogInfo 102 | case "error": 103 | return LogError 104 | case "fatal": 105 | return LogFatal 106 | case "none": 107 | return LogNone 108 | default: 109 | return LogUnknown 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /release/websocketd.man: -------------------------------------------------------------------------------- 1 | .\" Manpage for websocketd. 2 | .\" Contact abc@alexsergeyev.com to correct errors or typos. 3 | .TH websocketd 8 "28 Sep 2014" "0.0" "websocketd man page" 4 | .SH NAME 5 | websocketd \- turns any program that uses STDIN/STDOUT into a WebSocket server. 6 | .SH SYNOPSIS 7 | websocketd [options] COMMAND [command args] 8 | 9 | or 10 | 11 | websocketd [options] --dir=SOMEDIR 12 | .SH DESCRIPTION 13 | \fBwebsocketd\fR is a command line tool that will allow any executable program 14 | that accepts input on stdin and produces output on stdout to be turned into 15 | a WebSocket server. 16 | 17 | To learn more about websocketd visit \fIhttp://websocketd.com\fR and project WIKI 18 | on GitHub! 19 | .SH OPTIONS 20 | A summary of the options supported by websocketd is included below. 21 | .PP 22 | \-\-port=PORT 23 | .RS 4 24 | HTTP port to listen on. 25 | .RE 26 | .PP 27 | \-\-address=ADDRESS 28 | .RS 4 29 | Address to bind to (multiple options allowed). Use square brackets to specify IPv6 address. Default: "" (all) 30 | .RE 31 | .PP 32 | \-\-sameorigin={true,false} 33 | .RS 4 34 | Restrict (HTTP 403) protocol upgrades if the Origin header does not match to requested HTTP Host. Default: false. 35 | .RE 36 | .PP 37 | --origin=host[:port][,host[:port]...] 38 | .RS 4 39 | Restrict (HTTP 403) protocol upgrades if the Origin header does not match to one of the host and port combinations listed. If the port is not specified, any port number will match. Default: "" (allow any origin) 40 | .RE 41 | .PP 42 | \-\-ssl \-\-sslcert=FILE \-\-sslkey=FILE 43 | .RS 4 44 | Listen for HTTPS socket instead of HTTP. All three options must be used or all of them should be omitted. 45 | .RE 46 | .PP 47 | \-\-passenv VAR[,VAR...] 48 | .RS 4 49 | Lists environment variables allowed to be passed to executed scripts. 50 | .RE 51 | .PP 52 | \-\-reverselookup={true,false} 53 | .RS 4 54 | Perform DNS reverse lookups on remote clients. Default: true 55 | .RE 56 | .PP 57 | \-\-dir=DIR 58 | .RS 4 59 | Allow all scripts in the local directory to be accessed as WebSockets. If using this, option, then the standard program and args options should not be specified. 60 | .RE 61 | .PP 62 | \-\-staticdir=DIR 63 | .RS 4 64 | Serve static files in this directory over HTTP. 65 | .RE 66 | .PP 67 | \-\-cgidir=DIR 68 | .RS 4 69 | Serve CGI scripts in this directory over HTTP. 70 | .RE 71 | .PP 72 | \-\-help 73 | .RS 4 74 | Print help and exit. 75 | .RE 76 | .PP 77 | \-\-version 78 | .RS 4 79 | Print version and exit. 80 | .RE 81 | .PP 82 | \-\-license 83 | .RS 4 84 | Print license and exit. 85 | .RE 86 | .PP 87 | \-\-devconsole 88 | .RS 4 89 | Enable interactive development console. This enables you to access the websocketd server with a web-browser and use a user interface to quickly test WebSocket endpoints. For example, to test an endpoint at ws://[host]/foo, you can visit http://[host]/foo in your browser. This flag cannot be used in conjunction with \-\-staticdir or \-\-cgidir. 90 | .RE 91 | .PP 92 | \-\-loglevel=LEVEL 93 | .RS 4 94 | Log level to use (default access). From most to least verbose: debug, trace, access, info, error, fatal 95 | .RE 96 | .SH SEE ALSO 97 | .RS 2 98 | * full documentation at \fIhttp://websocketd.com\fR 99 | .RE 100 | .RS 2 101 | * project source at \fIhttps://github.com/joewalnes/websocketd\fR 102 | .RE 103 | .SH BUGS 104 | The only known condition so far is that certain applications in programming languages that enforce implicit STDOUT buffering (Perl, Python, etc.) would be producing unexpected data passing 105 | delays when run under \fBwebsocketd\fR. Such issues could be solved by editing the source code of those applications (prohibiting buffering) or modifying their environment to trick them 106 | into autoflush mode (e.g. pseudo-terminal wrapper "unbuffer"). 107 | 108 | Active issues in development are discussed on GitHub: \fIhttps://github.com/joewalnes/websocketd/issues\fR. 109 | 110 | Please use that page to share your concerns and ideas about \fBwebsocketd\fR, authors would greatly appreciate your help! 111 | .SH AUTHOR 112 | Copyright 2013-2014 Joe Walnes and the websocketd team. All rights reserved. 113 | 114 | BSD license: Run 'websocketd \-\-license' for details. 115 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "net/http" 11 | "os" 12 | "runtime" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/joewalnes/websocketd/libwebsocketd" 17 | ) 18 | 19 | func logfunc(l *libwebsocketd.LogScope, level libwebsocketd.LogLevel, levelName string, category string, msg string, args ...interface{}) { 20 | if level < l.MinLevel { 21 | return 22 | } 23 | fullMsg := fmt.Sprintf(msg, args...) 24 | 25 | assocDump := "" 26 | for index, pair := range l.Associated { 27 | if index > 0 { 28 | assocDump += " " 29 | } 30 | assocDump += fmt.Sprintf("%s:'%s'", pair.Key, pair.Value) 31 | } 32 | 33 | l.Mutex.Lock() 34 | fmt.Printf("%s | %-6s | %-10s | %s | %s\n", libwebsocketd.Timestamp(), levelName, category, assocDump, fullMsg) 35 | l.Mutex.Unlock() 36 | } 37 | 38 | func main() { 39 | config := parseCommandLine() 40 | 41 | log := libwebsocketd.RootLogScope(config.LogLevel, logfunc) 42 | 43 | if config.DevConsole { 44 | if config.StaticDir != "" { 45 | log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --staticdir. Pick one.") 46 | os.Exit(4) 47 | } 48 | if config.CgiDir != "" { 49 | log.Fatal("server", "Invalid parameters: --devconsole cannot be used with --cgidir. Pick one.") 50 | os.Exit(4) 51 | } 52 | } 53 | 54 | if runtime.GOOS != "windows" { // windows relies on env variables to find its libs... e.g. socket stuff 55 | os.Clearenv() // it's ok to wipe it clean, we already read env variables from passenv into config 56 | } 57 | handler := libwebsocketd.NewWebsocketdServer(config.Config, log, config.MaxForks) 58 | http.Handle("/", handler) 59 | 60 | if config.UsingScriptDir { 61 | log.Info("server", "Serving from directory : %s", config.ScriptDir) 62 | } else if config.CommandName != "" { 63 | log.Info("server", "Serving using application : %s %s", config.CommandName, strings.Join(config.CommandArgs, " ")) 64 | } 65 | if config.StaticDir != "" { 66 | log.Info("server", "Serving static content from : %s", config.StaticDir) 67 | } 68 | if config.CgiDir != "" { 69 | log.Info("server", "Serving CGI scripts from : %s", config.CgiDir) 70 | } 71 | 72 | rejects := make(chan error, 1) 73 | for _, addrSingle := range config.Addr { 74 | log.Info("server", "Starting WebSocket server : %s", handler.TellURL("ws", addrSingle, "/")) 75 | if config.DevConsole { 76 | log.Info("server", "Developer console enabled : %s", handler.TellURL("http", addrSingle, "/")) 77 | } else if config.StaticDir != "" || config.CgiDir != "" { 78 | log.Info("server", "Serving CGI or static files : %s", handler.TellURL("http", addrSingle, "/")) 79 | } 80 | // ListenAndServe is blocking function. Let's run it in 81 | // go routine, reporting result to control channel. 82 | // Since it's blocking it'll never return non-error. 83 | 84 | go func(addr string) { 85 | if config.Ssl { 86 | rejects <- http.ListenAndServeTLS(addr, config.CertFile, config.KeyFile, nil) 87 | } else { 88 | rejects <- http.ListenAndServe(addr, nil) 89 | } 90 | }(addrSingle) 91 | 92 | if config.RedirPort != 0 { 93 | go func(addr string) { 94 | pos := strings.IndexByte(addr, ':') 95 | rediraddr := addr[:pos] + ":" + strconv.Itoa(config.RedirPort) // it would be silly to optimize this one 96 | redir := &http.Server{Addr: rediraddr, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 97 | // redirect to same hostname as in request but different port and probably schema 98 | uri := "https://" 99 | if !config.Ssl { 100 | uri = "http://" 101 | } 102 | if cpos := strings.IndexByte(r.Host, ':'); cpos > 0 { 103 | uri += r.Host[:strings.IndexByte(r.Host, ':')] + addr[pos:] + "/" 104 | } else { 105 | uri += r.Host + addr[pos:] + "/" 106 | } 107 | 108 | http.Redirect(w, r, uri, http.StatusMovedPermanently) 109 | })} 110 | log.Info("server", "Starting redirect server : http://%s/", rediraddr) 111 | rejects <- redir.ListenAndServe() 112 | }(addrSingle) 113 | } 114 | } 115 | err := <-rejects 116 | if err != nil { 117 | log.Fatal("server", "Can't start server: %s", err) 118 | os.Exit(3) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /libwebsocketd/env.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "fmt" 10 | "net/http" 11 | "strings" 12 | ) 13 | 14 | const ( 15 | gatewayInterface = "websocketd-CGI/0.1" 16 | ) 17 | 18 | var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") 19 | var headerDashToUnderscore = strings.NewReplacer("-", "_") 20 | 21 | func createEnv(handler *WebsocketdHandler, req *http.Request, log *LogScope) []string { 22 | headers := req.Header 23 | 24 | url := req.URL 25 | 26 | serverName, serverPort, err := tellHostPort(req.Host, handler.server.Config.Ssl) 27 | if err != nil { 28 | // This does mean that we cannot detect port from Host: header... Just keep going with "", guessing is bad. 29 | log.Debug("env", "Host port detection error: %s", err) 30 | serverPort = "" 31 | } 32 | 33 | standardEnvCount := 20 34 | if handler.server.Config.Ssl { 35 | standardEnvCount += 1 36 | } 37 | 38 | parentLen := len(handler.server.Config.ParentEnv) 39 | env := make([]string, 0, len(headers)+standardEnvCount+parentLen+len(handler.server.Config.Env)) 40 | 41 | // This variable could be rewritten from outside 42 | env = appendEnv(env, "SERVER_SOFTWARE", handler.server.Config.ServerSoftware) 43 | 44 | parentStarts := len(env) 45 | env = append(env, handler.server.Config.ParentEnv...) 46 | 47 | // IMPORTANT ---> Adding a header? Make sure standardEnvCount (above) is up to date. 48 | 49 | // Standard CGI specification headers. 50 | // As defined in http://tools.ietf.org/html/rfc3875 51 | env = appendEnv(env, "REMOTE_ADDR", handler.RemoteInfo.Addr) 52 | env = appendEnv(env, "REMOTE_HOST", handler.RemoteInfo.Host) 53 | env = appendEnv(env, "SERVER_NAME", serverName) 54 | env = appendEnv(env, "SERVER_PORT", serverPort) 55 | env = appendEnv(env, "SERVER_PROTOCOL", req.Proto) 56 | env = appendEnv(env, "GATEWAY_INTERFACE", gatewayInterface) 57 | env = appendEnv(env, "REQUEST_METHOD", req.Method) 58 | env = appendEnv(env, "SCRIPT_NAME", handler.URLInfo.ScriptPath) 59 | env = appendEnv(env, "PATH_INFO", handler.URLInfo.PathInfo) 60 | env = appendEnv(env, "PATH_TRANSLATED", url.Path) 61 | env = appendEnv(env, "QUERY_STRING", url.RawQuery) 62 | 63 | // Not supported, but we explicitly clear them so we don't get leaks from parent environment. 64 | env = appendEnv(env, "AUTH_TYPE", "") 65 | env = appendEnv(env, "CONTENT_LENGTH", "") 66 | env = appendEnv(env, "CONTENT_TYPE", "") 67 | env = appendEnv(env, "REMOTE_IDENT", "") 68 | env = appendEnv(env, "REMOTE_USER", "") 69 | 70 | // Non standard, but commonly used headers. 71 | env = appendEnv(env, "UNIQUE_ID", handler.Id) // Based on Apache mod_unique_id. 72 | env = appendEnv(env, "REMOTE_PORT", handler.RemoteInfo.Port) 73 | env = appendEnv(env, "REQUEST_URI", url.RequestURI()) // e.g. /foo/blah?a=b 74 | 75 | // The following variables are part of the CGI specification, but are optional 76 | // and not set by websocketd: 77 | // 78 | // AUTH_TYPE, REMOTE_USER, REMOTE_IDENT 79 | // -- Authentication left to the underlying programs. 80 | // 81 | // CONTENT_LENGTH, CONTENT_TYPE 82 | // -- makes no sense for WebSocket connections. 83 | // 84 | // SSL_* 85 | // -- SSL variables are not supported, HTTPS=on added for websocketd running with --ssl 86 | 87 | if handler.server.Config.Ssl { 88 | env = appendEnv(env, "HTTPS", "on") 89 | } 90 | 91 | if log.MinLevel == LogDebug { 92 | for i, v := range env { 93 | if i >= parentStarts && i < parentLen+parentStarts { 94 | log.Debug("env", "Parent envvar: %v", v) 95 | } else { 96 | log.Debug("env", "Std. variable: %v", v) 97 | } 98 | } 99 | } 100 | 101 | for k, hdrs := range headers { 102 | header := fmt.Sprintf("HTTP_%s", headerDashToUnderscore.Replace(k)) 103 | env = appendEnv(env, header, hdrs...) 104 | log.Debug("env", "Header variable %s", env[len(env)-1]) 105 | } 106 | 107 | for _, v := range handler.server.Config.Env { 108 | env = append(env, v) 109 | log.Debug("env", "External variable: %s", v) 110 | } 111 | 112 | return env 113 | } 114 | 115 | // Adapted from net/http/header.go 116 | func appendEnv(env []string, k string, v ...string) []string { 117 | if len(v) == 0 { 118 | return env 119 | } 120 | 121 | vCleaned := make([]string, 0, len(v)) 122 | for _, val := range v { 123 | vCleaned = append(vCleaned, strings.TrimSpace(headerNewlineToSpace.Replace(val))) 124 | } 125 | return append(env, fmt.Sprintf("%s=%s", 126 | strings.ToUpper(k), 127 | strings.Join(vCleaned, ", "))) 128 | } 129 | -------------------------------------------------------------------------------- /libwebsocketd/handler.go: -------------------------------------------------------------------------------- 1 | package libwebsocketd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/gorilla/websocket" 15 | ) 16 | 17 | var ScriptNotFoundError = errors.New("script not found") 18 | 19 | // WebsocketdHandler is a single request information and processing structure, it handles WS requests out of all that daemon can handle (static, cgi, devconsole) 20 | type WebsocketdHandler struct { 21 | server *WebsocketdServer 22 | 23 | Id string 24 | *RemoteInfo 25 | *URLInfo // TODO: I cannot find where it's used except in one single place as URLInfo.FilePath 26 | Env []string 27 | 28 | command string 29 | } 30 | 31 | // NewWebsocketdHandler constructs the struct and parses all required things in it... 32 | func NewWebsocketdHandler(s *WebsocketdServer, req *http.Request, log *LogScope) (wsh *WebsocketdHandler, err error) { 33 | wsh = &WebsocketdHandler{server: s, Id: generateId()} 34 | log.Associate("id", wsh.Id) 35 | 36 | wsh.RemoteInfo, err = GetRemoteInfo(req.RemoteAddr, s.Config.ReverseLookup) 37 | if err != nil { 38 | log.Error("session", "Could not understand remote address '%s': %s", req.RemoteAddr, err) 39 | return nil, err 40 | } 41 | log.Associate("remote", wsh.RemoteInfo.Host) 42 | 43 | wsh.URLInfo, err = GetURLInfo(req.URL.Path, s.Config) 44 | if err != nil { 45 | log.Access("session", "NOT FOUND: %s", err) 46 | return nil, err 47 | } 48 | 49 | wsh.command = s.Config.CommandName 50 | if s.Config.UsingScriptDir { 51 | wsh.command = wsh.URLInfo.FilePath 52 | } 53 | log.Associate("command", wsh.command) 54 | 55 | wsh.Env = createEnv(wsh, req, log) 56 | 57 | return wsh, nil 58 | } 59 | 60 | func (wsh *WebsocketdHandler) accept(ws *websocket.Conn, log *LogScope) { 61 | defer ws.Close() 62 | 63 | log.Access("session", "CONNECT") 64 | defer log.Access("session", "DISCONNECT") 65 | 66 | launched, err := launchCmd(wsh.command, wsh.server.Config.CommandArgs, wsh.Env) 67 | if err != nil { 68 | log.Error("process", "Could not launch process %s %s (%s)", wsh.command, strings.Join(wsh.server.Config.CommandArgs, " "), err) 69 | return 70 | } 71 | 72 | log.Associate("pid", strconv.Itoa(launched.cmd.Process.Pid)) 73 | 74 | binary := wsh.server.Config.Binary 75 | process := NewProcessEndpoint(launched, binary, log) 76 | if cms := wsh.server.Config.CloseMs; cms != 0 { 77 | process.closetime += time.Duration(cms) * time.Millisecond 78 | } 79 | wsEndpoint := NewWebSocketEndpoint(ws, binary, log) 80 | 81 | PipeEndpoints(process, wsEndpoint) 82 | } 83 | 84 | // RemoteInfo holds information about remote http client 85 | type RemoteInfo struct { 86 | Addr, Host, Port string 87 | } 88 | 89 | // GetRemoteInfo creates RemoteInfo structure and fills its fields appropriately 90 | func GetRemoteInfo(remote string, doLookup bool) (*RemoteInfo, error) { 91 | addr, port, err := net.SplitHostPort(remote) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | var host string 97 | if doLookup { 98 | hosts, err := net.LookupAddr(addr) 99 | if err != nil || len(hosts) == 0 { 100 | host = addr 101 | } else { 102 | host = hosts[0] 103 | } 104 | } else { 105 | host = addr 106 | } 107 | 108 | return &RemoteInfo{Addr: addr, Host: host, Port: port}, nil 109 | } 110 | 111 | // URLInfo - structure carrying information about current request and it's mapping to filesystem 112 | type URLInfo struct { 113 | ScriptPath string 114 | PathInfo string 115 | FilePath string 116 | } 117 | 118 | // GetURLInfo is a function that parses path and provides URL info according to libwebsocketd.Config fields 119 | func GetURLInfo(path string, config *Config) (*URLInfo, error) { 120 | if !config.UsingScriptDir { 121 | return &URLInfo{"/", path, ""}, nil 122 | } 123 | 124 | parts := strings.Split(path[1:], "/") 125 | urlInfo := &URLInfo{} 126 | 127 | for i, part := range parts { 128 | urlInfo.ScriptPath = strings.Join([]string{urlInfo.ScriptPath, part}, "/") 129 | urlInfo.FilePath = filepath.Join(config.ScriptDir, urlInfo.ScriptPath) 130 | isLastPart := i == len(parts)-1 131 | statInfo, err := os.Stat(urlInfo.FilePath) 132 | 133 | // not a valid path 134 | if err != nil { 135 | return nil, ScriptNotFoundError 136 | } 137 | 138 | // at the end of url but is a dir 139 | if isLastPart && statInfo.IsDir() { 140 | return nil, ScriptNotFoundError 141 | } 142 | 143 | // we've hit a dir, carry on looking 144 | if statInfo.IsDir() { 145 | continue 146 | } 147 | 148 | // no extra args 149 | if isLastPart { 150 | return urlInfo, nil 151 | } 152 | 153 | // build path info from extra parts of url 154 | urlInfo.PathInfo = "/" + strings.Join(parts[i+1:], "/") 155 | return urlInfo, nil 156 | } 157 | panic(fmt.Sprintf("GetURLInfo cannot parse path %#v", path)) 158 | } 159 | 160 | func generateId() string { 161 | return strconv.FormatInt(time.Now().UnixNano(), 10) 162 | } 163 | -------------------------------------------------------------------------------- /libwebsocketd/process_endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "bufio" 10 | "io" 11 | "syscall" 12 | "time" 13 | ) 14 | 15 | type ProcessEndpoint struct { 16 | process *LaunchedProcess 17 | closetime time.Duration 18 | output chan []byte 19 | log *LogScope 20 | bin bool 21 | } 22 | 23 | func NewProcessEndpoint(process *LaunchedProcess, bin bool, log *LogScope) *ProcessEndpoint { 24 | return &ProcessEndpoint{ 25 | process: process, 26 | output: make(chan []byte), 27 | log: log, 28 | bin: bin, 29 | } 30 | } 31 | 32 | func (pe *ProcessEndpoint) Terminate() { 33 | terminated := make(chan struct{}) 34 | go func() { pe.process.cmd.Wait(); terminated <- struct{}{} }() 35 | 36 | // for some processes this is enough to finish them... 37 | pe.process.stdin.Close() 38 | 39 | // a bit verbose to create good debugging trail 40 | select { 41 | case <-terminated: 42 | pe.log.Debug("process", "Process %v terminated after stdin was closed", pe.process.cmd.Process.Pid) 43 | return // means process finished 44 | case <-time.After(100*time.Millisecond + pe.closetime): 45 | } 46 | 47 | err := pe.process.cmd.Process.Signal(syscall.SIGINT) 48 | if err != nil { 49 | // process is done without this, great! 50 | pe.log.Error("process", "SIGINT unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) 51 | } 52 | 53 | select { 54 | case <-terminated: 55 | pe.log.Debug("process", "Process %v terminated after SIGINT", pe.process.cmd.Process.Pid) 56 | return // means process finished 57 | case <-time.After(250*time.Millisecond + pe.closetime): 58 | } 59 | 60 | err = pe.process.cmd.Process.Signal(syscall.SIGTERM) 61 | if err != nil { 62 | // process is done without this, great! 63 | pe.log.Error("process", "SIGTERM unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) 64 | } 65 | 66 | select { 67 | case <-terminated: 68 | pe.log.Debug("process", "Process %v terminated after SIGTERM", pe.process.cmd.Process.Pid) 69 | return // means process finished 70 | case <-time.After(500*time.Millisecond + pe.closetime): 71 | } 72 | 73 | err = pe.process.cmd.Process.Kill() 74 | if err != nil { 75 | pe.log.Error("process", "SIGKILL unsuccessful to %v: %s", pe.process.cmd.Process.Pid, err) 76 | return 77 | } 78 | 79 | select { 80 | case <-terminated: 81 | pe.log.Debug("process", "Process %v terminated after SIGKILL", pe.process.cmd.Process.Pid) 82 | return // means process finished 83 | case <-time.After(1000 * time.Millisecond): 84 | } 85 | 86 | pe.log.Error("process", "SIGKILL did not terminate %v!", pe.process.cmd.Process.Pid) 87 | } 88 | 89 | func (pe *ProcessEndpoint) Output() chan []byte { 90 | return pe.output 91 | } 92 | 93 | func (pe *ProcessEndpoint) Send(msg []byte) bool { 94 | pe.process.stdin.Write(msg) 95 | return true 96 | } 97 | 98 | func (pe *ProcessEndpoint) StartReading() { 99 | go pe.log_stderr() 100 | if pe.bin { 101 | go pe.process_binout() 102 | } else { 103 | go pe.process_txtout() 104 | } 105 | } 106 | 107 | func (pe *ProcessEndpoint) process_txtout() { 108 | bufin := bufio.NewReader(pe.process.stdout) 109 | for { 110 | buf, err := bufin.ReadBytes('\n') 111 | if err != nil { 112 | if err != io.EOF { 113 | pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err) 114 | } else { 115 | pe.log.Debug("process", "Process STDOUT closed") 116 | } 117 | break 118 | } 119 | pe.output <- trimEOL(buf) 120 | } 121 | close(pe.output) 122 | } 123 | 124 | func (pe *ProcessEndpoint) process_binout() { 125 | buf := make([]byte, 10*1024*1024) 126 | for { 127 | n, err := pe.process.stdout.Read(buf) 128 | if err != nil { 129 | if err != io.EOF { 130 | pe.log.Error("process", "Unexpected error while reading STDOUT from process: %s", err) 131 | } else { 132 | pe.log.Debug("process", "Process STDOUT closed") 133 | } 134 | break 135 | } 136 | pe.output <- append(make([]byte, 0, n), buf[:n]...) // cloned buffer 137 | } 138 | close(pe.output) 139 | } 140 | 141 | func (pe *ProcessEndpoint) log_stderr() { 142 | bufstderr := bufio.NewReader(pe.process.stderr) 143 | for { 144 | buf, err := bufstderr.ReadSlice('\n') 145 | if err != nil { 146 | if err != io.EOF { 147 | pe.log.Error("process", "Unexpected error while reading STDERR from process: %s", err) 148 | } else { 149 | pe.log.Debug("process", "Process STDERR closed") 150 | } 151 | break 152 | } 153 | pe.log.Error("stderr", "%s", string(trimEOL(buf))) 154 | } 155 | } 156 | 157 | // trimEOL cuts unixy style \n and windowsy style \r\n suffix from the string 158 | func trimEOL(b []byte) []byte { 159 | lns := len(b) 160 | if lns > 0 && b[lns-1] == '\n' { 161 | lns-- 162 | if lns > 0 && b[lns-1] == '\r' { 163 | lns-- 164 | } 165 | } 166 | return b[:lns] 167 | } 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | websocketd 2 | ========== 3 | 4 | `websocketd` is a small command-line tool that will wrap an existing command-line interface program, and allow it to be accessed via a WebSocket. 5 | 6 | WebSocket-capable applications can now be built very easily. As long as you can write an executable program that reads `STDIN` and writes to `STDOUT`, you can build a WebSocket server. Do it in Python, Ruby, Perl, Bash, .NET, C, Go, PHP, Java, Clojure, Scala, Groovy, Expect, Awk, VBScript, Haskell, Lua, R, whatever! No networking libraries necessary. 7 | 8 | -[@joewalnes](https://twitter.com/joewalnes) 9 | 10 | Details 11 | ------- 12 | 13 | Upon startup, `websocketd` will start a WebSocket server on a specified port, and listen for connections. 14 | 15 | Upon a connection, it will fork the appropriate process, and disconnect the process when the WebSocket connection closes (and vice-versa). 16 | 17 | Any message sent from the WebSocket client will be piped to the process's `STDIN` stream, followed by a `\n` newline. 18 | 19 | Any text printed by the process to `STDOUT` shall be sent as a WebSocket message whenever a `\n` newline is encountered. 20 | 21 | 22 | Download 23 | -------- 24 | 25 | If you're on a Mac, you can install `websocketd` using [Homebrew](http://brew.sh/). Just run `brew install websocketd`. For other operating systems, or if you don't want to use Homebrew, check out the link below. 26 | 27 | **[Download for Linux, OS X and Windows](https://github.com/joewalnes/websocketd/wiki/Download-and-install)** 28 | 29 | 30 | Quickstart 31 | ---------- 32 | 33 | To get started, we'll create a WebSocket endpoint that will accept connections, then send back messages, counting to 10 with 1 second pause between each one, before disconnecting. 34 | 35 | To show how simple it is, let's do it in Bash! 36 | 37 | __count.sh__: 38 | 39 | ```sh 40 | #!/bin/bash 41 | for ((COUNT = 1; COUNT <= 10; COUNT++)); do 42 | echo $COUNT 43 | sleep 1 44 | done 45 | ``` 46 | 47 | Before turning it into a WebSocket server, let's test it from the command line. The beauty of `websocketd` is that servers work equally well in the command line, or in shell scripts, as they do in the server - with no modifications required. 48 | 49 | ```sh 50 | $ chmod +x count.sh 51 | $ ./count.sh 52 | 1 53 | 2 54 | 3 55 | 4 56 | 5 57 | 6 58 | 7 59 | 8 60 | 9 61 | 10 62 | ``` 63 | 64 | Now let's turn it into a WebSocket server: 65 | 66 | ```sh 67 | $ websocketd --port=8080 ./count.sh 68 | ``` 69 | 70 | Finally, let's create a web-page to test it. 71 | 72 | __count.html__: 73 | 74 | ```html 75 | 76 |

 77 | 
 95 | ```
 96 | Open this page in your web-browser. It will even work if you open it directly
 97 | from disk using a `file://` URL.
 98 | 
 99 | More Features
100 | -------------
101 | 
102 | *   Very simple install. Just [download](https://github.com/joewalnes/websocketd/wiki/Download-and-install) the single executable for Linux, Mac or Windows and run it. Minimal dependencies, no installers, no package managers, no external libraries. Suitable for development and production servers.
103 | *   Server side scripts can access details about the WebSocket HTTP request (e.g. remote host, query parameters, cookies, path, etc) via standard [CGI environment variables](https://github.com/joewalnes/websocketd/wiki/Environment-variables).
104 | *   As well as serving websocket daemons it also includes a static file server and classic CGI server for convenience.
105 | *   Command line help available via `websocketd --help`.
106 | *   Includes [WebSocket developer console](https://github.com/joewalnes/websocketd/wiki/Developer-console) to make it easy to test your scripts before you've built a JavaScript frontend.
107 | *   [Examples in many programming languages](https://github.com/joewalnes/websocketd/tree/master/examples) are available to help you getting started.
108 | 
109 | User Manual
110 | -----------
111 | 
112 | **[More documentation in the user manual](https://github.com/joewalnes/websocketd/wiki)**
113 | 
114 | Example Projects
115 | ----------------
116 | 
117 | *   [Plot real time Linux CPU/IO/Mem stats to a HTML5 dashboard using websocketd and vmstat](https://github.com/joewalnes/web-vmstats) _(for Linux)_
118 | *   [Arbitrary REPL in the browser using websocketd](https://github.com/rowanthorpe/ws-repl)
119 | *   [Retrieve SQL data from server with LiveCode and webSocketd](https://github.com/samansjukur/wslc)
120 | *   [List files from a configured folder](https://github.com/dbalakirev/directator) _(for Linux)_
121 | *   [Listen for gamepad events and report them to the system](https://github.com/experiment322/controlloid-server) _(this + android = gamepad emulator)_
122 | 
123 | Got more examples? Open a pull request.
124 | 
125 | My Other Projects
126 | -----------------
127 | 
128 | *   [ReconnectingWebSocket](https://github.com/joewalnes/reconnecting-websocket) - Simplest way to add some robustness to your WebSocket connections.
129 | *   [Smoothie Charts](http://smoothiecharts.org/) - JavaScript charts for streaming data.
130 | *   Visit [The Igloo Lab](http://theigloolab.com/) to see and subscribe to other thingies I make.
131 | 
132 | And [follow @joewalnes](https://twitter.com/joewalnes)!
133 | 


--------------------------------------------------------------------------------
/libwebsocketd/http_test.go:
--------------------------------------------------------------------------------
  1 | package libwebsocketd
  2 | 
  3 | import (
  4 | 	"bufio"
  5 | 	"crypto/tls"
  6 | 	"fmt"
  7 | 	"net/http"
  8 | 	"strings"
  9 | 	"testing"
 10 | )
 11 | 
 12 | var tellHostPortTests = []struct {
 13 | 	src          string
 14 | 	ssl          bool
 15 | 	server, port string
 16 | }{
 17 | 	{"localhost", false, "localhost", "80"},
 18 | 	{"localhost:8080", false, "localhost", "8080"},
 19 | 	{"localhost", true, "localhost", "443"},
 20 | 	{"localhost:8080", true, "localhost", "8080"},
 21 | }
 22 | 
 23 | func TestTellHostPort(t *testing.T) {
 24 | 	for _, testcase := range tellHostPortTests {
 25 | 		s, p, e := tellHostPort(testcase.src, testcase.ssl)
 26 | 		if testcase.server == "" {
 27 | 			if e == nil {
 28 | 				t.Errorf("test case for %#v failed, error was not returned", testcase.src)
 29 | 			}
 30 | 		} else if e != nil {
 31 | 			t.Errorf("test case for %#v failed, error should not happen", testcase.src)
 32 | 		}
 33 | 		if testcase.server != s || testcase.port != p {
 34 | 			t.Errorf("test case for %#v failed, server or port mismatch to expected values (%s:%s)", testcase.src, s, p)
 35 | 		}
 36 | 	}
 37 | }
 38 | 
 39 | var NoOriginsAllowed = []string{}
 40 | var NoOriginList []string = nil
 41 | 
 42 | const (
 43 | 	ReqHTTPS = iota
 44 | 	ReqHTTP
 45 | 	OriginMustBeSame
 46 | 	OriginCouldDiffer
 47 | 	ReturnsPass
 48 | 	ReturnsError
 49 | )
 50 | 
 51 | var CheckOriginTests = []struct {
 52 | 	host    string
 53 | 	reqtls  int
 54 | 	origin  string
 55 | 	same    int
 56 | 	allowed []string
 57 | 	getsErr int
 58 | 	name    string
 59 | }{
 60 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed"},
 61 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin mismatch"},
 62 | 	{"server.example.com", ReqHTTP, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsPass, "same origin match"},
 63 | 	{"server.example.com", ReqHTTP, "https://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 1"},
 64 | 	{"server.example.com", ReqHTTPS, "http://server.example.com", OriginMustBeSame, NoOriginList, ReturnsError, "same origin schema mismatch 2"},
 65 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, NoOriginsAllowed, ReturnsError, "no origins allowed"},
 66 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"server.example.com"}, ReturnsError, "no origin allowed matches (junk prefix)"},
 67 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com.t"}, ReturnsError, "no origin allowed matches (junk suffix)"},
 68 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed clean match"},
 69 | 	{"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com"}, ReturnsPass, "origin allowed any port match"},
 70 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:80"}, ReturnsPass, "origin allowed port match"},
 71 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"},
 72 | 	{"server.example.com", ReqHTTP, "http://example.com", OriginCouldDiffer, []string{"example.com:81"}, ReturnsError, "origin allowed port mismatch"},
 73 | 	{"server.example.com", ReqHTTP, "http://example.com:81", OriginCouldDiffer, []string{"example.com:81"}, ReturnsPass, "origin allowed port 81 match"},
 74 | 	{"server.example.com", ReqHTTP, "null", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even null"},
 75 | 	{"server.example.com", ReqHTTP, "", OriginCouldDiffer, NoOriginList, ReturnsPass, "any origin allowed, even empty"},
 76 | }
 77 | 
 78 | // CONVERT GORILLA
 79 | // as method for origin checking changes to handle things without websocket.Config the test
 80 | // should be altered too
 81 | 
 82 | func TestCheckOrigin(t *testing.T) {
 83 | 	for _, testcase := range CheckOriginTests {
 84 | 		br := bufio.NewReader(strings.NewReader(fmt.Sprintf(`GET /chat HTTP/1.1
 85 | Host: %s
 86 | Upgrade: websocket
 87 | Connection: Upgrade
 88 | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
 89 | Origin: %s
 90 | Sec-WebSocket-Version: 13
 91 | 
 92 | `, testcase.host, testcase.origin)))
 93 | 
 94 | 		req, err := http.ReadRequest(br)
 95 | 		if err != nil {
 96 | 			t.Fatal("request", err)
 97 | 		}
 98 | 
 99 | 		log := new(LogScope)
100 | 		log.LogFunc = func(*LogScope, LogLevel, string, string, string, ...interface{}) {}
101 | 
102 | 		config := new(Config)
103 | 
104 | 		if testcase.reqtls == ReqHTTPS { // Fake TLS
105 | 			config.Ssl = true
106 | 			req.TLS = &tls.ConnectionState{}
107 | 		}
108 | 		if testcase.same == OriginMustBeSame {
109 | 			config.SameOrigin = true
110 | 		}
111 | 		if testcase.allowed != nil {
112 | 			config.AllowOrigins = testcase.allowed
113 | 		}
114 | 
115 | 		err = checkOrigin(req, config, log)
116 | 		if testcase.getsErr == ReturnsError && err == nil {
117 | 			t.Errorf("Test case %#v did not get an error", testcase.name)
118 | 		} else if testcase.getsErr == ReturnsPass && err != nil {
119 | 			t.Errorf("Test case %#v got error while expected to pass", testcase.name)
120 | 		}
121 | 	}
122 | }
123 | 
124 | var mimetest = [][3]string{
125 | 	{"Content-Type: text/plain", "Content-Type", "text/plain"},
126 | 	{"Content-Type:    ", "Content-Type", ""},
127 | }
128 | 
129 | func TestSplitMimeHeader(t *testing.T) {
130 | 	for _, tst := range mimetest {
131 | 		s, v := splitMimeHeader(tst[0])
132 | 		if tst[1] != s || tst[2] != v {
133 | 			t.Errorf("%v and %v  are not same as expexted %v and %v", s, v, tst[1], tst[2])
134 | 		}
135 | 	}
136 | }
137 | 


--------------------------------------------------------------------------------
/help.go:
--------------------------------------------------------------------------------
  1 | // Copyright 2013 Joe Walnes and the websocketd team.
  2 | // All rights reserved.
  3 | // Use of this source code is governed by a BSD-style
  4 | // license that can be found in the LICENSE file.
  5 | 
  6 | package main
  7 | 
  8 | import (
  9 | 	"fmt"
 10 | 	"os"
 11 | 	"path/filepath"
 12 | 	"strings"
 13 | )
 14 | 
 15 | const (
 16 | 	help = `
 17 | {{binary}} ({{version}})
 18 | 
 19 | {{binary}} is a command line tool that will allow any executable program
 20 | that accepts input on stdin and produces output on stdout to be turned into
 21 | a WebSocket server.
 22 | 
 23 | Usage:
 24 | 
 25 |   Export a single executable program a WebSocket server:
 26 |     {{binary}} [options] COMMAND [command args]
 27 | 
 28 |   Or, export an entire directory of executables as WebSocket endpoints:
 29 |     {{binary}} [options] --dir=SOMEDIR
 30 | 
 31 | Options:
 32 | 
 33 |   --port=PORT                    HTTP port to listen on.
 34 | 
 35 |   --address=ADDRESS              Address to bind to (multiple options allowed)
 36 |                                  Use square brackets to specify IPv6 address.
 37 |                                  Default: "" (all)
 38 | 
 39 |   --sameorigin={true,false}      Restrict (HTTP 403) protocol upgrades if the
 40 |                                  Origin header does not match to requested HTTP
 41 |                                  Host. Default: false.
 42 | 
 43 |   --origin=host[:port][,host[:port]...]
 44 |                                  Restrict (HTTP 403) protocol upgrades if the
 45 |                                  Origin header does not match to one of the host
 46 |                                  and port combinations listed. If the port is not
 47 |                                  specified, any port number will match.
 48 |                                  Default: "" (allow any origin)
 49 | 
 50 |   --ssl                          Listen for HTTPS socket instead of HTTP.
 51 |   --sslcert=FILE                 All three options must be used or all of
 52 |   --sslkey=FILE                  them should be omitted.
 53 | 
 54 |   --redirport=PORT               Open alternative port and redirect HTTP traffic
 55 |                                  from it to canonical address (mostly useful
 56 |                                  for HTTPS-only configurations to redirect HTTP
 57 |                                  traffic)
 58 | 
 59 |   --passenv VAR[,VAR...]         Lists environment variables allowed to be
 60 |                                  passed to executed scripts. Does not work for
 61 |                                  Windows since all the variables are kept there.
 62 | 
 63 |   --binary={true,false}          Switches communication to binary, process reads
 64 |                                  send to browser as blobs and all reads from the
 65 |                                  browser are immediately flushed to the process.
 66 |                                  Default: false
 67 | 
 68 |   --reverselookup={true,false}   Perform DNS reverse lookups on remote clients.
 69 |                                  Default: false
 70 | 
 71 |   --dir=DIR                      Allow all scripts in the local directory
 72 |                                  to be accessed as WebSockets. If using this,
 73 |                                  option, then the standard program and args
 74 |                                  options should not be specified.
 75 | 
 76 |   --staticdir=DIR                Serve static files in this directory over HTTP.
 77 | 
 78 |   --cgidir=DIR                   Serve CGI scripts in this directory over HTTP.
 79 | 
 80 |   --maxforks=N                   Limit number of processes that websocketd is
 81 |                                  able to execute with WS and CGI handlers.
 82 |                                  When maxforks reached the server will be
 83 |                                  rejecting requests that require executing
 84 |                                  another process (unlimited when 0 or negative).
 85 |                                  Default: 0
 86 | 
 87 |   --closems=milliseconds         Specifies additional time process needs to gracefully
 88 |                                  finish before websocketd will send termination signals
 89 |                                  to it. Default: 0 (signals sent after 100ms, 250ms,
 90 |                                  and 500ms of waiting)
 91 | 
 92 |   --header="..."                 Set custom HTTP header to each answer. For
 93 |                                  example: --header="Server: someserver/0.0.1"
 94 | 
 95 |   --header-ws="...."             Same as --header, just applies to only those
 96 |                                  responses that indicate upgrade of TCP connection
 97 |                                  to a WebSockets protocol.
 98 | 
 99 |   --header-http="...."           Same as --header, just applies to only to plain
100 |                                  HTTP responses that do not indicate WebSockets
101 |                                  upgrade
102 | 
103 | 
104 |   --help                         Print help and exit.
105 | 
106 |   --version                      Print version and exit.
107 | 
108 |   --license                      Print license and exit.
109 | 
110 |   --devconsole                   Enable interactive development console.
111 |                                  This enables you to access the websocketd
112 |                                  server with a web-browser and use a
113 |                                  user interface to quickly test WebSocket
114 |                                  endpoints. For example, to test an
115 |                                  endpoint at ws://[host]/foo, you can
116 |                                  visit http://[host]/foo in your browser.
117 |                                  This flag cannot be used in conjunction
118 |                                  with --staticdir or --cgidir.
119 | 
120 |   --loglevel=LEVEL               Log level to use (default access).
121 |                                  From most to least verbose:
122 |                                  debug, trace, access, info, error, fatal
123 | 
124 | Full documentation at http://websocketd.com/
125 | 
126 | Copyright 2013 Joe Walnes and the websocketd team. All rights reserved.
127 | BSD license: Run '{{binary}} --license' for details.
128 | `
129 | 	short = `
130 | Usage:
131 | 
132 |   Export a single executable program a WebSocket server:
133 |     {{binary}} [options] COMMAND [command args]
134 | 
135 |   Or, export an entire directory of executables as WebSocket endpoints:
136 |     {{binary}} [options] --dir=SOMEDIR
137 | 
138 |   Or, show extended help message using:
139 |     {{binary}} --help
140 | `
141 | )
142 | 
143 | func get_help_message(content string) string {
144 | 	msg := strings.Trim(content, " \n")
145 | 	msg = strings.Replace(msg, "{{binary}}", HelpProcessName(), -1)
146 | 	return strings.Replace(msg, "{{version}}", Version(), -1)
147 | }
148 | 
149 | func HelpProcessName() string {
150 | 	binary := os.Args[0]
151 | 	if strings.Contains(binary, "/go-build") { // this was run using "go run", let's use something appropriate
152 | 		binary = "websocketd"
153 | 	} else {
154 | 		binary = filepath.Base(binary)
155 | 	}
156 | 	return binary
157 | }
158 | 
159 | func PrintHelp() {
160 | 	fmt.Fprintf(os.Stderr, "%s\n", get_help_message(help))
161 | }
162 | 
163 | func ShortHelp() {
164 | 	// Shown after some error
165 | 	fmt.Fprintf(os.Stderr, "\n%s\n", get_help_message(short))
166 | }
167 | 


--------------------------------------------------------------------------------
/release/Makefile:
--------------------------------------------------------------------------------
  1 | # Copyright 2013-2019 Joe Walnes and the websocketd team.
  2 | # All rights reserved.
  3 | # Use of this source code is governed by a BSD-style
  4 | # license that can be found in the LICENSE file.
  5 | 
  6 | # Uses Semantic Versioning scheme - http://semver.org/
  7 | VERSION_MAJOR=0
  8 | VERSION_MINOR=4
  9 | 
 10 | # Last part of version number (patch) is incremented automatically from Git tags
 11 | LAST_PATCH_VERSION:=$(shell git ls-remote git@github.com:joewalnes/websocketd.git \
 12 | 		| grep -e 'refs/tags/v[0-9\.]*$$' \
 13 | 		| sed -e 's|^.*refs/tags/v||' \
 14 | 		| grep -e "^$(VERSION_MAJOR)\.$(VERSION_MINOR)\.[0-9][0-9]*$$" \
 15 | 		| sed -e 's/^.*\.//' \
 16 | 		| sort -n \
 17 | 		| tail -n 1)
 18 | 
 19 | 
 20 | 
 21 | VERSION_PATCH:=$(or $(LAST_PATCH_VERSION),0)
 22 | RELEASE_VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH)
 23 | 
 24 | GO_VERSION=1.15.7
 25 | PLATFORMS=linux_amd64 linux_386 linux_arm linux_arm64 darwin_amd64 freebsd_amd64 freebsd_386 windows_386 windows_amd64 openbsd_386 openbsd_amd64 solaris_amd64
 26 | 
 27 | 
 28 | # Would NOT WORK on: ARM, WINDOWS
 29 | 
 30 | UNAME_SYS=$(shell uname -s | tr A-Z a-z)
 31 | UNAME_ARCH=$(shell uname -m)
 32 | ifeq ($(UNAME_ARCH),x86_64)
 33 | 	UNAME_ARCH=amd64
 34 | else
 35 | 	UNAME_ARCH=386 
 36 | endif
 37 | 
 38 | ifeq ($(UNAME_SYS),gnu)
 39 | 	UNAME_SYS=linux
 40 | else ifeq ($(UNAME_SYS),sunos)
 41 | 	UNAME_SYS=solaris
 42 | endif
 43 | 
 44 | 
 45 | 
 46 | GO_DOWNLOAD_URL=https://dl.google.com/go/go$(GO_VERSION).$(UNAME_SYS)-$(UNAME_ARCH).tar.gz
 47 | GO_DIR=../go-$(GO_VERSION)
 48 | 
 49 | # Prevent any global environment polluting the builds
 50 | FLAGS_linux_amd64   = GOOS=linux   GOARCH=amd64
 51 | FLAGS_linux_386     = GOOS=linux   GOARCH=386
 52 | FLAGS_linux_arm     = GOOS=linux   GOARCH=arm   GOARM=5 # ARM5 support for Raspberry Pi
 53 | FLAGS_linux_arm64   = GOOS=linux   GOARCH=arm64  # no need for GOARM= (which is technically 8)
 54 | FLAGS_darwin_amd64  = GOOS=darwin  GOARCH=amd64 CGO_ENABLED=0
 55 | FLAGS_darwin_386    = GOOS=darwin  GOARCH=386   CGO_ENABLED=0
 56 | FLAGS_freebsd_amd64 = GOOS=freebsd GOARCH=amd64 CGO_ENABLED=0
 57 | FLAGS_freebsd_386   = GOOS=freebsd GOARCH=386   CGO_ENABLED=0
 58 | FLAGS_windows_386   = GOOS=windows GOARCH=386   CGO_ENABLED=0
 59 | FLAGS_windows_amd64 = GOOS=windows GOARCH=amd64 CGO_ENABLED=0
 60 | FLAGS_openbsd_386   = GOOS=openbsd GOARCH=386   CGO_ENABLED=0
 61 | FLAGS_openbsd_amd64 = GOOS=openbsd GOARCH=amd64 CGO_ENABLED=0
 62 | FLAGS_solaris_amd64 = GOOS=solaris GOARCH=amd64 CGO_ENABLED=0
 63 | 
 64 | EXTENSION_windows_386 = .exe
 65 | EXTENSION_windows_amd64 = .exe
 66 | 
 67 | all: build
 68 | 
 69 | localgo: $(GO_DIR)/bin/go
 70 | 
 71 | $(GO_DIR)/bin/go:
 72 | 	mkdir -p $(GO_DIR)
 73 | 	rm -f $@
 74 | 	@echo Downloading and unpacking Go $(GO_VERSION) to $(GO_DIR)
 75 | 	curl -s $(GO_DOWNLOAD_URL) | tar xzf - --strip-components=1 -C $(GO_DIR)
 76 | 
 77 | 
 78 | # Cross-compile final applications
 79 | out/$(RELEASE_VERSION)/%/websocketd: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go
 80 | 	rm -f $@
 81 | 	mkdir -p $(dir $@)
 82 | 	$(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd ..
 83 | 
 84 | out/$(RELEASE_VERSION)/%/websocketd.exe: ../*.go ../libwebsocketd/*.go $(GO_DIR)/bin/go
 85 | 	rm -f $@
 86 | 	mkdir -p $(dir $@)
 87 | 	$(FLAGS_$*) $(GO_DIR)/bin/go build -ldflags "-X main.version=$(RELEASE_VERSION)" -o out/$(RELEASE_VERSION)/$*/websocketd.exe ..
 88 | 
 89 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-%.zip: out/$(RELEASE_VERSION)/%/websocketd
 90 | 	rm -f $@
 91 | 	zip -j $@ $< ../{README.md,LICENSE,CHANGES}
 92 | 
 93 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-windows_%.zip: out/$(RELEASE_VERSION)/windows_%/websocketd.exe
 94 | 	rm -f $@
 95 | 	zip -j $@ $< ../{README.md,LICENSE,CHANGES}
 96 | 
 97 | 
 98 | BINARIES = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/$(PLATFORM)/websocketd$(EXTENSION_$(PLATFORM)))
 99 | ZIPS = $(foreach PLATFORM,$(PLATFORMS),out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)-$(PLATFORM).zip)
100 | DEBS = out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb
101 | RPMS = out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm
102 | 
103 | binaries: $(BINARIES)
104 | 
105 | build: out/$(RELEASE_VERSION)/CHECKSUMS
106 | 
107 | out/$(RELEASE_VERSION)/CHECKSUMS: $(BINARIES) $(ZIPS) $(DEBS) $(RPMS)
108 | 	sha256sum $^ | sed -e 's/out\/$(RELEASE_VERSION)\///' >$@
109 | 
110 | 
111 | 
112 | BASEFPM=--description "WebSockets server that converts STDIO scripts to powerful HTML5 applications." --url http://websocketd.com/ --license MIT  --vendor "websocketd team " --maintainer "abc@alexsergeyev.com"
113 | 
114 | DEBFPM=""
115 | RPMFPM=--rpm-os linux
116 | 
117 | deb: $(DEBS)
118 | 
119 | rpm: $(RPMS)
120 | 
121 | 
122 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_i386.deb: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd
123 | 	mkdir -p out/$(RELEASE_VERSION)/deb32/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
124 | 	cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/deb32/usr/bin/
125 | 	cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb32/usr/share/doc/websocketd-$(RELEASE_VERSION)
126 | 	cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb32/usr/share/man/man1/websocket.1.gz
127 | 	fpm -f -s dir -t deb -a i386 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/deb32/ -p out/$(RELEASE_VERSION)/websocketd-VERSION_ARCH.deb --deb-no-default-config-files $(BASEFPM) $(DEB_FPM) usr/
128 | 	rm -rf out/$(RELEASE_VERSION)/deb32/
129 | 
130 | out/$(RELEASE_VERSION)/websocketd-$(RELEASE_VERSION)_amd64.deb:  $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd
131 | 	mkdir -p out/$(RELEASE_VERSION)/deb64/{usr/bin,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
132 | 	cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/deb64/usr/bin/
133 | 	cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/deb64/usr/share/doc/websocketd-$(RELEASE_VERSION)
134 | 	cat websocketd.man | gzip > out/$(RELEASE_VERSION)/deb64/usr/share/man/man1/websocket.1.gz
135 | 	fpm -f -s dir -t deb -a amd64 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/deb64/ -p out/$(RELEASE_VERSION)/websocketd-VERSION_ARCH.deb --deb-no-default-config-files $(BASEFPM) $(DEB_FPM) usr/
136 | 	rm -rf out/$(RELEASE_VERSION)/deb64/
137 | 
138 | out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).x86_64.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_amd64/websocketd
139 | 	mkdir -p out/$(RELEASE_VERSION)/rpm64/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
140 | 	cp out/$(RELEASE_VERSION)/linux_amd64/websocketd out/$(RELEASE_VERSION)/rpm64/usr/bin/
141 | 	cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm64/usr/share/doc/websocketd-$(RELEASE_VERSION)
142 | 	cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm64/usr/share/man/man1/websocket.1.gz
143 | 	fpm -f -s dir -t rpm -a x86_64 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/rpm64/ -p out/$(RELEASE_VERSION)/websocketd.VERSION.ARCH.rpm $(BASEFPM) $(RPMFPM) usr/
144 | 	rm -rf out/$(RELEASE_VERSION)/rpm64/
145 | 
146 | out/$(RELEASE_VERSION)/websocketd.$(RELEASE_VERSION).i386.rpm: $(GO_UNPACKED) out/$(RELEASE_VERSION)/linux_386/websocketd
147 | 	mkdir -p out/$(RELEASE_VERSION)/rpm32/{usr/bin,etc/default,usr/share/man/man1,usr/share/doc/websocketd-$(RELEASE_VERSION)}
148 | 	cp out/$(RELEASE_VERSION)/linux_386/websocketd out/$(RELEASE_VERSION)/rpm32/usr/bin/
149 | 	cp ../{LICENSE,AUTHORS,CHANGES,README.md} out/$(RELEASE_VERSION)/rpm32/usr/share/doc/websocketd-$(RELEASE_VERSION)
150 | 	cat websocketd.man | gzip > out/$(RELEASE_VERSION)/rpm32/usr/share/man/man1/websocket.1.gz
151 | 	fpm -f -s dir -t rpm -a i386 -n websocketd -v $(RELEASE_VERSION) -C out/$(RELEASE_VERSION)/rpm32/ -p out/$(RELEASE_VERSION)/websocketd.VERSION.ARCH.rpm $(BASEFPM) $(RPMFPM) usr/
152 | 	rm -rf out/$(RELEASE_VERSION)/rpm32/
153 | 
154 | 
155 | # Clean up
156 | clobber: clean
157 | 	rm -rf $(GO_DIR)
158 | 
159 | 
160 | clean:
161 | 	rm -rf out
162 | 
163 | .PHONY: all build deb rpm localgo clobber clean
164 | 


--------------------------------------------------------------------------------
/libwebsocketd/console.go:
--------------------------------------------------------------------------------
  1 | // Copyright 2013 Joe Walnes and the websocketd team.
  2 | // All rights reserved.
  3 | // Use of this source code is governed by a BSD-style
  4 | // license that can be found in the LICENSE file.
  5 | 
  6 | package libwebsocketd
  7 | 
  8 | // Although this isn't particularly elegant, it's the simplest
  9 | // way to embed the console content into the binary.
 10 | 
 11 | // Note that the console is served by a single HTML file containing
 12 | // all CSS and JS inline.
 13 | // We can get by without jQuery or Bootstrap for this one ;).
 14 | 
 15 | const (
 16 | 	defaultConsoleContent = `
 17 | 
 18 | 
 25 | 
 26 | 
 27 | 
 28 | 
 29 | websocketd console
 30 | 
 31 | 
137 | 
138 | 
139 | 140 | 141 |
142 | 143 |
144 |
145 | 146 |
147 |
148 | 149 | 150 |
151 |
152 | send » 153 | 154 |
155 |
156 | 157 | 346 | 347 | ` 348 | ) 349 | 350 | var ConsoleContent = defaultConsoleContent 351 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "flag" 10 | "fmt" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "runtime" 15 | "strings" 16 | "time" 17 | 18 | "github.com/joewalnes/websocketd/libwebsocketd" 19 | ) 20 | 21 | type Config struct { 22 | Addr []string // TCP addresses to listen on. e.g. ":1234", "1.2.3.4:1234" or "[::1]:1234" 23 | MaxForks int // Number of allowable concurrent forks 24 | LogLevel libwebsocketd.LogLevel 25 | RedirPort int 26 | CertFile, KeyFile string 27 | *libwebsocketd.Config 28 | } 29 | 30 | type Arglist []string 31 | 32 | func (al *Arglist) String() string { 33 | return fmt.Sprintf("%v", []string(*al)) 34 | } 35 | 36 | func (al *Arglist) Set(value string) error { 37 | *al = append(*al, value) 38 | return nil 39 | } 40 | 41 | // Borrowed from net/http/cgi 42 | var defaultPassEnv = map[string]string{ 43 | "darwin": "PATH,DYLD_LIBRARY_PATH", 44 | "freebsd": "PATH,LD_LIBRARY_PATH", 45 | "hpux": "PATH,LD_LIBRARY_PATH,SHLIB_PATH", 46 | "irix": "PATH,LD_LIBRARY_PATH,LD_LIBRARYN32_PATH,LD_LIBRARY64_PATH", 47 | "linux": "PATH,LD_LIBRARY_PATH", 48 | "openbsd": "PATH,LD_LIBRARY_PATH", 49 | "solaris": "PATH,LD_LIBRARY_PATH,LD_LIBRARY_PATH_32,LD_LIBRARY_PATH_64", 50 | "windows": "PATH,SystemRoot,COMSPEC,PATHEXT,WINDIR", 51 | } 52 | 53 | func parseCommandLine() *Config { 54 | var mainConfig Config 55 | var config libwebsocketd.Config 56 | 57 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 58 | flag.CommandLine.Usage = func() {} 59 | 60 | // If adding new command line options, also update the help text in help.go. 61 | // The flag library's auto-generate help message isn't pretty enough. 62 | 63 | addrlist := Arglist(make([]string, 0, 1)) // pre-reserve for 1 address 64 | flag.Var(&addrlist, "address", "Interfaces to bind to (e.g. 127.0.0.1 or [::1]).") 65 | 66 | // server config options 67 | portFlag := flag.Int("port", 0, "HTTP port to listen on") 68 | versionFlag := flag.Bool("version", false, "Print version and exit") 69 | licenseFlag := flag.Bool("license", false, "Print license and exit") 70 | logLevelFlag := flag.String("loglevel", "access", "Log level, one of: debug, trace, access, info, error, fatal") 71 | sslFlag := flag.Bool("ssl", false, "Use TLS on listening socket (see also --sslcert and --sslkey)") 72 | sslCert := flag.String("sslcert", "", "Should point to certificate PEM file when --ssl is used") 73 | sslKey := flag.String("sslkey", "", "Should point to certificate private key file when --ssl is used") 74 | maxForksFlag := flag.Int("maxforks", 0, "Max forks, zero means unlimited") 75 | closeMsFlag := flag.Uint("closems", 0, "Time to start sending signals (0 never)") 76 | redirPortFlag := flag.Int("redirport", 0, "HTTP port to redirect to canonical --port address") 77 | 78 | // lib config options 79 | binaryFlag := flag.Bool("binary", false, "Set websocketd to experimental binary mode (default is line by line)") 80 | reverseLookupFlag := flag.Bool("reverselookup", false, "Perform reverse DNS lookups on remote clients") 81 | scriptDirFlag := flag.String("dir", "", "Base directory for WebSocket scripts") 82 | staticDirFlag := flag.String("staticdir", "", "Serve static content from this directory over HTTP") 83 | cgiDirFlag := flag.String("cgidir", "", "Serve CGI scripts from this directory over HTTP") 84 | devConsoleFlag := flag.Bool("devconsole", false, "Enable development console (cannot be used in conjunction with --staticdir)") 85 | passEnvFlag := flag.String("passenv", defaultPassEnv[runtime.GOOS], "List of envvars to pass to subprocesses (others will be cleaned out)") 86 | sameOriginFlag := flag.Bool("sameorigin", false, "Restrict upgrades if origin and host headers differ") 87 | allowOriginsFlag := flag.String("origin", "", "Restrict upgrades if origin does not match the list") 88 | 89 | headers := Arglist(make([]string, 0)) 90 | headersWs := Arglist(make([]string, 0)) 91 | headersHttp := Arglist(make([]string, 0)) 92 | flag.Var(&headers, "header", "Custom headers for any response.") 93 | flag.Var(&headersWs, "header-ws", "Custom headers for successful WebSocket upgrade responses.") 94 | flag.Var(&headersHttp, "header-http", "Custom headers for all but WebSocket upgrade HTTP responses.") 95 | 96 | err := flag.CommandLine.Parse(os.Args[1:]) 97 | if err != nil { 98 | if err == flag.ErrHelp { 99 | PrintHelp() 100 | os.Exit(0) 101 | } else { 102 | ShortHelp() 103 | os.Exit(2) 104 | } 105 | } 106 | 107 | port := *portFlag 108 | if port == 0 { 109 | if *sslFlag { 110 | port = 443 111 | } else { 112 | port = 80 113 | } 114 | } 115 | 116 | if socknum := len(addrlist); socknum != 0 { 117 | mainConfig.Addr = make([]string, socknum) 118 | for i, addrSingle := range addrlist { 119 | mainConfig.Addr[i] = fmt.Sprintf("%s:%d", addrSingle, port) 120 | } 121 | } else { 122 | mainConfig.Addr = []string{fmt.Sprintf(":%d", port)} 123 | } 124 | mainConfig.MaxForks = *maxForksFlag 125 | mainConfig.RedirPort = *redirPortFlag 126 | mainConfig.LogLevel = libwebsocketd.LevelFromString(*logLevelFlag) 127 | if mainConfig.LogLevel == libwebsocketd.LogUnknown { 128 | fmt.Printf("Incorrect loglevel flag '%s'. Use --help to see allowed values.\n", *logLevelFlag) 129 | ShortHelp() 130 | os.Exit(1) 131 | } 132 | 133 | config.Headers = []string(headers) 134 | config.HeadersWs = []string(headersWs) 135 | config.HeadersHTTP = []string(headersHttp) 136 | 137 | config.CloseMs = *closeMsFlag 138 | config.Binary = *binaryFlag 139 | config.ReverseLookup = *reverseLookupFlag 140 | config.Ssl = *sslFlag 141 | config.ScriptDir = *scriptDirFlag 142 | config.StaticDir = *staticDirFlag 143 | config.CgiDir = *cgiDirFlag 144 | config.DevConsole = *devConsoleFlag 145 | config.StartupTime = time.Now() 146 | config.ServerSoftware = fmt.Sprintf("websocketd/%s", Version()) 147 | config.HandshakeTimeout = time.Millisecond * 1500 // only default for now 148 | 149 | if len(os.Args) == 1 { 150 | fmt.Printf("Command line arguments are missing.\n") 151 | ShortHelp() 152 | os.Exit(1) 153 | } 154 | 155 | if *versionFlag { 156 | fmt.Printf("%s %s\n", HelpProcessName(), Version()) 157 | os.Exit(0) 158 | } 159 | 160 | if *licenseFlag { 161 | fmt.Printf("%s %s\n", HelpProcessName(), Version()) 162 | fmt.Printf("%s\n", libwebsocketd.License) 163 | os.Exit(0) 164 | } 165 | 166 | // Reading SSL options 167 | if config.Ssl { 168 | if *sslCert == "" || *sslKey == "" { 169 | fmt.Fprintf(os.Stderr, "Please specify both --sslcert and --sslkey when requesting --ssl.\n") 170 | os.Exit(1) 171 | } 172 | } else { 173 | if *sslCert != "" || *sslKey != "" { 174 | fmt.Fprintf(os.Stderr, "You should not be using --ssl* flags when there is no --ssl option.\n") 175 | os.Exit(1) 176 | } 177 | } 178 | 179 | mainConfig.CertFile = *sslCert 180 | mainConfig.KeyFile = *sslKey 181 | 182 | // Building config.ParentEnv to avoid calling Environ all the time in the scripts 183 | // (caller is responsible for wiping environment if desired) 184 | config.ParentEnv = make([]string, 0) 185 | newlineCleaner := strings.NewReplacer("\n", " ", "\r", " ") 186 | for _, key := range strings.Split(*passEnvFlag, ",") { 187 | if key != "HTTPS" { 188 | if v := os.Getenv(key); v != "" { 189 | // inevitably adding flavor of libwebsocketd appendEnv func. 190 | // it's slightly nicer than in net/http/cgi implementation 191 | if clean := strings.TrimSpace(newlineCleaner.Replace(v)); clean != "" { 192 | config.ParentEnv = append(config.ParentEnv, fmt.Sprintf("%s=%s", key, clean)) 193 | } 194 | } 195 | } 196 | } 197 | 198 | if *allowOriginsFlag != "" { 199 | config.AllowOrigins = strings.Split(*allowOriginsFlag, ",") 200 | } 201 | config.SameOrigin = *sameOriginFlag 202 | 203 | args := flag.Args() 204 | if len(args) < 1 && config.ScriptDir == "" && config.StaticDir == "" && config.CgiDir == "" { 205 | fmt.Fprintf(os.Stderr, "Please specify COMMAND or provide --dir, --staticdir or --cgidir argument.\n") 206 | ShortHelp() 207 | os.Exit(1) 208 | } 209 | 210 | if len(args) > 0 { 211 | if config.ScriptDir != "" { 212 | fmt.Fprintf(os.Stderr, "Ambiguous. Provided COMMAND and --dir argument. Please only specify just one.\n") 213 | ShortHelp() 214 | os.Exit(1) 215 | } 216 | if path, err := exec.LookPath(args[0]); err == nil { 217 | config.CommandName = path // This can be command in PATH that we are able to execute 218 | config.CommandArgs = flag.Args()[1:] 219 | config.UsingScriptDir = false 220 | } else { 221 | fmt.Fprintf(os.Stderr, "Unable to locate specified COMMAND '%s' in OS path.\n", args[0]) 222 | ShortHelp() 223 | os.Exit(1) 224 | } 225 | } 226 | 227 | if config.ScriptDir != "" { 228 | scriptDir, err := filepath.Abs(config.ScriptDir) 229 | if err != nil { 230 | fmt.Fprintf(os.Stderr, "Could not resolve absolute path to dir '%s'.\n", config.ScriptDir) 231 | ShortHelp() 232 | os.Exit(1) 233 | } 234 | inf, err := os.Stat(scriptDir) 235 | if err != nil { 236 | fmt.Fprintf(os.Stderr, "Could not find your script dir '%s'.\n", config.ScriptDir) 237 | ShortHelp() 238 | os.Exit(1) 239 | } 240 | if !inf.IsDir() { 241 | fmt.Fprintf(os.Stderr, "Did you mean to specify COMMAND instead of --dir '%s'?\n", config.ScriptDir) 242 | ShortHelp() 243 | os.Exit(1) 244 | } else { 245 | config.ScriptDir = scriptDir 246 | config.UsingScriptDir = true 247 | } 248 | } 249 | 250 | if config.CgiDir != "" { 251 | if inf, err := os.Stat(config.CgiDir); err != nil || !inf.IsDir() { 252 | fmt.Fprintf(os.Stderr, "Your CGI dir '%s' is not pointing to an accessible directory.\n", config.CgiDir) 253 | ShortHelp() 254 | os.Exit(1) 255 | } 256 | } 257 | 258 | if config.StaticDir != "" { 259 | if inf, err := os.Stat(config.StaticDir); err != nil || !inf.IsDir() { 260 | fmt.Fprintf(os.Stderr, "Your static dir '%s' is not pointing to an accessible directory.\n", config.StaticDir) 261 | ShortHelp() 262 | os.Exit(1) 263 | } 264 | } 265 | 266 | mainConfig.Config = &config 267 | 268 | return &mainConfig 269 | } 270 | -------------------------------------------------------------------------------- /examples/lua/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2015 rxi 5 | -- 6 | -- This library is free software; you can redistribute it and/or modify it 7 | -- under the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | local json = { _version = "0.1.0" } 11 | 12 | ------------------------------------------------------------------------------- 13 | -- Encode 14 | ------------------------------------------------------------------------------- 15 | 16 | local encode 17 | 18 | local escape_char_map = { 19 | [ "\\" ] = "\\\\", 20 | [ "\"" ] = "\\\"", 21 | [ "\b" ] = "\\b", 22 | [ "\f" ] = "\\f", 23 | [ "\n" ] = "\\n", 24 | [ "\r" ] = "\\r", 25 | [ "\t" ] = "\\t", 26 | } 27 | 28 | local escape_char_map_inv = { [ "\\/" ] = "/" } 29 | for k, v in pairs(escape_char_map) do 30 | escape_char_map_inv[v] = k 31 | end 32 | 33 | 34 | local function escape_char(c) 35 | return escape_char_map[c] or string.format("\\u%04x", c:byte()) 36 | end 37 | 38 | 39 | local function encode_nil(val) 40 | return "null" 41 | end 42 | 43 | 44 | local function encode_table(val, stack) 45 | local res = {} 46 | stack = stack or {} 47 | 48 | -- Circular reference? 49 | if stack[val] then error("circular reference") end 50 | 51 | stack[val] = true 52 | 53 | if val[1] ~= nil or next(val) == nil then 54 | -- Treat as array -- check keys are valid and it is not sparse 55 | local n = 0 56 | for k in pairs(val) do 57 | if type(k) ~= "number" then 58 | error("invalid table: mixed or invalid key types") 59 | end 60 | n = n + 1 61 | end 62 | if n ~= #val then 63 | error("invalid table: sparse array") 64 | end 65 | -- Encode 66 | for i, v in ipairs(val) do 67 | table.insert(res, encode(v, stack)) 68 | end 69 | stack[val] = nil 70 | return "[" .. table.concat(res, ",") .. "]" 71 | 72 | else 73 | -- Treat as an object 74 | for k, v in pairs(val) do 75 | if type(k) ~= "string" then 76 | error("invalid table: mixed or invalid key types") 77 | end 78 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 79 | end 80 | stack[val] = nil 81 | return "{" .. table.concat(res, ",") .. "}" 82 | end 83 | end 84 | 85 | 86 | local function encode_string(val) 87 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 88 | end 89 | 90 | 91 | local function encode_number(val) 92 | -- Check for NaN, -inf and inf 93 | if val ~= val or val <= -math.huge or val >= math.huge then 94 | error("unexpected number value '" .. tostring(val) .. "'") 95 | end 96 | return string.format("%.14g", val) 97 | end 98 | 99 | 100 | local type_func_map = { 101 | [ "nil" ] = encode_nil, 102 | [ "table" ] = encode_table, 103 | [ "string" ] = encode_string, 104 | [ "number" ] = encode_number, 105 | [ "boolean" ] = tostring, 106 | } 107 | 108 | 109 | encode = function(val, stack) 110 | local t = type(val) 111 | local f = type_func_map[t] 112 | if f then 113 | return f(val, stack) 114 | end 115 | error("unexpected type '" .. t .. "'") 116 | end 117 | 118 | 119 | function json.encode(val) 120 | return ( encode(val) ) 121 | end 122 | 123 | 124 | ------------------------------------------------------------------------------- 125 | -- Decode 126 | ------------------------------------------------------------------------------- 127 | 128 | local parse 129 | 130 | local function create_set(...) 131 | local res = {} 132 | for i = 1, select("#", ...) do 133 | res[ select(i, ...) ] = true 134 | end 135 | return res 136 | end 137 | 138 | local space_chars = create_set(" ", "\t", "\r", "\n") 139 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 140 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 141 | local literals = create_set("true", "false", "null") 142 | 143 | local literal_map = { 144 | [ "true" ] = true, 145 | [ "false" ] = false, 146 | [ "null" ] = nil, 147 | } 148 | 149 | 150 | local function next_char(str, idx, set, negate) 151 | for i = idx, #str do 152 | if set[str:sub(i, i)] ~= negate then 153 | return i 154 | end 155 | end 156 | return #str + 1 157 | end 158 | 159 | 160 | local function decode_error(str, idx, msg) 161 | local line_count = 1 162 | local col_count = 1 163 | for i = 1, idx - 1 do 164 | col_count = col_count + 1 165 | if str:sub(i, i) == "\n" then 166 | line_count = line_count + 1 167 | col_count = 1 168 | end 169 | end 170 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 171 | end 172 | 173 | 174 | local function codepoint_to_utf8(n) 175 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 176 | local f = math.floor 177 | if n <= 0x7f then 178 | return string.char(n) 179 | elseif n <= 0x7ff then 180 | return string.char(f(n / 64) + 192, n % 64 + 128) 181 | elseif n <= 0xffff then 182 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 183 | elseif n <= 0x10ffff then 184 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 185 | f(n % 4096 / 64) + 128, n % 64 + 128) 186 | end 187 | error( string.format("invalid unicode codepoint '%x'", n) ) 188 | end 189 | 190 | 191 | local function parse_unicode_escape(s) 192 | local n1 = tonumber( s:sub(3, 6), 16 ) 193 | local n2 = tonumber( s:sub(9, 12), 16 ) 194 | -- Surrogate pair? 195 | if n2 then 196 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 197 | else 198 | return codepoint_to_utf8(n1) 199 | end 200 | end 201 | 202 | 203 | local function parse_string(str, i) 204 | local has_unicode_escape = false 205 | local has_surrogate_escape = false 206 | local has_escape = false 207 | local last 208 | for j = i + 1, #str do 209 | local x = str:byte(j) 210 | 211 | if x < 32 then 212 | decode_error(str, j, "control character in string") 213 | end 214 | 215 | if last == 92 then -- "\\" (escape char) 216 | if x == 117 then -- "u" (unicode escape sequence) 217 | local hex = str:sub(j + 1, j + 5) 218 | if not hex:find("%x%x%x%x") then 219 | decode_error(str, j, "invalid unicode escape in string") 220 | end 221 | if hex:find("^[dD][89aAbB]") then 222 | has_surrogate_escape = true 223 | else 224 | has_unicode_escape = true 225 | end 226 | else 227 | local c = string.char(x) 228 | if not escape_chars[c] then 229 | decode_error(str, j, "invalid escape char '" .. c .. "' in string") 230 | end 231 | has_escape = true 232 | end 233 | last = nil 234 | 235 | elseif x == 34 then -- '"' (end of string) 236 | local s = str:sub(i + 1, j - 1) 237 | if has_surrogate_escape then 238 | s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) 239 | end 240 | if has_unicode_escape then 241 | s = s:gsub("\\u....", parse_unicode_escape) 242 | end 243 | if has_escape then 244 | s = s:gsub("\\.", escape_char_map_inv) 245 | end 246 | return s, j + 1 247 | 248 | else 249 | last = x 250 | end 251 | end 252 | decode_error(str, i, "expected closing quote for string") 253 | end 254 | 255 | 256 | local function parse_number(str, i) 257 | local x = next_char(str, i, delim_chars) 258 | local s = str:sub(i, x - 1) 259 | local n = tonumber(s) 260 | if not n then 261 | decode_error(str, i, "invalid number '" .. s .. "'") 262 | end 263 | return n, x 264 | end 265 | 266 | 267 | local function parse_literal(str, i) 268 | local x = next_char(str, i, delim_chars) 269 | local word = str:sub(i, x - 1) 270 | if not literals[word] then 271 | decode_error(str, i, "invalid literal '" .. word .. "'") 272 | end 273 | return literal_map[word], x 274 | end 275 | 276 | 277 | local function parse_array(str, i) 278 | local res = {} 279 | local n = 1 280 | i = i + 1 281 | while 1 do 282 | local x 283 | i = next_char(str, i, space_chars, true) 284 | -- Empty / end of array? 285 | if str:sub(i, i) == "]" then 286 | i = i + 1 287 | break 288 | end 289 | -- Read token 290 | x, i = parse(str, i) 291 | res[n] = x 292 | n = n + 1 293 | -- Next token 294 | i = next_char(str, i, space_chars, true) 295 | local chr = str:sub(i, i) 296 | i = i + 1 297 | if chr == "]" then break end 298 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 299 | end 300 | return res, i 301 | end 302 | 303 | 304 | local function parse_object(str, i) 305 | local res = {} 306 | i = i + 1 307 | while 1 do 308 | local key, val 309 | i = next_char(str, i, space_chars, true) 310 | -- Empty / end of object? 311 | if str:sub(i, i) == "}" then 312 | i = i + 1 313 | break 314 | end 315 | -- Read key 316 | if str:sub(i, i) ~= '"' then 317 | decode_error(str, i, "expected string for key") 318 | end 319 | key, i = parse(str, i) 320 | -- Read ':' delimiter 321 | i = next_char(str, i, space_chars, true) 322 | if str:sub(i, i) ~= ":" then 323 | decode_error(str, i, "expected ':' after key") 324 | end 325 | i = next_char(str, i + 1, space_chars, true) 326 | -- Read value 327 | val, i = parse(str, i) 328 | -- Set 329 | res[key] = val 330 | -- Next token 331 | i = next_char(str, i, space_chars, true) 332 | local chr = str:sub(i, i) 333 | i = i + 1 334 | if chr == "}" then break end 335 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 336 | end 337 | return res, i 338 | end 339 | 340 | 341 | local char_func_map = { 342 | [ '"' ] = parse_string, 343 | [ "0" ] = parse_number, 344 | [ "1" ] = parse_number, 345 | [ "2" ] = parse_number, 346 | [ "3" ] = parse_number, 347 | [ "4" ] = parse_number, 348 | [ "5" ] = parse_number, 349 | [ "6" ] = parse_number, 350 | [ "7" ] = parse_number, 351 | [ "8" ] = parse_number, 352 | [ "9" ] = parse_number, 353 | [ "-" ] = parse_number, 354 | [ "t" ] = parse_literal, 355 | [ "f" ] = parse_literal, 356 | [ "n" ] = parse_literal, 357 | [ "[" ] = parse_array, 358 | [ "{" ] = parse_object, 359 | } 360 | 361 | 362 | parse = function(str, idx) 363 | local chr = str:sub(idx, idx) 364 | local f = char_func_map[chr] 365 | if f then 366 | return f(str, idx) 367 | end 368 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 369 | end 370 | 371 | 372 | function json.decode(str) 373 | if type(str) ~= "string" then 374 | error("expected argument of type string, got " .. type(str)) 375 | end 376 | return ( parse(str, next_char(str, 1, space_chars, true)) ) 377 | end 378 | 379 | 380 | return json 381 | -------------------------------------------------------------------------------- /libwebsocketd/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Joe Walnes and the websocketd team. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package libwebsocketd 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "net" 12 | "net/http" 13 | "net/http/cgi" 14 | "net/textproto" 15 | "net/url" 16 | "os" 17 | "path" 18 | "path/filepath" 19 | "regexp" 20 | "strings" 21 | 22 | "github.com/gorilla/websocket" 23 | ) 24 | 25 | var ForkNotAllowedError = errors.New("too many forks active") 26 | 27 | // WebsocketdServer presents http.Handler interface for requests libwebsocketd is handling. 28 | type WebsocketdServer struct { 29 | Config *Config 30 | Log *LogScope 31 | forks chan byte 32 | } 33 | 34 | // NewWebsocketdServer creates WebsocketdServer struct with pre-determined config, logscope and maxforks limit 35 | func NewWebsocketdServer(config *Config, log *LogScope, maxforks int) *WebsocketdServer { 36 | mux := &WebsocketdServer{ 37 | Config: config, 38 | Log: log, 39 | } 40 | if maxforks > 0 { 41 | mux.forks = make(chan byte, maxforks) 42 | } 43 | return mux 44 | } 45 | 46 | func splitMimeHeader(s string) (string, string) { 47 | p := strings.IndexByte(s, ':') 48 | if p < 0 { 49 | return s, "" 50 | } 51 | key := textproto.CanonicalMIMEHeaderKey(s[:p]) 52 | 53 | for p = p + 1; p < len(s); p++ { 54 | if s[p] != ' ' { 55 | break 56 | } 57 | } 58 | return key, s[p:] 59 | } 60 | 61 | func pushHeaders(h http.Header, hdrs []string) { 62 | for _, hstr := range hdrs { 63 | h.Add(splitMimeHeader(hstr)) 64 | } 65 | } 66 | 67 | // ServeHTTP muxes between WebSocket handler, CGI handler, DevConsole, Static HTML or 404. 68 | func (h *WebsocketdServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 69 | log := h.Log.NewLevel(h.Log.LogFunc) 70 | log.Associate("url", h.TellURL("http", req.Host, req.RequestURI)) 71 | 72 | if h.Config.CommandName != "" || h.Config.UsingScriptDir { 73 | hdrs := req.Header 74 | upgradeRe := regexp.MustCompile(`(?i)(^|[,\s])Upgrade($|[,\s])`) 75 | // WebSocket, limited to size of h.forks 76 | if strings.ToLower(hdrs.Get("Upgrade")) == "websocket" && upgradeRe.MatchString(hdrs.Get("Connection")) { 77 | if h.noteForkCreated() == nil { 78 | defer h.noteForkCompled() 79 | 80 | // start figuring out if we even need to upgrade 81 | handler, err := NewWebsocketdHandler(h, req, log) 82 | if err != nil { 83 | if err == ScriptNotFoundError { 84 | log.Access("session", "NOT FOUND: %s", err) 85 | http.Error(w, "404 Not Found", 404) 86 | } else { 87 | log.Access("session", "INTERNAL ERROR: %s", err) 88 | http.Error(w, "500 Internal Server Error", 500) 89 | } 90 | return 91 | } 92 | 93 | var headers http.Header 94 | if len(h.Config.Headers)+len(h.Config.HeadersWs) > 0 { 95 | headers = http.Header(make(map[string][]string)) 96 | pushHeaders(headers, h.Config.Headers) 97 | pushHeaders(headers, h.Config.HeadersWs) 98 | } 99 | 100 | upgrader := &websocket.Upgrader{ 101 | HandshakeTimeout: h.Config.HandshakeTimeout, 102 | CheckOrigin: func(r *http.Request) bool { 103 | // backporting previous checkorigin for use in gorilla/websocket for now 104 | err := checkOrigin(req, h.Config, log) 105 | return err == nil 106 | }, 107 | } 108 | conn, err := upgrader.Upgrade(w, req, headers) 109 | if err != nil { 110 | log.Access("session", "Unable to Upgrade: %s", err) 111 | http.Error(w, "500 Internal Error", 500) 112 | return 113 | } 114 | 115 | // old func was used in x/net/websocket style, we reuse it here for gorilla/websocket 116 | handler.accept(conn, log) 117 | return 118 | 119 | } else { 120 | log.Error("http", "Max of possible forks already active, upgrade rejected") 121 | http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests) 122 | } 123 | return 124 | } 125 | } 126 | 127 | pushHeaders(w.Header(), h.Config.HeadersHTTP) 128 | 129 | // Dev console (if enabled) 130 | if h.Config.DevConsole { 131 | log.Access("http", "DEVCONSOLE") 132 | content := ConsoleContent 133 | content = strings.Replace(content, "{{license}}", License, -1) 134 | content = strings.Replace(content, "{{addr}}", h.TellURL("ws", req.Host, req.RequestURI), -1) 135 | http.ServeContent(w, req, ".html", h.Config.StartupTime, strings.NewReader(content)) 136 | return 137 | } 138 | 139 | // CGI scripts, limited to size of h.forks 140 | if h.Config.CgiDir != "" { 141 | filePath := path.Join(h.Config.CgiDir, fmt.Sprintf(".%s", filepath.FromSlash(req.URL.Path))) 142 | if fi, err := os.Stat(filePath); err == nil && !fi.IsDir() { 143 | 144 | log.Associate("cgiscript", filePath) 145 | if h.noteForkCreated() == nil { 146 | defer h.noteForkCompled() 147 | 148 | // Make variables to supplement cgi... Environ it uses will show empty list. 149 | envlen := len(h.Config.ParentEnv) 150 | cgienv := make([]string, envlen+1) 151 | if envlen > 0 { 152 | copy(cgienv, h.Config.ParentEnv) 153 | } 154 | cgienv[envlen] = "SERVER_SOFTWARE=" + h.Config.ServerSoftware 155 | cgiHandler := &cgi.Handler{ 156 | Path: filePath, 157 | Env: []string{ 158 | "SERVER_SOFTWARE=" + h.Config.ServerSoftware, 159 | }, 160 | } 161 | log.Access("http", "CGI") 162 | cgiHandler.ServeHTTP(w, req) 163 | } else { 164 | log.Error("http", "Fork not allowed since maxforks amount has been reached. CGI was not run.") 165 | http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests) 166 | } 167 | return 168 | } 169 | } 170 | 171 | // Static files 172 | if h.Config.StaticDir != "" { 173 | handler := http.FileServer(http.Dir(h.Config.StaticDir)) 174 | log.Access("http", "STATIC") 175 | handler.ServeHTTP(w, req) 176 | return 177 | } 178 | 179 | // 404 180 | log.Access("http", "NOT FOUND") 181 | http.NotFound(w, req) 182 | } 183 | 184 | var canonicalHostname string 185 | 186 | // TellURL is a helper function that changes http to https or ws to wss in case if SSL is used 187 | func (h *WebsocketdServer) TellURL(scheme, host, path string) string { 188 | if len(host) > 0 && host[0] == ':' { 189 | if canonicalHostname == "" { 190 | var err error 191 | canonicalHostname, err = os.Hostname() 192 | if err != nil { 193 | canonicalHostname = "UNKNOWN" 194 | } 195 | } 196 | host = canonicalHostname + host 197 | } 198 | if h.Config.Ssl { 199 | return scheme + "s://" + host + path 200 | } 201 | return scheme + "://" + host + path 202 | } 203 | 204 | func (h *WebsocketdServer) noteForkCreated() error { 205 | // note that forks can be nil since the construct could've been created by 206 | // someone who is not using NewWebsocketdServer 207 | if h.forks != nil { 208 | select { 209 | case h.forks <- 1: 210 | return nil 211 | default: 212 | return ForkNotAllowedError 213 | } 214 | } else { 215 | return nil 216 | } 217 | } 218 | 219 | func (h *WebsocketdServer) noteForkCompled() { 220 | if h.forks != nil { // see comment in noteForkCreated 221 | select { 222 | case <-h.forks: 223 | return 224 | default: 225 | // This could only happen if the completion handler called more times than creation handler above 226 | // Code should be audited to not allow this to happen, it's desired to have test that would 227 | // make sure this is impossible but it is not exist yet. 228 | panic("Cannot deplet number of allowed forks, something is not right in code!") 229 | } 230 | } 231 | } 232 | 233 | func checkOrigin(req *http.Request, config *Config, log *LogScope) (err error) { 234 | // CONVERT GORILLA: 235 | // this is origin checking function, it's called from wshandshake which is from ServeHTTP main handler 236 | // should be trivial to reuse in gorilla's upgrader.CheckOrigin function. 237 | // Only difference is to parse request and fetching passed Origin header out of it instead of using 238 | // pre-parsed wsconf.Origin 239 | 240 | // check for origin to be correct in future 241 | // handshaker triggers answering with 403 if error was returned 242 | // We keep behavior of original handshaker that populates this field 243 | origin := req.Header.Get("Origin") 244 | if origin == "" || (origin == "null" && config.AllowOrigins == nil) { 245 | // we don't want to trust string "null" if there is any 246 | // enforcements are active 247 | origin = "file:" 248 | } 249 | 250 | originParsed, err := url.ParseRequestURI(origin) 251 | if err != nil { 252 | log.Access("session", "Origin parsing error: %s", err) 253 | return err 254 | } 255 | 256 | log.Associate("origin", originParsed.String()) 257 | 258 | // If some origin restrictions are present: 259 | if config.SameOrigin || config.AllowOrigins != nil { 260 | originServer, originPort, err := tellHostPort(originParsed.Host, originParsed.Scheme == "https") 261 | if err != nil { 262 | log.Access("session", "Origin hostname parsing error: %s", err) 263 | return err 264 | } 265 | if config.SameOrigin { 266 | localServer, localPort, err := tellHostPort(req.Host, req.TLS != nil) 267 | if err != nil { 268 | log.Access("session", "Request hostname parsing error: %s", err) 269 | return err 270 | } 271 | if originServer != localServer || originPort != localPort { 272 | log.Access("session", "Same origin policy mismatch") 273 | return fmt.Errorf("same origin policy violated") 274 | } 275 | } 276 | if config.AllowOrigins != nil { 277 | matchFound := false 278 | for _, allowed := range config.AllowOrigins { 279 | if pos := strings.Index(allowed, "://"); pos > 0 { 280 | // allowed schema has to match 281 | allowedURL, err := url.Parse(allowed) 282 | if err != nil { 283 | continue // pass bad URLs in origin list 284 | } 285 | if allowedURL.Scheme != originParsed.Scheme { 286 | continue // mismatch 287 | } 288 | allowed = allowed[pos+3:] 289 | } 290 | allowServer, allowPort, err := tellHostPort(allowed, false) 291 | if err != nil { 292 | continue // unparseable 293 | } 294 | if allowPort == "80" && allowed[len(allowed)-3:] != ":80" { 295 | // any port is allowed, host names need to match 296 | matchFound = allowServer == originServer 297 | } else { 298 | // exact match of host names and ports 299 | matchFound = allowServer == originServer && allowPort == originPort 300 | } 301 | if matchFound { 302 | break 303 | } 304 | } 305 | if !matchFound { 306 | log.Access("session", "Origin is not listed in allowed list") 307 | return fmt.Errorf("origin list matches were not found") 308 | } 309 | } 310 | } 311 | return nil 312 | } 313 | 314 | func tellHostPort(host string, ssl bool) (server, port string, err error) { 315 | server, port, err = net.SplitHostPort(host) 316 | if err != nil { 317 | if addrerr, ok := err.(*net.AddrError); ok && strings.Contains(addrerr.Err, "missing port") { 318 | server = host 319 | if ssl { 320 | port = "443" 321 | } else { 322 | port = "80" 323 | } 324 | err = nil 325 | } 326 | } 327 | return server, port, err 328 | } 329 | --------------------------------------------------------------------------------