How to use AnyEvent::HTTP to get only part of the page? - perl

It is necessary to read only part of the page (n bytes) and close the connection, how to do this on AnyEvent::HTTP ?

on_body is called repeatedly as chunks arrive. Returning false from on_body terminates the download.
sub my_http_request {
my $cb = pop;
my ($method, $url, %args) = #_;
croak("Unsupported: on_body") if $args{on_body};
croak("Unsupported: want_body_handle") if $args{want_body_handle};
my $max_to_read = delete($args{max_to_read});
my $data;
return http_request(
$method => $url,
%args,
on_body => sub {
#my ($chunk, $headers) = #_;
$data .= $_[0];
return !defined($max_to_read) || length($data) < $max_to_read;
},
sub {
my (undef, $headers) = #_;
$cb->($data, $headers);
},
);
}
Use my_http_request just like http_request, except it accepts an optional max_to_read parameter.
For example,
my $cb = AnyEvent->condvar();
my_http_request(
GET => 'http://...',
...
max_to_read => ...,
$cb,
);
my ($data, $headers) = $cb->recv();
For example,
my $done = AnyEvent->condvar();
my_http_request(
GET => 'http://...',
...
max_to_read => ...,
sub {
my ($data, $headers) = #_;
...
$done->send();
},
);
$done->recv();

Related

Perl rest client declaration causes failure of user agent call with custom headers with another end point

I have 2 subroutines called in a single perl program .
First one (get_secrets) I am using the perl REST client directly with custom header and second one (app_restart) I am using LWP user agent and make and HTTP call .
my second subroutine fails when the $client header declaration is available in the first subroutine , as soon as i remove the that subroutine or comment the lines those lines app_restart subroutine works fine .
use REST::Client;
use Data::Dumper;
use JSON; #use strict;
use MIME::Base64 qw( decode_base64 );
use POSIX 'strftime';
use Date::Parse;
use DateTime;
use Date::Calc qw(:all);
use LWP::UserAgent;
#use IO::Socket::SSL 'debug4';
use Data::Dumper qw(Dumper);
use Getopt::Long;
sub toList {
my $data = shift;
my $key = shift;
if ( ref( $data->{$key} ) eq 'ARRAY' ) {
$data->{$key};
}
elsif ( ref( $data->{$key} ) eq 'HASH' ) {
[ $data->{$key} ];
}
else {
[];
}
}
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
$endpoint = $ENV{'ENDPOINT'};
$token = `cat /var/run/secrets/kubernetes.io/serviceaccount/token`;
$namespace = $ENV{'NAMESPACE'};
$apikey = $ENV{'APIKEY'};
$instance = $ENV{'INSTANCE'};
$appid = $ENV{'APPID'};
$storeid = "checker";
sub get_secrets {
my $client = REST::Client->new();
$client->setHost("https://${endpoint}");
#$client->addHeader('Authorization', "Bearer ${token}");
$client->addHeader( 'Accept', "application/json" );
$client->GET("/api/v1/namespaces/${namespace}/secrets?labelSelector=true");
}
get_secrets();
$ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = "Net::SSL";
my $ua = LWP::UserAgent->new( 'send_te' => '0' );
$ua->ssl_opts(
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
SSL_hostname => '',
verify_hostname => 0
);
sub app_restart {
$ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = "Net::SSL";
$ua = LWP::UserAgent->new( 'send_te' => '0' );
$ua->ssl_opts(
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
verify_hostname => 0
print "$instance\n";
print "$apikey\n";
print "$cstoreid\n";
print "$appid\n";
my $r = HTTP::Request->new(
'PUT' =>
"https://api.service.intranet.com/rest/application/$appid/instance/$instance/action?action=restart&config=$storeid&deploy=0",
[
'Accept' => '*/*',
'Authorization' => "Realm $apikey",
'Host' => 'api.service.intranet.com:443',
'User-Agent' => 'curl/7.55.1',
],
);
my $res = $ua->request( $r, );
#$response = $res->decoded_content;
$json = JSON->new->allow_nonref;
$response_decoded = $json->decode( $res->decoded_content );
$actionID = $response_decoded->{'action_id'};
print "$actionID\n";
}
app_restart();

Perl: Issue with blessed object

I am creating a bot that connects to a Matrix server. For that I use Net::Async::Matrix.
The code:
#!/usr/bin/perl
use strict;
use warnings;
use Net::Async::Matrix;
use Net::Async::Matrix::Utils qw ( parse_formatted_message );
use IO::Async::Loop;
use Data::Dumper;
my $loop = IO::Async::Loop->new;
my $matrix = Net::Async::Matrix->new(
server => 'matrix.server.net',
on_error => sub {
my ( undef, $message ) = #_;
warn "error: $message\n";
},
);
$loop->add( $matrix );
$matrix->login(
user_id => '#bot:matrix.server.net',
password => 'password',
)->get;
my $room = $matrix->join_room( '#Lobby:matrix.server.net' )->get;
$room->configure(
on_message => sub {
my ( undef, $member, $content, $event ) = #_;
my $msg = parse_formatted_message( $content );
my $sendername = $member->displayname;
print Dumper $sendername;
&sendmsg("$sendername said: $msg");
},
);
my $stream = $matrix->start;
sub sendmsg {
my $input = shift;
if ($input) {
$room->send_message(
type => "m.text",
body => $input,
),
}
}
$loop->run;
Basically, I want the bot to echo what was said.
I get following output:
$VAR1 = 'm1ndgames'; Longpoll failed - encountered object 'm1ndgames
said: test', but neither allow_blessed, convert_blessed nor
allow_tags settings are enabled (or TO_JSON/FREEZE method missing) at
/usr/local/share/perl/5.24.1/Net/Async/Matrix.pm line 292.
and I don't understand it. When I enter a string like test into the body, it gets sent to the room.
parse_formatted_message returns a String::Tagged object. This class overloads concatenation so that "$sendername said: $msg" also returns a String::Tagged object. This object is passed to sendmsg which tries to serialize it into JSON, but it refuses to serialize objects.
Fix: Replace
my $msg = parse_formatted_message( $content );
with
my $msg = parse_formatted_message( $content )->str;
I'd guess that this is a quoting error. If you look at Net::Async::Matrix::Room:
sub send_message
{
my $self = shift;
my %args = ( #_ == 1 ) ? ( type => "m.text", body => shift ) : #_;
my $type = $args{msgtype} = delete $args{type} or
croak "Require a 'type' field";
$MSG_REQUIRED_FIELDS{$type} or
croak "Unrecognised message type '$type'";
foreach (#{ $MSG_REQUIRED_FIELDS{$type} } ) {
$args{$_} or croak "'$type' messages require a '$_' field";
}
if( defined( my $txn_id = $args{txn_id} ) ) {
$self->_do_PUT_json( "/send/m.room.message/$txn_id", \%args )
->then_done()
}
else {
$self->_do_POST_json( "/send/m.room.message", \%args )
->then_done()
}
}
The type you sent is handled by this sub, and then the actual message gets handed off to _do_POST_json in Net::Async::Matrix.
But you've sent a string containing a :.
So I think what's happening is it's encoding like this:
use JSON;
use Data::Dumper;
my $json = encode_json ( {body => "m1ndgames: said test"});
print Dumper $json;
But the response that's coming back, at line 292 which is:
if( length $content and $content ne q("") ) {
eval {
$content = decode_json( $content );
1;
} or
return Future->fail( "Unable to parse JSON response $content" );
return Future->done( $content, $response );
}
So I think is what is happening is the remote server is sending you a broken error code, and the module isn't handling it properly - it's expecting JSON but it isn't actually getting it.
My best guess would be - try dropping the : out of your message, because I would guess there's some bad quoting happening. But without seeing the code on the server side, I can't quite tell.

Sequentioal requests with Mojo::IOLoop::Delay

I need to get list of URLs in non-blocking mode, but not in parallel. It should be sequential requests one by one. How can I realize that?
I cannot find examples. Documentation and articles highlight only parallel execution.
Now my code looks like the following (simplified):
my $delay = Mojo::IOLoop::Delay->new;
$delay->steps(
sub {
build_report();
say "done";
}
);
sub parse_data {
...;
my $url = shift #urls;
my $end = $delay->begin;
$ua->get( $url => \&parse_data );
$end->();
}
my $end = $delay->begin;
$ua->get( $url => \&parse_data );
$end->();
$delay->wait;
I want to avoid multiple closures by using Mojo::IOLoop::Delay.
I have just started looking at Mojo::IOLoop myself for a project and I'm not au fait with it yet. But I think the simplest way is to build an array of closures and pass it to $delay->steps.
use strict;
use warnings 'all';
use feature 'say';
use Mojo;
use constant URL => 'http://stackoverflow.com/questions/tagged/perl';
STDOUT->autoflush;
my $ua = Mojo::UserAgent->new;
my $delay = Mojo::IOLoop::Delay->new;
my #urls = ( URL ) x 10;
my #steps = map {
my $url = $_;
sub {
my $end = $delay->begin;
$ua->get( $url => sub {
my ( $ua, $txn ) = #_;
$end->();
if ( my $err = $txn->error ) {
say $err->message;
}
else {
my $res = $txn->success;
say "#{$res}{qw/ code message /}";
}
});
}
} #urls;
$delay->steps( #steps, sub { say 'Done' } );
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
output
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
200 OK
Done
200 OK
Note that Done is printed before the status line of the final get, because I have called $end->() immediately the callback arrives, assuming that any handling of the response doesn't need to be synchronised
If you don't want that then just move $end->() to the end of the callback. Then the delay will wait until the output has been generated before sending another request
Sequential requests very easy to implement.
Web server
#!/usr/bin/perl
use Mojo::Base -strict;
use Mojolicious::Lite;
get '/' => sub {
my $c = shift->render_later;
$c->delay(sub {
my $delay = shift;
$c->ua->get('https://google.ru' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the google.ru
$c->ua->get('https://twitter.com' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the twitter.com
$c->render(text => 'Done');
});
};
app->start;
Script
#!/usr/bin/perl
use Mojo::Base -strict;
use Mojo::IOLoop;
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
Mojo::IOLoop->delay(sub {
my $delay = shift;
$ua->get('https://www.google.ru' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the google.ru
$ua->get('https://twitter.com' => $delay->begin);
}, sub {
my ($delay, $tx) = #_;
$tx->res->body; # body of the twitter.com
warn 'DONE';
})->wait;
Script (dynamic requests)
Example here

Perl : Error 500 Cant Connect Certificate Verify Failed

I have problem when I try to running Perl script using PHP, below is the Perl Script
use HandlePWRequestService;
my $certfile = "E:\perl\Cache-UserMySQL.p12";
my $certpw = "Welcome1";
my $system = "Test-MySQL";
my $account = "admin1";
#$ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
#$ENV{HTTPS_CA_DIR} = 'E:\perl';
#$ENV{HTTPS_CA_FILE} = 'E:\perl\parRootCA';
$ENV{HTTPS_PKCS12_FILE} = $certfile;
$ENV{HTTPS_PKCS12_PASSWORD} = $certpw;
$ENV{HTTPS_DEBUG} = 1;
my $pwservice = new HandlePWRequestService;
my #rc = $pwservice->handleRequestWS($system,$account);
print "rc=$rc[0], password=$rc[1]\n";
And whenever I try run I got error 500 Certificate Verify Failed.
I wonder what might cause this?
Below is the code for HandlePWRequestService
HandlePWRequestService.pm
package HandlePWRequestService;
my %methods = (
handleRequestWS => {
endpoint => 'https://some_ip/HandlePWRequestService/HandlePWRequest',
soapaction => '',
namespace => 'http://ejb3.pwAccel.edmz.com/',
parameters => [
SOAP::Data->new(name => 'systemName', type => 'xsd:string', attr => {}),
SOAP::Data->new(name => 'accountName', type => 'xsd:string', attr => {}),
], # end parameters
}, # end handleRequestWS
); # end my %methods
use SOAP::Lite;
use Exporter;
use Carp ();
use vars qw(#ISA $AUTOLOAD #EXPORT_OK %EXPORT_TAGS);
#ISA = qw(Exporter SOAP::Lite);
#EXPORT_OK = (keys %methods);
%EXPORT_TAGS = ('all' => [#EXPORT_OK]);
sub _call {
my ($self, $method) = (shift, shift);
my $name = UNIVERSAL::isa($method => 'SOAP::Data') ? $method->name : $method;
my %method = %{$methods{$name}};
$self->proxy($method{endpoint} || Carp::croak "No server address (proxy) specified")
unless $self->proxy;
my #templates = #{$method{parameters}};
my #parameters = ();
foreach my $param (#_) {
if (#templates) {
my $template = shift #templates;
my ($prefix,$typename) = SOAP::Utils::splitqname($template->type);
my $method = 'as_'.$typename;
# TODO - if can('as_'.$typename) {...}
my $result = $self->serializer->$method($param, $template->name, $template->type, $template->attr);
push(#parameters, $template->value($result->[2]));
}
else {
push(#parameters, $param);
}
}
$self->endpoint($method{endpoint})
->ns($method{namespace})
->on_action(sub{qq!"$method{soapaction}"!});
$self->serializer->register_ns("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd","wsu");
$self->serializer->register_ns("http://www.w3.org/2001/XMLSchema","xsd");
$self->serializer->register_ns("http://ejb3.pwAccel.edmz.com/","tns");
$self->serializer->register_ns("http://schemas.xmlsoap.org/wsdl/soap/","soap");
my $som = $self->SUPER::call($method => #parameters);
if ($self->want_som) {
return $som;
}
UNIVERSAL::isa($som => 'SOAP::SOM') ? wantarray ? $som->paramsall : $som->result : $som;
}
sub BEGIN {
no strict 'refs';
for my $method (qw(want_som)) {
my $field = '_' . $method;
*$method = sub {
my $self = shift->new;
#_ ? ($self->{$field} = shift, return $self) : return $self->{$field};
}
}
}
no strict 'refs';
for my $method (#EXPORT_OK) {
my %method = %{$methods{$method}};
*$method = sub {
my $self = UNIVERSAL::isa($_[0] => __PACKAGE__)
? ref $_[0]
? shift # OBJECT
# CLASS, either get self or create new and assign to self
: (shift->self || __PACKAGE__->self(__PACKAGE__->new))
# function call, either get self or create new and assign to self
: (__PACKAGE__->self || __PACKAGE__->self(__PACKAGE__->new));
$self->_call($method, #_);
}
}
sub AUTOLOAD {
my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::') + 2);
return if $method eq 'DESTROY' || $method eq 'want_som';
die "Unrecognized method '$method'. List of available method(s): #EXPORT_OK\n";
}
1;
Hard to be sure as HandlePWRequestService seems to be a private module that we can't be expected to know anything about.
But looking at the environment variables that your code is setting, I'd guess that there is some kind of HTTPS interaction going on that probably uses LWP and friends. So my first recommendation would be to uncomment the line which turns off hostname verification and see if that fixes the problem.

Perl OOP method returns array I cannot loop

Here is what my module looks like:
#!/usr/bin/perl
package Page;
use strict;
use warnings;
use base qw/DBObj/;
our %fields = (
id => undef,
title => '$',
content => '$'
);
sub get_field_names {
my #names = sort keys \%fields;
return \#names;
}
for my $field ( keys %fields ) {
my $slot = __PACKAGE__ ."::$field";
no strict "refs";
*$field = sub {
my $self = shift;
$self->{$slot} = shift if #_;
return $self->{$slot};
}
}
1;
Here the parent module where the strange behaviour occurs
#!/usr/bin/perl
package DBObj;
use strict;
use warnings;
use Data::Dumper;
use DBConn;
sub new {
my $me = shift;
my $class = ref $me || $me;
my $self = {
dbh => new DBConn->new(
dns => '/db.sql',
user => '',
pass => '',
)
};
return bless $self, $class;
}
sub save {
my $self = shift;
my #field_names = #{$self->get_field_names};
print Dumper #field_names;
foreach my $item ( reverse #field_names ) {
print $item;
}
}
sub fill {
my ( $self, $args ) = #_;
foreach my $key ( keys $args ) {
$self->$key( $args->{$key} );
}
}
1;
here is what I am experiencing. This snippet
my #field_names = $self->get_field_names;
print Dumper #field_names;
foreach my $item ( reverse #field_names ) {
print $item;
}
Data::Dumper shows
$VAR1 = [
'content',
'id',
'title'
];
But the foreach loop returns
ARRAY(0x7fc750a26470)
I have a Test::Simple test case where I perform the following test
ok( shift $page->get_field_names eq 'content', 'Page has field content');
so I can shift off an item from the array, but I cannot loop through it which is a puzzle to me.
And please; before you tell me that I shouldn't be doing this and that there is a ton of modules out there I should pick instead, I want to point out; I am doing this our of pure fun, I have been away from Perl for ~10 years and thought it would be fun to play around with it again.
You have made get_field_names return a reference to an array, but you are then putting that reference into an array variable.
Try:
my $field_names = $self->get_field_names;
print Dumper $field_names;
foreach my $item ( reverse #$field_names ) {
print $item;
}
get_field_names returns an arrayref, not an array. Either change its return type by removing the backslash from return \#names; or "cast" its return type to an array by writing:
my #field_names = #{$self->get_field_names};