How can I get the port that Mojolicious::Lite chooses? - perl

Joel Berger posted this little program to start a web server to serve local files, and it works great:
use Mojolicious::Lite;
#ARGV = qw(daemon);
use Cwd;
app->static->paths->[0] = getcwd;
any '/' => sub {
shift->render_static('index.html');
};
app->start;
I prepopulated the command line in #ARGV because I forget to do that. When it starts, it gives a message telling you which port it chose, using 3000 if it can:
$ perl ~/bin/mojo_cwd
[Fri Mar 29 19:14:09 2013] [info] Listening at "http://*:3000".
Server available at http://127.0.0.1:3000.
I'd like to get that port programmatically so a test suite can know where to look for it, and I'd prefer not to do it by scrapping output. None of my experiments for this were useful and I think I was always going in the wrong direction. It appears that it doesn't choose the port until it starts, and once I call start, that's the end of it.
I don't want to specify the port myself, either.
This isn't an urgent matter. I have a current solution to this with another simple HTTP framework, but I've been looking at replacing most of that stuff with Mojo if I can. Since the old stuff still works, this is really just something nice to have rather than something in my way.

You can't, but the daemon command only binds to port 3000 and will not try anything else unless you tell it to. If you're using Test::Mojo you don't need to know the port in advance anyway, for anything else you can always wrap your application in a little Mojo::Server::Daemon script.
use Mojolicious::Lite;
use Mojo::IOLoop;
use Mojo::Server::Daemon;
get '/' => {text => 'Hello World!'};
my $port = Mojo::IOLoop->generate_port;
my $daemon = Mojo::Server::Daemon->new(
app => app,
listen => ["http://*:$port"]
);
$daemon->run;

With the --listen param you specify to your app where to listen:
use Mojolicious::Lite;
#ARGV = qw(daemon --listen http://*:5000);
use Cwd;
app->static->paths->[0] = getcwd;
any '/' => sub {
shift->render_static('index.html');
};
app->start;
You can access the port number within the app with $self->tx->local_port:
#!/usr/bin/env perl
use Mojolicious::Lite;
#ARGV = qw(daemon --listen http://*:5000);
use Cwd;
app->static->paths->[0] = getcwd;
any '/' => sub {
my $self = shift;
$self->render_text('port: '. $self->tx->local_port);
};
app->start if $ENV{MOJO_MODE} ne 'test';
1;
I like to test Mojolicious apps with Test::Mojo because you get access to the running app, for example, in a file t/test_mojo.t:
use strict;
use warnings;
use feature 'say';
use Test::More;
use Test::Mojo;
$ENV{MOJO_MODE} = 'test';
require "$FindBin::Bin/../test_mojo.pl";
my $t = Test::Mojo->new;
$t->get_ok('/')->status_is(200)->content_is('port: '.$t->tx->remote_port);
say 'local port: '. $t->tx->local_port; #as seen from the user-agent's perspective
say 'remote port:'. $t->tx->remote_port;
done_testing();
I'm not sure I correctly understood what you are trying to accomplish, but I hope this helps you.

Related

Passing Parameteres to Interactive script programatically

We have an interactive script(Script 1) which asks for an IP Address and continues it's execution process. Script 1 is called from script2.
As we know the IP address we want to pass IP Automatically to script so that manual intervention is not required
I looked into Expect Module. But i cannot install that module in PRODUCTION server.
Can someone suggest a way to overcome this issue.
Try this,
#script2.pl
use strict;
use warnings;
use Getopt::Long;
GetOptions (
"ipAddress=s" => \$ip,
) or die("Enter IP address");
my $cmd = "perl script1.pl --ip=$ip";
system($cmd);
.
#script1.pl
use strict;
use warnings;
use Getopt::Long;
GetOptions (
"ip=s" => \$ip,
) or die("Enter IP address");
print "IP address is $ip";
Execute like this.
perl script2.pl --ipAddress=10.11.12.13
If you want to execute script1 directly, can execute like this,
perl script1.pl --ip=10.11.12.13

How to run a Perl Dancer Test

Reading through the Dancer::Test documentation made it seem straightforward to do a test, but I'm missing something. If I have the following Dancer application (WebApp.pm):
package WebApp;
use Dancer;
# declare routes/actions
get '/' => sub {
"Hello World";
};
dance;
and then the following testing file 001_base.t:
use strict;
use warnings;
use Test::More tests => 1;
use WebApp;
use Dancer::Test;
response_status_is [GET => '/'], 200, "GET / is found";
Then when I run the test: perl 001_base.t, the output is that the dancer script starts up:
Dancer 1.3132 server 7679 listening on http://0.0.0.0:3000
== Entering the development dance floor ...
But then waits. (This is the same as if I just run the code in WebApp.pm). What am I missing here? I guess I'm not running the test correctly.
You should remove dancer() from the WebApp.pm. Here is the correct content:
package WebApp;
use Dancer;
# declare routes/actions
get '/' => sub {
"Hello World";
};
1;
Then you test will pass.
The common way to create dancer apps is to declare all the routes in one or more .pm files and to have a file usually called app.psgi with the content:
#!/usr/bin/env perl
use Dancer;
use WebApp;
dance;
Then to start your web application you should run perl -Ilib app.psgi.

Perl IO::Socket::INET permission denied

Have a perl script to connect with a Java service running on localhost, passes encrypted cookie, and returns decrypted data. When I run script from command line, it works fine. Even gave apache user a shell, and ran from command line as that user, which also works fine. If the script is run as CGI from apache, the socket new returns undef and $! is set with "permission denied". ???
Running CentOS 6.3 on this server, and IPtables are disabled.
#!/usr/bin/perl
use strict;
use CGI;
use IO::Socket;
use JSON;
my $cgi = CGI->new();
my $cookie = $cgi->cookie('attESSec') || shift (#ARGV) || undef;
my $data = JSON::false;
if($cookie){
my $socket = IO::Socket::INET->new(
'PeerHost' => '127.0.0.1',
'PeerPort' => '1500',
'Proto' => 'tcp'
);
if($socket){
$socket->send($cookie . "\r\n");
$socket->recv(my $auth,1024);
$socket->close();
chomp($auth);
if($auth){
$data = (split(/\|/,$auth))[5];
}
}
else{
$data = $!;
}
}
print($cgi->header('application/javascript'));
print(JSON->new()->allow_nonref()->utf8()->encode($data));
exit();
I found the answer. The problem was SElinux. By default it doesn't let the httpd process (or anything that spawns from it, such as CGI scripts) establish network sockets. So just had to enable that particular feature with command "setsebool -P httpd_can_network_connect 1". Now it works perfectly.

Can't run Perl script on other computer

When I try to run script on my second computer I get this message:
malformed JSON string, neither array, object, number, string or atom, at character offset 0
(before "LWP will support htt...") at iptest.pl line 21, line 2.
On my first computer, the script works fine.
Line 21:
my $data = decode_json($resp->content);
Does anyone know what the problem can be?
Thanks in advance
I'm a bit surprised that the JSON error is the only error you get. But it does contain a tiny little hint: "LWP will support htt...". I bet that LWP is missing a module it needs to be able to make https connections. You now have two options:
print $response->content to see the full error message.
On the command line, do something like lwp-request https://google.com/. You should see the full error message.
Then install the missing module.
And of course: please, please, please:
use strict and use warnings
Clean that script up and throw away every use-line you don't need: IO::Socket, LWP::Simple, YAML::Tiny.
Read the documentation of the modules that you actually are using. What are you trying to achieve with LWP::UserAgent->new(keep_alive)? Hint: It won't help to quote keep_alive.
Some issues:
Always use use strict; use warnings;.
Never use $response->content. What it returns is useless. Instead, use $response->decoded_content( charset => 'none').
You need to chomp the values you get from STDIN.
You should never use our unless forced to (e.g. our #ISA = ok). my should be used instead.
my $format = '$format'; "$format" is a silly way of writing "\$format".
I applied most of the changes ikegami suggested. Then perl gave me good error messages to fix the remaining issues. It looks like it works now. Don't ask why it didn't work before. Your code is weird that it's hard to say what exactly went wrong. With strict and warnings you're forced to write better code. Maybe add some nicely named subroutines to add more clarity.
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
use LWP::UserAgent;
use open qw(:std :utf8);
use LWP::Simple;
use YAML::Tiny;
use JSON;
use URI;
use List::MoreUtils qw(uniq);
print "Enter Qve:";
my ( $qve, $loc, $key, $href );
chomp( $qve = <STDIN> );
print "Enter Location:";
chomp( $loc = <STDIN> );
$key = '';
my $format = '$format';
$href =
"https://api.datamarket.azure.com/Bing/Search/v1/Web?Query='$qve [loc:$loc]'&Latitude=43&Longitude=19&$format=JSON";
my $ua = LWP::UserAgent->new('keep_alive');
$ua->credentials( "api.datamarket.azure.com" . ':443', '', '', $key );
my $resp = $ua->get($href);
my $data = decode_json( $resp->decoded_content( charset => 'none' ) );
my #urls = map { $_->{'Url'} } #{ $data->{d}->{results} };
my #za;
for my $i ( 0 .. $#urls ) {
my $trz = "www.";
my $host = URI->new( $urls[$i] )->host;
$host =~ s/$trz//g;
push( #za, $host );
}
For the record, this fixed the issue for me (CentOS):
# yum install perl-Crypt-SSLeay
I got in the same situation. After I used 'yum' to install perl, I still got the error when run the perl script.
$ sudo yum install perl
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered to Red Hat Subscription Management
...
Eventually I did these, and it works.
$ lwp-request https://google.com/
It returned an error message .. (LWP::Protocol::https not installed)
$ sudo rpm -ivh ~/perl-LWP-Protocol-https-6.07-4.el8.noarch.rpm
$ lwp-request https://google.com/
it returned a HTML page.
and my perl script can run without error.
(my system is:
$ cat /etc/os-release
PRETTY_NAME=Red Hat Enterprise Linux 8.0 )

Display Output In Browser Perl CGI SSH

I'm executing remote commands using Net::OpenSSH using a web frontend. My commands return without failure on the command line, but I get nothing in a web browser. I've done a couple hour research to no avail--any ideas?
Here is some code to give you an example (some removed for obvious reasons).
#!/usr/bin/perl -w
use strict;
use CGI ':standard';
use Net::OpenSSH;
# Here in the code is just the header and standard tags
print "1";
print "2"; # both display
my $ssh = Net::OpenSSH->new($host, user => $uname, key_path => $key); # all works
$ssh- error and die "Can't ssh to host" . $ssh->error;
print "3";
$ssh->system("uname -a") or
die "remote command failed: " . $ssh->error;
my #lsa = $ssh->capture("ls -a");
$ssh->error and
die "remote ls command failed: ". $ssh->error;
print "4";
print "5";
print #lsa; # won't display in browser, just terminal/CLI
Cheers!
I maintain CGI.pm. I recommend these additions to your simple script:
Before you print anything else, print the standard HTTP header: print header();
Add this after the use CGI line: use CGI::Carp qw(fatalsToBrowser); ... that will display any run-time problems in the browser. If you don't get any output after these changes, check that the script compiles with perl -cw script.pl
Below is about the minimum Perl code that worked for me on Debian machine. I suggest you go through it and compare it to your actual code.
However, it did not work out-of-the box on my Debian, I had make some decisions most of which probably aren't very safe, but that's more about specific environment:
make home for user that server runs writable (/var/www)
add host to ~/.ssh/known_hosts beforehand
use the strict_mode => 0 to bypass Net::OpenSSH's security checks instead of finding proper
ctl_dir (Net::OpenSSH requires that the folder and all above folders are 0755 or more strict,
so /tmp I used is normally not good enough)
I believe there are much safer practices than that, but as I said, that's specific to environment.
So the code:
#!/usr/bin/perl
use strict;
use warnings;
use Net::OpenSSH;
use File::Temp qw/ tempdir /;
# necessary minimum for CGI
print "Content-type: text/plain\n\n";
# prepare temp dir
my $temp = tempdir("/tmp/sshme.pl-XXXXXXXX", CLEANUP => 1);
# open SSH session
my %opts = (
user => "user",
password => "password",
ctl_dir => $temp,
strict_mode => 0 ## NOT recommended - see my comments
);
my $ssh = Net::OpenSSH->new("host", %opts);
$ssh->error
and die "Couldn't establish SSH connection: ". $ssh->error;
# perform command and print output
my #lines = $ssh->capture("ls")
or die "remote command failed: " . $ssh->error;
print #lines;
Perhaps your errors get directed to standard error, not standard output. In that case, they'll usually end up in the server log, not the browser window. Perhaps you can use POSIX::dup2 to avoid this:
use POSIX;
# Make sure to send HTTP headers before redirecting streams!
POSIX::close(2); # close original stderr stream. No more output to apache logs!!!
POSIX::dup2(1,2); # redirect error stream to standard output, so errors will appear in browser.
Processes launched by perl, like e.g. some ssh binary, will inherit these streams.