Project

General

Profile

Actions

mod_wstunnel

Module: mod_wstunnel

(since lighttpd 1.4.46)

WebSocket tunnel endpoint. This module terminates the websocket tunnel from a client. This module then passes data (without websocket frames) to a backend and encodes responses from backend in websocket frames before sending responses to client.

Note: if looking to proxy websockets to a backend, then see Docs_ModProxy, Docs_ModCGI, or other backends, some of which can be enabled to be transparent proxies to backends after client sends Upgrade: websocket

Description

Brief description of mod_wstunnel directives

option description
wstunnel.server backend server definition(s) for hosts to which to send requests; options for each backend host
wstunnel.balance load-balancing algorithm for backends ("fair", "least-connection", "round-robin", "hash", or "sticky")
wstunnel.debug debug level (value between 0 and 65535)
wstunnel.frame-type websocket frame type: "text" or "binary"
wstunnel.map-extensions map multiple extensions to the same backend
wstunnel.origins list of permitted origins in Origin request header (optional)
wstunnel.ping-interval send websocket PING frame at given interval in sec (default 0; none sent)

Details for wstunnel.server parameters can be found in mod_fastcgi documentation, since the wstunnel module shares the same code infrastructure with the FastCGI module, and fastcgi.server parameters are very similar.

Example: websocket tunnel to VNC server via noVNC client

Follow instructions to download and install, or use available packages for your Linux/*BSD distribution.
https://github.com/novnc/noVNC
x11vnc

If noVNC files are installed in /usr/share/novnc, and x11vnc is running as VNC server with x11vnc -localhost -forever -display :0 then lighttpd mod_wstunnel can be configured with:

server.document-root = "/usr/share/novnc" 
server.indexfiles = ("index.html")
server.modules += ( "mod_wstunnel" )
$HTTP["url"] =~ "^/websockify" {
    wstunnel.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "5900" ) ) )
    wstunnel.frame-type = "binary" 
    server.stream-request-body  = 2
    server.stream-response-body = 2
}

Please take proper precautions to limit access to the VNC server, possibly including requiring proper authentication and limiting access to certain source IPs.

Example: websocket tunnel to a trivial "echo" script which reads and echoes line-by-line

Simple example showing lighttpd mod_wstunnel terminating a websocket tunnel and send/receive payloads to backend "echo" script. If you are connecting from a remote system, then modify lighttpd-wstunnel.conf to comment out server.bind = "127.0.0.1", and modify the URL in new WebSocket('ws://localhost:8081/ws/') in count.html to connect to the target server.

  • /dev/shm/echo.pl
    #!/usr/bin/perl -Tw
    $SIG{PIPE} = 'IGNORE';
    for (my $FH; accept($FH, STDIN); close $FH) {
        select($FH); $|=1; # $FH->autoflush;
        print $FH $_ while (<$FH>);
    }
    
  • /dev/shm/lighttpd-wstunnel.conf (listening on port 8081; start with lighttpd -D -f /dev/shm/lighttpd-wstunnel.conf; Ctrl-C to quit)
    server.document-root = "/tmp"  # not used in this example
    server.bind = "127.0.0.1" 
    server.port = 8081
    mimetype.assign = (".txt" => "text/plain", ".html" => "text/html" )
    
    server.modules += ("mod_wstunnel")
    wstunnel.server = (
      "/ws/" => (
        (
          "socket" => "/dev/shm/psock",
          "bin-path" => "/dev/shm/echo.pl",
          "max-procs" => 1
        )
      )
    )
    
  • /dev/shm/count.html (load into local browser as ///dev/shm/count.html and watch it count 1 .. 5; connects to localhost:8080)
    (Alternatively, place in /tmp/count.html (i.e. under server.document-root) and load in browser as http://127.0.0.1:8080/count.html)
    <!DOCTYPE html>
    <!-- modified from example in https://github.com/joewalnes/websocketd README.md -->
    <pre id="log"></pre>
    <script>
      // helper function: log message to screen
      var logelt = document.getElementById('log');
      function log(msg) { logelt.textContent += msg + '\n'; }
      // helper function: send websocket msg with count (1 .. 5)
      var ll = 0;
      function send_msg() { if (++ll <= 5) { log('SEND: '+ll); ws.send(ll+'\n'); } }
      // setup websocket with callbacks
      var ws = new WebSocket('ws://localhost:8081/ws/');
      ws.onopen = function()         { log('CONNECT\n'); send_msg(); };
      ws.onclose = function()        { log('DISCONNECT'); };
      ws.onmessage = function(event) { log('RECV: ' + event.data); send_msg(); };
    </script>
    

Example: proxied websocket tunnel to a trivial "echo" script which reads and echoes line-by-line

Simple example showing lighttpd mod_proxy supporting websockets to another lighttpd instance using mod_wstunnel to terminate the websocket tunnel and send/receive payloads to backend "echo" script. This example demonstrates WebSockets works through mod_proxy and through mod_wstunnel. /dev/shm/echo.pl and /dev/shm/lighttpd-wstunnel.conf are the same as above. Please note that count.html target has been modified to use port 8080, not 8081 in this example. If you are connecting from a remote system, then modify lighttpd-proxy.conf to comment out server.bind = "127.0.0.1", and modify the URL in new WebSocket('ws://localhost:8080/ws/') in count.html to connect to the target server.

  • /dev/shm/echo.pl
    #!/usr/bin/perl -Tw
    $SIG{PIPE} = 'IGNORE';
    for (my $FH; accept($FH, STDIN); close $FH) {
        select($FH); $|=1; # $FH->autoflush;
        print $FH $_ while (<$FH>);
    }
    
  • /dev/shm/lighttpd-wstunnel.conf (listening on port 8081; start with lighttpd -D -f /dev/shm/lighttpd-wstunnel.conf; Ctrl-C to quit)
    server.document-root = "/tmp"  # not used in this example
    server.bind = "127.0.0.1" 
    server.port = 8081
    mimetype.assign = (".txt" => "text/plain", ".html" => "text/html" )
    
    server.modules += ("mod_wstunnel")
    wstunnel.server = (
      "/ws/" => (
        (
          "socket" => "/dev/shm/psock",
          "bin-path" => "/dev/shm/echo.pl",
          "max-procs" => 1
        )
      )
    )
    
  • /dev/shm/lighttpd-proxy.conf (listening on port 8080; start with lighttpd -D -f /dev/shm/lighttpd-proxy.conf in another shell; Ctrl-C to quit)
    server.document-root = "/tmp"  # not used in this example
    server.bind = "127.0.0.1" 
    server.port = 8080
    
    server.modules += ("mod_proxy")
    proxy.server = ( "/" => (( "host" => "127.0.0.1", "port" => "8081" )))
    proxy.header = ( "upgrade" => "enable" )
    
  • /dev/shm/count.html (load into local browser as ///dev/shm/count.html and watch it count 1 .. 5; connects to localhost:8080)
    (Alternatively, place in /tmp/count.html (i.e. under server.document-root) and load in browser as http://127.0.0.1:8080/count.html)
    <!DOCTYPE html>
    <!-- modified from example in https://github.com/joewalnes/websocketd README.md -->
    <pre id="log"></pre>
    <script>
      // helper function: log message to screen
      var logelt = document.getElementById('log');
      function log(msg) { logelt.textContent += msg + '\n'; }
      // helper function: send websocket msg with count (1 .. 5)
      var ll = 0;
      function send_msg() { if (++ll <= 5) { log('SEND: '+ll); ws.send(ll+'\n'); } }
      // setup websocket with callbacks
      var ws = new WebSocket('ws://localhost:8080/ws/');
      ws.onopen = function()         { log('CONNECT\n'); send_msg(); };
      ws.onclose = function()        { log('DISCONNECT'); };
      ws.onmessage = function(event) { log('RECV: ' + event.data); send_msg(); };
    </script>
    
  • /dev/shm/echo-incremental.pl
    Here is an alternative sample "echo" script which is unbuffered -- instead of the simpler line-buffered example above -- and will sleep 1 second between reads so that you can visualize in the browser that multiple websocket messages are going back and forth to the server. This sample script will run for 5 seconds and then will close the websocket connection. (To run this example, use this script in place of "/dev/shm/echo.pl" and restart lighttpd running lighttpd-wstunnel.conf)
    #!/usr/bin/perl -Tw
    $SIG{PIPE} = 'IGNORE';
    for (my $FH; accept($FH, STDIN); close $FH) {
        foreach (1..5) {
            sleep 1;
            my $foo = "";
            sysread($FH, $foo, 1024);
            if (length($foo)) { syswrite($FH, $foo); }
        }
        close $FH;
    }
    

Updated by gstrauss 1 day ago ยท 14 revisions