<img height="1" width="1" src="https://www.facebook.com/tr?id=1953097804934218&amp;ev=PageView &amp;noscript=1">

Blog

Get an in-browser remote desktop with Mojolicious and noVNC

Note: This is an excerpt from my blog post originally published on PerlTricks.com

While SSH is a staple of remote system administration, sometimes only a GUI will do. Perhaps the remote system doesn’t have a terminal environment to connect to; perhaps the target application doesn’t present an adequate command line interface; perhaps there is an existing GUI session you need to interact with. There can be all kinds of reasons.

For this purpose, a generic type of remote desktop service called VNC is commonly used. The servers are easy to install, start on seemingly all platforms, and lots of hardware has a VNC server embedded for remote administration. Clients are similarly easy to use, but when building a management console in the web, wouldn’t it be nice to have the console view right in your browser?

Luckily, there is a pure JavaScript VNC client called noVNC.

0156BEFE-C9B2-11E5-A2BB-E80C3AD1818C.jpeg

noVNC listens for VNC traffic over WebSockets, which is convenient for browsers but isn’t supported by most VNC servers. To overcome this problem, they provide a command-line application called Websockify.

Websockify is a relay that connects to a TCP connection (the VNC server) and exposes the traffic as a WebSocket stream that a browser client can listen on. While this does fix the problem, it isn’t an elegant solution. Each VNC Server needs its own instance of Websockify requiring a separate port. Further, you either need to leave these connected at all times in case of a web client or else spawn them on demand and clean them up later.

Mojolicious to the Rescue

Mojolicious has a built-in event-based TCP Client and native WebSocket handling. If you are already serving your site with Mojolicious, why not let it do the TCP/WebSocket relay work too? Even if you aren’t, the on-demand nature of the solution I’m going to show would be useful as a stand-alone app for this single purpose versus the websockify application.

Here is a Mojolicious::Lite application which serves the noVNC client when you request a url like /192.168.0.1. When the page loads, the client requests the WebSocket route at /proxy?target=192.168.0.1 which establishes the bridge. This example is bundled with my forthcoming wrapper module with a working name of Mojo::Websockify. The code is remarkably simple:

use Mojolicious::Lite;

use Mojo::IOLoop;

websocket '/proxy' => sub {
  my $c = shift;
  $c->render_later->on(finish => sub { warn 'websocket closing' });

  my $tx = $c->tx;
  $tx->with_protocols('binary');

  my $host = $c->param('target') || '127.0.0.1';
  my $port = $host =~ s{:(\d+)$}{} ? $1 : 5901;

  Mojo::IOLoop->client(address => $host, port => $port, sub {
    my ($loop, $err, $tcp) = @_;

    $tx->finish(4500, "TCP connection error: $err") if $err;
    $tcp->on(error => sub { $tx->finish(4500, "TCP error: $_[1]") });

    $tcp->on(read => sub {
      my ($tcp, $bytes) = @_;
      $tx->send({binary => $bytes});
    });

    $tx->on(binary => sub {
      my ($tx, $bytes) = @_;
      $tcp->write($bytes);
    });

    $tx->on(finish => sub {
      $tcp->close;
      undef $tcp;
      undef $tx;
    });
  });
};

get '/*target' => sub {
  my $c = shift;
  my $target = $c->stash('target');
  my $url = $c->url_for('proxy')->query(target => $target);
  $url->path->leading_slash(0); # novnc assumes no leading slash :(
  $c->render(
    vnc  =>
    base => $c->tx->req->url->to_abs,
    path => $url,
  );
};

app->start;

Read the rest on PerlTricks.com.

Topics: Tips