Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Mass Class Confusion - Who calls what how?

by holandes777 (Scribe)
on Feb 20, 2020 at 19:36 UTC ( #11113274=perlquestion: print w/replies, xml ) Need Help??

holandes777 has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks:

I am attempting to learn how to use objects and have made progress, but here I am stymied. I am using HTTP::Server::Simple::CGI;= and having cognitive difficulties getting it to pass the incoming message to another class. I thought the answer was in the post_setup_hook method, but when I try to run I get a error that says I cannot call SUPER when I reach the handle_request method.

What I am trying to do below is to geth the message from the handle_request method of HTTP::Server::Simple::CGI;= to the process method of the Kiosk package and then to get the response to go back out. I tried passing the Kiosk object into the server first adn that did not work because I don't see how to add this to the "new" method of the server, so now I am trying to pass the server into the Kiosk module and attempting to get a hold of the message

this is the main program, as you can see I instantiate the server adn pass it to the Kiosk

#!/usr/bin/perl { package MyWebServer; # https://metacpan.org/pod/HTTP::Server::Simple # curl --header "Content-Type: application/json" --request POST -- +data '{ "type":"cardCheck", "date":"1482307795601", "lang": "EN", "ma +chineName":"PLUSMAKM", "cardNumber":"1234567890", "PIN": "1234" }' ht +tp://localhost:2999 use lib qw (/home/pi/cashcade /home/gary/projects/cashcade); use HTTP::Server::Simple::CGI; use base qw(HTTP::Server::Simple::CGI); # use HTTP::Server::Simple; # use base qw(HTTP::Server::Simple); use Data::Dumper; use JSON; sub handle_request { my $self = shift; my $cgi = shift; my $json_string = $cgi->param('POSTDATA'); # { "type":"cardCheck", "date":"1482307795601", "lang": "EN", +"machineName":"PLUSMAKM", "cardNumber":"1234567890", "PIN": "1234" } print "json_string=$json_string\n"; $json_dict = decode_json $json_string; print "json_dict=" . Dumper($json_dict) . "\n"; my $msg = $json_dict->{param}->{POSTDATA}->[0]; # my $response = $self->{kiosk}->process($msg); # note: this +says I cannot call on undefined, the problme is, how do I pass the ob +ject in # print "response=$response\n"; return $msg; } } sub sigIntHandler { exit; } END { print "MAIN: END*END*END*END*END*END*END*END*END*END*END*END*END*E +ND\n"; $kiosk->disconnect(); } my $kiosk_port = shift // 'unknown'; die "FATAL ERROR: Need to specify a port\n" unless ($kiosk_port ne 'un +known'); my $placard = shift // 'unknown'; die "FATAL ERROR: Need to specify a placard\n" unless ($placard ne 'un +known'); my $debug_level = shift // 9; # TODO: make level higher on final print "$placard, $debug_level\n"; use Kiosk; # start the server on port __ print "INSTANTIATING SERVER port $kiosk_port ========================= +=====\n"; my $server = MyWebServer->new($kiosk_port); print "INSTANTIATING KIOSK ==============================\n"; $kiosk = Kiosk->new($placard, $server, $debug_level);

In the Kiosk module (the first part below, I run the server when I instantiate the Kiosk

package Kiosk; # This implements communications to a VNE kiosk as an HTTP server # based on VNE JSON Communication Protocol Ver. 1.8 # Alessandro: add to protocol: change PIN? There is no card removed me +ssage use strict; use Device::SerialPort; #use Data::CISerializer; #use MIME::Base64; #use JSON; use Time::HiRes qw(usleep gettimeofday tv_interval time); use Data::Dumper; use Debug; use GetConfiguration; use Database; use DatabaseProcesses; $|=1; sub new { my ($class, $placard, $server, $debug_level) = @_; # config is a d +ata structure that has serial setup information my $self = {}; $self->{me} = 'Kiosk'; $self->{class} = $class; $self->{placard} = $placard; $self->{server} = $server; $self->{debug} = Debug->new($placard, "MAIN_$placard", $debug_leve +l); $self->{config} == GetConfiguration->new($self->{debug}, '/etc/cas +hcade/cashcade.conf'); $self->{debug}->prt(9, "KIOSK Config " . Dumper($self->{config}), +'KIOSK'); $self->{dbcomm} = Database->new($self->{debug}, $self->{configurat +ion}->{config}->{local_db}); # the DB connection must go first and su +cceed $self->{dbcomm}->connect(); die "Database unable to connect!" if (!$self->{dbcomm}->{connected +}); print "Database Connected\n"; $self->{dbproc} = DatabaseProcesses->new($self->{debug}, $self->{d +bcomm}); $self->{maxDeposit} = $self->{config}->{maxDepositCents}; $self->{maxWithdraw} = $self->{config}->{maxWithdrawCents}; $self->{debug}->prt(2, "KIOSK CONFIG: kiosk_id=$self->{kiosk_id} m +axDeposit=$self->{maxDeposit} maxWithdraw=$self->{maxWithdraw}", 'KIO +SK INIT'); $self->{connected} = 0; $self->{verbose} = 0; $self->{kiosk_id} = $self->{dbproc}->get_kiosk_sysid_from_placard( +$self->{placard}); $self->{card_number} = 0; # this is the latest card number seen $self->{account_id} = 0; # this is the account id while the card i +s in the reader of the kiosk $self->{account_value} = 0; # value of account in cents $self->{response} = ''; $self->{server}->run(); bless($self,$class); return $self; } sub process { my ($self, $msg) = @_; # $msg is the pointer to the incoming mess +age json hash # { "type":"cardCheck", "date":"1482307795601", "lang": "EN", "mac +hineName":"PLUSMAKM", "cardNumber":"1234567890", "PIN": "1234" } # TODO: is message for this machine $msg->{machineName} # TODO: is date of message reasonable $msg->{date} $self->{response} = ''; if ( $msg->{type} eq 'cardCheck') { return $self->card_was_inserted($msg); } if ( $msg->{type} eq 'cardLoad') { return $self->deposit_amount_received($msg); } if ( $msg->{type} eq 'cardWithdrawal') { return $self->withdrawal_amount_requested($msg); } }

Anyhow, I am confused after several hours of tinkering and appreciate your help.

Replies are listed 'Best First'.
Re: Mass Class Confusion - Who calls what how?
by bliako (Parson) on Feb 20, 2020 at 20:40 UTC
    my $response = $self->{kiosk}->process($msg)

    here you are attempting to execute method process() from an object which is held in $self->{kiosk}. Now the latter is undefined as you never set it.

    You can set the kiosk object inside $server after you create the kiosk like this:

    my $server = MyWebServer->new($kiosk_port); print "INSTANTIATING KIOSK ==============================\n"; $kiosk = Kiosk->new($placard, $server, $debug_level); #### let the server know what the kiosk obj is: $server->{kiosk} = $kiosk;

    That said, the "proper" way would be to create accessors or getter and setter methods to set the kiosk object and other parameters internal to a class with all the proper checks, if kiosk is empty and what not. The boilerplate code can be avoided and "properness" inserted automatically by using an OO system, for example Moose (edit: or Moo). It is not something that I ever used as I like my own OO style, but using such a system in a production environment gives you certain code-quality guarantees. Although you can entirely do without it (and skip a few kilos of cargo).

    One of the aims of OO is to black-box your program units into classes which do complex things internally but provide you with the simplest possible API for telling them what to do and on what data. Like, initialise($kiosk);, is_connected(), do_transaction(10). Making a class with tens of internal variables all exposed defies using OO in the first place and soon complexity will either kill your program or introduce so many bugs. From your code: you don't know which variable is primary, like $self->{kiosk} and which is derived, $self->{kiosk_id} which I assume could be set via the kiosk object. Perl's OO beauty, in my opinion, is its light-weight-ness. But it allows a lot of things which are generally critisised like indiscriminately exposing variables.

    So,

    package Server; ... sub new { ... $self = {kiosk=>undef}; ... return $self; } sub kiosk { my $self = shift; my $m = shift; if( defined $m ){ die unless $self->_set_kiosk($m); } return $self->{kiosk} } # returns 0 on failure, 1 on success sub _set_kiosk { my $self = shift; my $akiosk = shift; if( ! $akiosk->connected() ){ return 0 } $self->{kiosk_id} = $akiosk->id(); print "setting the kiosk to this: " . $akiosk. "\n"; return 1; } sub do_transaction { my $self = shift; return 0 unless defined $self->{kiosk}; ... }

    OO is one of the great tools programmers were given ever (that and the hashtable!). Use it wisely and to your advantage.

    bw, bliako

    Edit: this is related: Inheritable configuration options.... but with default values?

      your solution is correct. Passing in the $kiosk object makes it accessible to the message handler routine

      print "INSTANTIATING KIOSK ==============================\n"; my $kiosk = Kiosk->new($placard, $server, $debug_level); # start the server on port __ print "INSTANTIATING SERVER port $kiosk_port ========================= +=====\n"; my $server = MyWebServer->new($kiosk_port); $server->run(); $server->{kiosk} = $kiosk;

      this part in HTTP::Server::Simple::CGI; sees it just fine and responds properly ... so THANK YOU.

      sub handle_request { my $self = shift; my $cgi = shift; my $json_string = $cgi->param('POSTDATA'); # { "type":"cardCheck", "date":"1482307795601", "lang": "EN", +"machineName":"PLUSMAKM", "cardNumber":"1234567890", "PIN": "1234" } print "json_string=$json_string\n"; $json_dict = decode_json $json_string; print "json_dict=" . Dumper($json_dict) . "\n"; my $response = $self->{kiosk}->process($json_dict); print "response=$response\n"; return $response; }

      there was one issue in fully implementing your solution (the code worked without it). When you add the "new" method to HTTP::Server::Simple::CGI;object as follows you get an execution error

      sub new { my $self = {}; $self->{class} = $class; $self->{kiosk} = undef; bless($self,$class); return $self; }

      Can't locate object method "run" via package "main" at http_server.pl line 56, <CONF> line 35. line 56 is

      $server->run();

      this was the original reason I requested help. Now that I see the code a bit more I guess the question is "how do you exend the HTTP::Server::Simple::CGI; object, but that is another topic. SO I thank you for an excelent easy solution and, if you have any ideas about the ast question, I would love to hear it so I can gain more knowledge about OO in Perl.

        You will be benefited from a Perl OO tutorial, here is one picked at random http://wwwacs.gantep.edu.tr/docs/perl-ebook/ch19.htm. There is also the official tutorial: perlootut

        I assume you have read Re: Mass Class Confusion - Who calls what how?

        To inherit from a class just use (after the package declaration):

        use parent 'HTTP::Server::Simple::CGI';

        There is also something similar in HTTP::Server::Simple's documentation...

        But do you want that? Or do you want to instantiate a HTTP::Server::Simple::CGI object in your server and then call its run() method via your own run() whenever needed like so:

        sub new { my ($class, $params) = @_; $self = {server_simple_cgi => undef, ...}; bless $self => $class; $params = {} unless defined $params; $self->{$_} = $params->{$_} for keys %$params; if( ! defined $self->{server_simple_cgi} ){ $self->{server_simple_cgi} = HTTP::Server::Simple->new(); } ... } sub run { my ($self, $params) = @_; # do something with params # and then run the server, it must be a valid object by now $self->{server_simple_cgi}->run() }

        In this way you keep total control of your server and allow callers to control it only via certain API calls you provide (like the above run()) which may do additional checks, filter out things, before calling the server's, "real" run(). Note that this IS NOT about security (e.g. who is allowed to call what). Because a caller could still call $self->{server_simple_cgi}->run() (there are ways to avoid that: Objects with Private Variables). I am talking about the clarity and simplicity of the API you are building.

        bw, bliako

Re: Mass Class Confusion - Who calls what how?
by haukex (Chancellor) on Feb 20, 2020 at 21:20 UTC

    HTTP::Server::Simple is nice for prototyping and testing out lower-level stuff. However, I would recommend against it for anything resembling production. I realize it's a lot to ask to switch after one has already started working with one module, but I would strongly recommend you look at alternatives, like at the very least Plack, or even better, Mojolicious::Lite (see Mojolicious::Guides::Tutorial).

      Your advice is well founded and I will look into Mojolicious::Lite next, thank you.

Re: Mass Class Confusion - Who calls what how?
by Anonymous Monk on Feb 20, 2020 at 20:16 UTC

    Anyhow, I am confused ...

    good :)

    now is the time to write some class crc

    # my $response = $self->{kiosk}->process($msg); # note: this says I c +annot call on undefined, the problme is, how do I pass the object in

    a $self_server object knows nothing of a $self_kiosk

      thank you

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://11113274]
Approved by marto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (8)
As of 2020-04-07 14:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    The most amusing oxymoron is:
















    Results (42 votes). Check out past polls.

    Notices?