Skip to content

Commit

Permalink
Make script/dewebsockify a Modulino
Browse files Browse the repository at this point in the history
  • Loading branch information
r-richardson committed Dec 20, 2024
1 parent c5e5f7a commit dda6a8e
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 124 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ set(PERL_FILES
OpenQA/Exceptions.pm
OpenQA/Isotovideo/Backend.pm
OpenQA/Isotovideo/CommandHandler.pm
OpenQA/Isotovideo/Dewebsockify.pm
OpenQA/Isotovideo/Interface.pm
OpenQA/Isotovideo/NeedleDownloader.pm
OpenQA/Isotovideo/Runner.pm
Expand Down
129 changes: 129 additions & 0 deletions OpenQA/Isotovideo/Dewebsockify.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Copyright SUSE LLC
# SPDX-License-Identifier: GPL-2.0-or-later
#

package OpenQA::Isotovideo::Dewebsockify;

use Mojo::Base -strict, -signatures;

use Mojo::IOLoop::Server;
use Mojo::IOLoop::Stream;
use Mojo::Log;
use Mojo::UserAgent;

sub main ($args) {
die "Arguments must be a hash reference\n" unless defined $args && ref($args) eq 'HASH';

my $ws_url = $args->{websocketurl} or die "websocket URL missing\n";
my $port = $args->{listenport} // 5900;
my $cookie = $args->{cookie} // undef;
my $log = Mojo::Log->new(level => $args->{loglevel} // 'info');

$log->debug("websocket url: $ws_url");
$log->debug("listen port: $port");
$log->debug("cookie: " . $cookie) if $cookie;

# create listen socket
my $ua = Mojo::UserAgent->new;
my $server = Mojo::IOLoop::Server->new;
my $ws_connection;
my $stream;
my @tosend;
$ua->insecure($args->{insecure} // 0);

# accept new connections
$server->on(accept => sub ($server, $handle) {
if ($stream) {
$log->info('Rejecting new client; already one client connected');
return undef;
}
$stream = Mojo::IOLoop::Stream->new($handle);
$stream->start;
$stream->reactor->start unless $stream->reactor->is_running;

# establish websocket connection
if (!$ws_connection) {
$log->info("Establishing WebSocket connection to $ws_url");
@tosend = ();
my $tx = $ua->build_websocket_tx($ws_url);
my $req = $tx->req;
my $headers = $tx->req->headers;
$req->cookies($cookie) if $cookie;
$headers->add(Pragma => 'no-cache');
$headers->add('Sec-WebSocket-Protocol' => 'binary, vmware-vvc');
$ua->start($tx => sub ($ua, $tx) {

# handle errors
if (!$tx->is_websocket) {
if (my $err = $tx->error) {
$log->error($err->{code} ? "WebSocket $err->{code} response: $err->{message}"
: "WebSocket connection error: $err->{message}");
}
else {
$log->error('Unable to upgrade to WebSocket connection');
}
my $body = $tx->res->body;
$log->trace($body) if $body;
$ws_connection = undef;
$stream->close_gracefully if $stream;
return undef;
}
$log->info('WebSocket connection established');
$tx->max_websocket_size(1024**3); # required to avoid 1009 error, at least when using raw encoding
$ws_connection = $tx;

# pass data from websocket to raw socket
$tx->on(text => sub ($tx, $text) {
$log->trace("WebSocket text message: $text");
});
$tx->on(binary => sub ($tx, $bytes) {
$log->trace("WebSocket binary message:\n" . sprintf("%v02X", $bytes));
$stream->write($bytes) if $stream;
});

# pass pending data from raw socket to websocket
$tx->send($_) for @tosend;
@tosend = ();

# handle websocket connection finish
# note: Terminating here because at least for VMWare one needed a new URL/ticket anyways.
$tx->on(finish => sub ($tx, $code, $reason) {
$log->info("WebSocket closed with status $code.");
$ws_connection = undef;
$stream->close_gracefully if $stream;
Mojo::IOLoop->stop_gracefully;
});
});
}

# pass data from raw socket to websocket
$stream->on(read => sub ($s, $bytes) {
if ($ws_connection) {
$log->debug("Raw socket message:\n" . sprintf("%v02X", $bytes)); # uncoverable statement
$ws_connection->send({binary => $bytes}); # uncoverable statement
}
else {
$log->debug("Raw socket message (forwarding later):\n" . sprintf("%v02X", $bytes)); # uncoverable statement
push @tosend, $bytes; # uncoverable statement
}
});

# handle raw socket close/error
$stream->on(close => sub ($s) {
$log->info('Client closed connection');
$stream = undef;
$ws_connection ? $ws_connection->finish : Mojo::IOLoop->stop_gracefully;
});
$stream->on(error => sub ($stream, $err) {
$log->error("Client error: $err"); # uncoverable statement
});

$log->info('Client accepted');
});
$server->listen(port => $port);
$server->start;
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
}

1;

149 changes: 25 additions & 124 deletions script/dewebsockify
Original file line number Diff line number Diff line change
Expand Up @@ -6,129 +6,30 @@
use Mojo::Base -strict, -signatures;

use Getopt::Long;
use Mojo::Cookie::Request;
use Mojo::IOLoop;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::Stream;
use Mojo::Log;
use Mojo::UserAgent;

sub usage ($r = 0) {
say 'Listens on a TCP port forwarding data to the specified websocket server
example: --websocketurl wss://... --listenport 590x --cookie "vmware_client=VMware; some_session=foobar" --insecure';
exit $r;
use FindBin;
use lib "$FindBin::Bin/.."; # Ensure OpenQA namespace is in @INC
use OpenQA::Isotovideo::Dewebsockify;

OpenQA::Isotovideo::Dewebsockify::main(parse_args()) unless caller();

sub usage () {
print <<"END_USAGE";
Usage: $0 --websocketurl <URL> [--listenport <PORT>] [--cookie <COOKIE>] [--loglevel <LEVEL>] [--insecure]
Options:
--websocketurl The WebSocket server URL (required).
--listenport Port to listen on (default: 5900).
--cookie Cookie to include in WebSocket requests.
--loglevel Log level (default: 'info').
--insecure Allow insecure WebSocket connections.
--help, -h, -? Show this help message.
END_USAGE
exit 0;
}

# read CLI args
my %options;
GetOptions(\%options, 'websocketurl=s', 'listenport=s', 'cookie=s', 'loglevel=s', 'insecure', 'help|h|?') or usage(1);
usage if $options{help};
my $ws_url = $options{websocketurl} or die "websocket URL missing\n";
my $port = $options{listenport} // 5900;
my $cookie = $options{cookie} ? $options{cookie} : undef;
my $log = Mojo::Log->new(level => $options{loglevel} // 'info');
$log->debug("websocket url: $ws_url");
$log->debug("listen port: $port");
$log->debug("cookie: " . $cookie) if $cookie;

# create listen socket
my $ua = Mojo::UserAgent->new;
my $server = Mojo::IOLoop::Server->new;
my $ws_connection;
my $stream;
my @tosend;
$ua->insecure($options{insecure} // 0);

# accept new connections
$server->on(accept => sub ($server, $handle) {
if ($stream) {
$log->info('Rejecting new client; already one client connected');
return undef;
}
$stream = Mojo::IOLoop::Stream->new($handle);
$stream->start;
$stream->reactor->start unless $stream->reactor->is_running;

# establish websocket connection
if (!$ws_connection) {
$log->info("Establishing WebSocket connection to $ws_url");
@tosend = ();
my $tx = $ua->build_websocket_tx($ws_url);
my $req = $tx->req;
my $headers = $tx->req->headers;
$req->cookies($cookie) if $cookie;
$headers->add(Pragma => 'no-cache');
$headers->add('Sec-WebSocket-Protocol' => 'binary, vmware-vvc');
$ua->start($tx => sub ($ua, $tx) {

# handle errors
if (!$tx->is_websocket) {
if (my $err = $tx->error) {
$log->error($err->{code} ? "WebSocket $err->{code} response: $err->{message}"
: "WebSocket connection error: $err->{message}");
}
else {
$log->error('Unable to upgrade to WebSocket connection');
}
my $body = $tx->res->body;
$log->trace($body) if $body;
$ws_connection = undef;
$stream->close_gracefully if $stream;
return undef;
}
$log->info('WebSocket connection established');
$tx->max_websocket_size(1024**3); # required to avoid 1009 error, at least when using raw encoding
$ws_connection = $tx;

# pass data from websocket to raw socket
$tx->on(text => sub ($tx, $text) {
$log->trace("WebSocket text message: $text");
});
$tx->on(binary => sub ($tx, $bytes) {
$log->trace("WebSocket binary message:\n" . sprintf("%v02X", $bytes));
$stream->write($bytes) if $stream;
});

# pass pending data from raw socket to websocket
$tx->send($_) for @tosend;
@tosend = ();

# handle websocket connection finish
# note: Terminating here because at least for VMWare one needed a new URL/ticket anyways.
$tx->on(finish => sub ($tx, $code, $reason) {
$log->info("WebSocket closed with status $code.");
$ws_connection = undef;
$stream->close_gracefully if $stream;
Mojo::IOLoop->stop_gracefully;
});
});
}

# pass data from raw socket to websocket
$stream->on(read => sub ($s, $bytes) {
if ($ws_connection) {
$log->debug("Raw socket message:\n" . sprintf("%v02X", $bytes)); # uncoverable statement
$ws_connection->send({binary => $bytes}); # uncoverable statement
}
else {
$log->debug("Raw socket message (forwarding later):\n" . sprintf("%v02X", $bytes)); # uncoverable statement
push @tosend, $bytes; # uncoverable statement
}
});

# handle raw socket close/error
$stream->on(close => sub ($s) {
$log->info('Client closed connection');
$stream = undef;
$ws_connection ? $ws_connection->finish : Mojo::IOLoop->stop_gracefully;
});
$stream->on(error => sub ($stream, $err) {
$log->error("Client error: $err"); # uncoverable statement
});

$log->info('Client accepted');
});

$server->listen(port => $port);
$server->start;
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
sub parse_args () {
my %options;
GetOptions(\%options, 'websocketurl=s', 'listenport=s', 'cookie=s', 'loglevel=s', 'insecure', 'help|h|?')
or usage;
usage if $options{help};
return \%options;
}
2 changes: 2 additions & 0 deletions t/data/tests/wheels.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
version: v0.1
wheels: [copy/writer]
5 changes: 5 additions & 0 deletions t/data/wheels_dir/writer/lib/Copy/Writer/Content0.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package Copy::Writer::Content0;
use Mojo::Base 'Exporter';
our @EXPORT_OK = qw(write);
sub write { "val0"};
1;
1 change: 1 addition & 0 deletions t/data/wheels_dir/writer/tests/pen/ink0.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use Mojo::Base 'basetest'; use Copy::Writer::Content0 'write'; sub run {}; 1

0 comments on commit dda6a8e

Please sign in to comment.