I'm working through example SOAP client/server code using SOAP::Transport::HTTP:Daemon and SOAP::Lite, and I've noticed that I cannot access an instance variable declared in an object's new() method. I'm sure I'm doing something wrong, but I'm not sure what (although it has been years since I was a half-decent Perl programmer).
Here's my server:
#! /usr/bin/env perl
use lib '/a/valid/directory/modules';
use SOAP::Transport::HTTP;
my $port = 9810;
my $dispatchers = {
'urn:remote_call' => 'ExampleLibrary::MyExample'
};
my $daemon = SOAP::Transport::HTTP::Daemon
-> new (LocalAddr => 'localhost', LocalPort => $port)
-> dispatch_with($dispatchers)
-> on_action(sub {return})
;
print "Connect to SOAP server at ", $daemon->url, "\n";
$daemon->handle;
Here's my client (located in a directory specified in the server's use lib line, modules/ExampleLibrary/MyExample.pm):
package ExampleLibrary::MyExample;
use vars qw(#ISA);
#ISA = qw(Exporter SOAP::Server::Parameters);
use SOAP::Lite;
sub new {
my($class, %args) = #_;
my $self = bless({}, $class);
$self->{somevar} = 'somedata';
return $self;
}
sub remote_call {
my $self = shift;
my $envelope = pop;
# Swap out return statements and the data is returned correctly
# return SOAP::Data->type('xml' => 'foo');
return SOAP::Data->type('xml' => "$self->{somevar}");
}
1;
I'm sure I'm hitting the client correctly--I can put in static text in the remote_call's return statement (the commented out code) and see it--but the object data specified as $self->{somevar} is never visible, with or without quotes. (I'm using SoapUI to hit the server.)
Does anyone see what I'm missing? (Oh, I'm using Perl v5.10.1 on Cygwin.)
Thanks...
The docs say
dispatch_with({
URI => MODULE, # 'http://www.soaplite.com/' => 'My::Class',
SOAPAction => MODULE, # 'http://www.soaplite.com/method' => 'Another::Class',
URI => object, # 'http://www.soaplite.com/obj' => My::Class->new,
})
You have
my $dispatchers = {
'urn:remote_call' => 'ExampleLibrary::MyExample'
};
maybe that should be
my $dispatchers = {
'urn:remote_call' => 'ExampleLibrary::MyExample->new'
};
Otherwise (I speculate that) your method may be being invoked as a static method not as an instance method (no instance having been instantiated).
P.S. The absence of single-quotes for the object instantiation string in the docs puzzles me as it suggests that a single instance is used to handle all requests (and that seems wrong to me) but maybe that is indeed what this experimental feature needs and you should omit the single quotes too.
I got this working after tweaking the code a bit from #RedGrittyBrick's suggestion (or perhaps it's exactly what he suggested and I just didn't understand it). Thanks also to #Axeman--you came back multiple times to try and help out--I appreciate that very much. I put comments in the server to indicate the two lines that fixed things.
Here's the revised server:
#! /usr/bin/env perl
use lib '/a/valid/directory/modules';
use ExampleLibrary::MyExample; # new!
use SOAP::Transport::HTTP;
my $port = 9810;
my $dispatchers = {
# new--no quotes around the hash value
'urn:remote_call' => ExampleLibrary::MyExample->new
};
my $daemon = SOAP::Transport::HTTP::Daemon
-> new (LocalAddr => 'localhost', LocalPort => $port)
-> dispatch_with($dispatchers)
-> on_action(sub {return})
;
print "Connect to SOAP server at ", $daemon->url, "\n";
$daemon->handle;
Here's the revised client. Really, the only changes were to put localtime() calls in so that I could verify that variables set in new() remained unchanged over the lifetime of the server.
package ExampleLibrary::MyExample;
use vars qw(#ISA);
#ISA = qw(Exporter SOAP::Server::Parameters);
use SOAP::Lite;
sub new {
my($class, %args) = #_;
my $self = bless({}, $class);
$self->{'somevar'} = localtime();
return $self;
}
sub remote_call {
my $self = shift;
my $envelope = pop;
$now = localtime();
return SOAP::Data->type('xml' => "now = $now, started at $self->{somevar}");
}
1;
Related
I need to instantiate a new $dbh using DBI.
My objects usually have a $dbh present when they're created.
When I try to create a new $dbh using
my $dbh = MyLib::Connect();
and after performing some DB operations doing
$dbh->disconnect();
my downstream code's $dbh is closed. Is there a way to get what I'm after? I've seen some example code that does two DBI->connect(...) calls but using the same code as an example produces the same result - it's like MyLib is caching the returned $dbh value.
Example code:
package MyLib;
sub DoConnect {
...
my $dbh = DBI->connect(...);
return($dbh)
}
package Object;
sub GetData {
my ($id) = #_;
my $dbh = MyLib::DoConnect(); # This should be separate
...
$dbh->commit()
$dbh->disconnect();
return($someData);
}
package AnotherObject;
sub DoSomething {
my ($self) = #_;
# $self had a dbh set on instantiation with MyLib::DoConnect();
my $newData = Object::GetData($self->id);
my $moreData = GetDataUsingDBH($self->dbh); # the dbh is closed!!!
}
Is what I need to do possible without starting a separate thread (which I can't guarantee will finish before GetDataUsingDBH is called). Should I do a system call to an external program to wait for it to finish? Does my question even make sense?
The approach you describe works fine.
package MyLib;
use DBI qw( );
sub DoConnect {
return DBI->connect(
'dbi:SQLite:foo.sqlite3', undef, undef,
{ PrintError=>0, RaiseError=>1 },
);
}
package Object;
sub new {
my $class = shift;
my $self = bless({}, $class);
return $self;
}
sub GetData {
my $dbh = MyLib::DoConnect();
$dbh->disconnect();
}
package AnotherObject;
sub new {
my $class = shift;
my $self = bless({}, $class);
$self->{dbh} = MyLib::DoConnect();
return $self;
}
sub DoSomething {
my ($self) = #_;
return $self->{dbh}->selectrow_array("SELECT 'abc'");
}
package main;
my $ao = AnotherObject->new();
my $o = Object->new();
$o->GetData();
print $ao->DoSomething(), "\n";
Output:
abc
Something else you didn't mention is causing the problems.
I figured it out. I setup my DBHs to be shared by all of my objects in the executable so I don't have to instantiate one DBH and pass it around to my objects manually - this ruined the behavior I was expecting when I actually needed to do this.
Thanks for all of your help, folks - learned a bunch.
Are you certain DoConnect is calling DBI->connect, not DBI->connect_cached, or something else that is caching database handles?
If so, I'm guessing you have Apache::DBI loaded and are running under mod_perl (or at any rate have $ENV{MOD_PERL} set), which causes DBI->connect to use Apache::DBI to cache connections.
You can tell DBI to not do this by calling connect with a dbi_connect_method attribute:
DBI->connect( $data_source, $username, $auth, {
'dbi_connect_method' => 'connect',
} );
(which would require you to pass something into your DoConnect saying that's what you want).
That is the documented way; an undocumented way that may work for you is to locally set the variable that DBI uses as the default for that attribute:
# This should be separate
my $dbh = do { local $DBI::connect_via = 'connect'; MyLib::DoConnect() };
(I guess it is possible even if you don't have $ENV{MOD_PERL} set when you load DBI that your code is already setting $DBI::connect_via to 'connect_cached' or some other package and that is causing your trouble.)
Another approach, if you don't actually need to use the two database handles at the same time, would be to just remove the disconnect call. When $dbh goes out of scope, if there are no other copies of that database handle around, it will be closed; explicitly calling disconnect isn't necessary.
I want to share a variable between different perl modules. So I created a perl module called MyCache.pm which saves the variable (in my case a hash variable):
package PerlModules::MyCache;
my %cache = ();
sub set {
my ($key, $value) = #_;
$cache{$key} = $value;
}
sub get {
my ($key) = #_;
return $cache{$key};
}
Now I have two handlers. The one handler will call the set method and the other one will call the get method to access the information.
package PerlModules::MyCacheSetter;
use Apache2::RequestRec();
use Apache2::RequestIO();
use Apache2::Const -compile => qw(OK);
use PerlModules::MyCache;
sub handler {
my $r = shift;
PerlModules::MyCache::set('test1', "true");
PerlModules::MyCache::set('test2', "false");
PerlModules::MyCache::set('test3', "true");
return Apache2::Const::OK;
}
And here is the getter handler:
package PerlModules::MyCacheGetter;
use Apache2::RequestRec();
use Apache2::RequestIO();
use Apache2::Const -compile => qw(OK);
use PerlModules::MyCache;
sub handler {
my $r = shift;
$r->print(PerlModules::MyCache::get('test1'));
$r->print(PerlModules::MyCache::get('test2'));
$r->print(PerlModules::MyCache::get('test3'));
return Apache2::Const::OK;
}
Now I've configured apache (via http.conf) to access these perl modules. I run the setter handler and then the getter, but there was no output.
In the error.log there are now some entries:
Use of uninitialized value in subroutine entry at ../MyCacheGetter.pm line 14.
Use of uninitialized value in subroutine entry at ../MyCacheGetter.pm line 15.
Use of uninitialized value in subroutine entry at ../MyCacheGetter.pm line 16.
This lines are the three calls of the get method. So what am I doing wrong? How can I fix the problem and share my cache variable between different handlers?
Your cache will only exist for the lifetime of a given Apache child process. If you want other processes to see it, you'll need to store it somewhere they can all get at it.
This is untested, but you can get the general idea: (Now tested).
EDIT: OK, it seems like you can get some issues with Storable depending on what perl version and Storable version you're running. I've replaced Storable with Data::Serialize in my example. I've also added a line to the get/set methods so that either the -> or :: syntax can be used.
package PerlModules::MyCache;
use IPC::ShareLite qw/:lock/;
use Data::Serializer;
use 5.10.0;
my $key = 1234; # Your shared memory key (you set this!)
my $ipc = IPC::ShareLite->new(
-key => $key,
-create => 'yes',
-destroy => 'no'
);
my $ser = Data::Serializer->new(
serializer => 'Data::Dumper'
);
sub set {
shift #_ if $_[0] eq __PACKAGE__;
my ($key, $value) = #_;
$ipc->lock(LOCK_EX);
my $frozen; eval { $frozen = $ipc->fetch; };
my $cache = defined($frozen) ? $ser->thaw($frozen) : {};
$cache->{$key} = $value;
$ipc->store($ser->freeze($cache));
$ipc->unlock;
return $value;
}
sub get {
shift #_ if $_[0] eq __PACKAGE__;
my ($key) = #_;
my $frozen; eval { $frozen = $ipc->fetch; };
my $cache = defined($frozen) ? $ser->thaw($frozen) : {};
return $cache->{$key};
}
sub clear {
shift #_ if $_[0] eq __PACKAGE__;
$ipc->store($ser->freeze({}));
return {};
}
1;
You might want to run PerlModules::MyCache->clear once before you test to ensure the correct structure of the cache storage.
I realise there are several questions like this out in the ether, but I can't a solution for my problem. Maybe I should improve my lateral thinking.
I have a module which I am testing. This module looks something like:
package MyModule;
use strict;
use warnings;
... # a bunch of 'use/use lib' etc.
sub new {
my $class = shift;
my ($name,$options) = #_;
my $self = {
_name => $name,
_features => $options,
_ids => undef,
_groups => undef,
_status => undef,
};
bless $self,$class;
return $self;
}
sub init {
my ($self) = #_;
my ($ids,$groups,$status) = ...; # these are from a working module
$self->{_ids} = $ids;
$self->{_groups} = $groups;
$self->{_status} = $status;
return $self;
}
This is my test file:
#!/usr/bin/perl -w
use strict;
use MyModule;
use Test::More tests => 1;
use Data::Dumper;
print "Name: ";
my $name;
chomp($name = <STDIN>);
print "chosen name: $name\n";
my %options = (
option1 => 'blah blah blah',
option2 => 'blu blu blu',
);
my $name_object = MyModule->new($name,\%options);
print Dumper($name_object);
isa_ok($name_object,'MyModule');
$name_object->init;
print Dumper($name_object);
Now it works down to the isa_ok, but then comes up with:
Can't locate object method "init" via package "MyModule" at test_MyModule.t line 31, <STDIN> line 1.
This has only occurred now that I'm trying (and somewhat failing it seems) to use objects. So thus I reckon I'm misunderstanding the applications of objects in Perl! Any help would be appreciated...
I think you're loading a different file than the one you think you are loading.
print($INC{"MyModule.pm"}, "\n");
will tell you which file you actually loaded. (If the module name is really of the form Foo::Bar, use $INC{"Foo/Bar.pm"}.) Make sure the capitalisation of the package and the file name match.
I want to use Perl module and wrap it into a standalone socket which would publish the subroutines from the module to other programmes. However, I probably cannot overcome namespace issues, since in the client script, I am still getting an error message:
RPC::Serialized::X::Application\',\'MESSAGE\' => \'No handler for 'predejPOS' .
My server script:
use RPC::Serialized::Server::NetServer::Single;
use RPC::Serialized::Handler::HashTree;
my $s = RPC::Serialized::Server::NetServer::Single->new({
net_server => {log_file => '', port => 20203 },
rpc_serialized => {handler_namespaces => ''},
});
$s->run;
My client script:
use RPC::Serialized::Client::INET;
my $client = RPC::Serialized::Client::INET->new({
io_socket_inet => {PeerAddr => '127.0.0.1', PeerPort => 20203,}
});
my $result = $client->predejPOS('flu-like');
My module (HastTree.pm):
package RPC::Serialized::Handler::HashTree;
require Exporter;
#ISA = qw(Exporter);
#EXPORT = qw(predejPOS);
use base 'RPC::Serialized::Handler';
our $VERSION = '0.01';
sub predejPOS {
my %POS;
$POS{'flu-like'}='<JJ>';
return $POS{$_[0]};
};
1;
I am using Windows 7, Strawberry Perl 5.12.3, and the module sits on the correct address
(C:\PROGS\Strawberry\perl\site\lib\RPC\Serialized\Handler). The function predejPOS is recognised inside the server script (ie. I can print its result from it), but I cannot access it through the client-server communication. I assume that it has something to do with the subtle difference between calling the function and calling the method. I am afraid that it is probably something simple, but even after a substantial effort and googling I was not able to make it work.
Thanks in advance!
Well I eventually solved it by myself:
First, I got completely wrong the concept of calling functions - over the network you can only call so called RPC Handlers. Moreover for each handler there must be a module in RPC::Serialized::Handler directory with the same name and a specific structure with only one subroutine inside called invoke(). Thus I changed my module (named now 'PredejPOS.pm') to:
package RPC::Serialized::Handler::PredejPOS;
{
$RPC::Serialized::Handler::PredejPOS::VERSION = '0.01';
}
use strict;
use warnings FATAL => 'all';
use base 'RPC::Serialized::Handler';
sub invoke {
my $self = shift;
my $key = shift;
my %POS;
$POS{'flu-like'}='<JJ>';
return scalar $POS{$key};
}
1;
But it was still not working.
Finally secondly I found that under Windows environment, the Perl Data::Serialize module does not work properly.
In the package Serialized.pm, subroutine recv (row 115), the chomp does not remove the damned Windows line ending '\cM'. When I corrected it, it started working as envisaged. Actually there is a lenghty discussion of this behaviour here ( http://www.perlmonks.org/?node_id=549385 )
Thanks for the suggestions.
For object method the first argument is always the current object instance itself. Sorry, if I am not clear enough, try to figure out the difference from this example:
Try this:
sub predejPOS {
my $self = shift;
my $key = shift;
my %POS;
$POS{'flu-like'}='<JJ>';
return $POS{$key};
};
package My::Module;
# $Id$
use strict;
use Carp;
use Data::Dumper;
use DBI;
$My::Module::VERSION = '0.1';
sub new {
my ($class, %opt) = #_;
my $opt_count = keys %opt;
$class->set_error('');
#return $class->set_error("Too many arguments to initialize.") if ($opt_count > 5);
#return $class->set_error("Missing arguments to initialize.") if ($opt_count < 2);
my $self = bless {
_DRIVER_OPTIONS => $opt{'mysql'},
},$class;
if (not defined $self) {
return $class->set_error( "new() failed: " . $class->errstr );
}
if ($self->{_DRIVER_OPTIONS}->{Host} ne '') {
$self->{_DRIVER_OPTIONS}->{DataSource} = 'DBI:mysql:database=' . $self->{_DRIVER_OPTIONS}->{Database} . ';host=' . $self->{_DRIVER_OPTIONS}->{Host};
} else {
$self->{_DRIVER_OPTIONS}->{DataSource} = 'DBI:mysql:database=' . $self->{_DRIVER_OPTIONS}->{Database} . ';';
}
$self->{Handle} = DBI->connect($self->{_DRIVER_OPTIONS}->{DataSource},
$self->{_DRIVER_OPTIONS}->{Username},
$self->{_DRIVER_OPTIONS}->{Password},
{ RaiseError=>1, PrintError=>1, AutoCommit=>1 }
);
return $self->set_error("new(): couldn't connect to database: " . DBI->errstr) unless ($self->{Handle});
$self->{_disconnect} = 1;
print Dumper \$self;
return $self;
}
sub database {
my $self = shift;
if (#_) { $self->{Handle} = shift }
return $self->{Handle};
}
sub set_error {
my $class = shift;
my $message = shift;
$class = ref($class) || $class;
no strict 'refs';
${ "$class\::errstr" } = sprintf($message || "", #_);
return;
}
*error = \&errstr;
sub errstr {
my $class = shift;
$class = ref( $class ) || $class;
no strict 'refs';
return ${ "$class\::errstr" } || '';
}
sub DESTROY {
my $self = shift;
unless (defined $self->{Handle} && $self->{Handle}->ping) {
$self->set_error(__PACKAGE__ . '::DESTROY(). Database handle has gone away');
return;
}
unless ($self->{Handle}->{AutoCommit}) {
$self->{Handle}->commit;
}
if ($self->{_disconnect}) {
$self->{Handle}->disconnect;
}
}
1;
Is that the right way so i can
re-use the database on my code
instead of having to open a new
connection or that will aswell open
a new connection every time i use it
?
Should i change anything on the
module ? or anything i did wrong ?
Currently i am just learning and thinked of doing my own engine module so i began with this.
Simple test code (the bellow code is not to be reviewed just a sample on how to use the module):
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use lib 'path to module';
use My::Module;
my $session = My::Module->new(mysql => {
Database =>'module',
Host =>'10.0.0.2',
Username =>'module',
Password =>'module'
}) or die My::Module->errstr;
my $dbh = $session->database();
my $sth = $dbh->prepare(q{
SELECT session_id
FROM sessions
});
$sth->execute() || die print($dbh->errstr);
my $ref = $sth->fetchall_arrayref({});
$sth->finish;
print Dumper \$ref;
I would suggest using an existing database interface rather than rolling your own, as there are many secret gotchas that others have spent years figuring out and solving for you. DBIx::Connector is excellent, and with its fixup mode, will let you reuse database connections, even across process forks.
Additionally, if you use Moose you will never have to write your own object constructors or object fields again. :)
DBIx::Class combined with Moose would be even better, but not necessary until you find yourself needing more ORM-ish features.
Other than using a CPAN module to accomplish this task, here are my practical recommendations:
Don't return an error value from the constructor. Instead, throw an exception.
Access the internals of your class using accessors rather than using direct hash access.
If the user of your class did not enable AutoCommit, she chose not to enable AutoCommit for a reason. Therefore don't do:
unless ($self->{Handle}->{AutoCommit}) {
$self->{Handle}->commit;
}
in DESTROY.
Note that bless is not going to fail so long as it is given a modifiable reference (compare this to, say, the behavior of open which can fail to open a file even though the argument to open is a valid filename and would indicate this situation by returning a false value). Therefore, checking the return value of bless does not serve any useful purpose. If you want to handle the possibility of bless failing, you will have to catch fatal runtime exceptions.
Your way of exposing errors is very, very oldfashioned. If something exceptional happens, why not raise a proper exception? You seem to have modelled your error handling after the DBI module. Note that DBI also has a RaiseError option. Using that is almost always more reasonable than using the oldfashioned errorstr version. Unfortunately DBI can't change it's default anymore now, but for new code I entirely don't see the reason to copy this flawed idea.
You're also constructing a DBI connection within your code, based on parameters the user provided from the outside. Do you have a good reason for doing that? Allowing the user to pass in the DBI::dh he constructed himself would be more flexible. Yes, that requires slightly more code on the outside to set up objects and wire them together, but it will also result in a cleaner design. If wiring up your objects manually bothers you too much, you might want to have a look at Bread::Board to do the wiring for you instead of compromising on the design of your module.
Also, I second Ether's suggestion of using DBIx::Connector. It really takes a lot of pain out of managing database handles, which can be very error-prone.