Using of standalone model in Catalyst - calling of general methods - perl

I am following receipt of using standalone models in Catalyst in the advent calendar from 2012 here:
http://www.catalystframework.org/calendar/2012/15
and previous days ....
Following this receipt I am transferring existing code from current fat model. I want to have some methods in Model on general level - I expect to put them in this example into lib/StandaloneApp3.pm file (eg. create order means to create record in table order_header, table order_item and table log) and call them from Controller with code:
$c->model('DB')->create_order($params);
I am getting error:
"Can't locate object method "create_order" via package
"WebApp::Model::DB"
When calling function from StandaloneApp3 namespace, this work:
&StandaloneApp3::create_order($params);
I do not think this is good approach as my Controller should connect only to Catalyst Model and not directly to standalone library.
Is there an error in my code to access methods in StandaloneApp3.pm or when using DBIC::Schema I am not expect to call this method? In case I am not supposed to call methods in StandaloneApp3.pm, what is the correct place were to store methods that can write to more tables in one transaction? Any example also for exact calling those methods from outside Catalyst - eg. from command line?
Thank you for your explanation and example.
----------- ADDED CODE -------------
Catalyst model:
file: lib\WebApp\Model\DB.pm:
package WebApp::Model::DB;
use strict;
use base 'Catalyst::Model::DBIC::Schema';
1;
Standalone Model:
file: lib/StandaloneApp3.pm
use utf8;
package StandaloneApp3;
use Moose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Schema';
PACKAGE->load_namespaces;
sub create_order {
# in development
return "order_created";
}
PACKAGE->meta->make_immutable(inline_constructor => 0);
1;
Config:
file: webapp.conf
<Model::DB>
schema_class StandaloneApp3
<connect_info>
dsn dbi:SQLite:__path_to(data,database_file2.db)__
</connect_info>
</Model::DB>
Thank you

You are using StandaloneApp3 as your schema class inside of the catalyst model WebApp::Model::DB. That means it's the DBIx::Class schema that is automatically loaded for you. The schema is something like a database object.
To access the schema, you use the schema accessor method on the model.
$c->model('DB')->schema;
So to reiterate, the schema is not part of the model. The model does not inherit from the schema. It just has the schema as an attribute.
Therefore the method create_order in the schema class StandaloneApp3 is not a method on the model WebApp::Model::DB. It's a method on its schema attribute.
$c->model('DB')->schema->create_order;
The easiest way to patch that through would be to make a method create_order in WebApp::Model::DB that calls the method with the same name on the schema.
package WebApp::Model::DB;
# ...
sub create_order {
my $self = shift;
return $self->schema->create_order(#_);
}
I shifted out the $self so I can pass on the rest of the argument list #_ to $self->schema->create_order.
To prove this works, I wrote create_order in StandaloneApp3 like this:
package StandaloneApp3;
# ...
# You can replace this text with custom code or comments, and it will be preserved on regeneration
sub create_order {
# in development
warn "creating order...";
return "order_created";
}
I put the warn in so we can find it in the log that you have in your console later. Note how it's placed underneath DBIC's comment, because we don't want it to be overwritten if we need to regenerate the schema class later in case someone made changes to the database schema.
Finally I'm calling the model method from sub index in the Root controller.
sub index :Path :Args(0) {
my ( $self, $c ) = #_;
$c->model('DB')->create_order;
# Hello World
$c->response->body( $c->welcome_message );
}
If you now request / in your browser (for me that's http://localhost:5000/) now it works, and shows up in the log. Mine is on Windows so it looks a bit odd.
[info] *** Request 1 (0.143/s) [8012] [Sun Nov 27 16:25:59 2016] ***
[debug] Path is "/"
[debug] "GET" request for "/" from "127.0.0.1"
creating order... at lib/StandaloneApp3.pm line 23.
127.0.0.1 - - [27/Nov/2016:16:25:59 +0100] "GET / HTTP/1.1" 200 5477 "-" "Mozill
a/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0
.2840.99 Safari/537.36"
[debug] Response Code: 200; Content-Type: text/html; charset=utf-8; Content-Leng
th: unknown
[info] Request took 0.012028s (83.139/s)
.------------------------------------------------------------+-----------.
| Action | Time |
+------------------------------------------------------------+-----------+
| /index | 0.000545s |
| /end | 0.000340s |
'------------------------------------------------------------+-----------'
But of course if you have a couple of those methods you want to patch through, it gets a bit tedious to write all of them yourself. You could use AUTOLOAD to create methods on the fly, but that seems wrong in this situation, so I'm not going to explain that.
Instead, let's use Catalyst::TraitFor::Model::DBIC::Schema::SchemaProxy, which is a model trait that's included with Catalyst::Model::DBIC::Schema for exactly this situation.
You load it from the configuration file or inside the __PACKAGE__->config(...) in your code. I prefer it there, because it's easier to see what's going on when reading the code, and you can still overwrite it later in the configuration files.
Here is the full WebApp::Model::DB file. I removed the method create_order we put there in the example above, and included the trait configuration instead.
package WebApp::Model::DB;
use strict;
use base 'Catalyst::Model::DBIC::Schema';
__PACKAGE__->config(
traits => 'SchemaProxy',
);
1;
That's all you need to change. If you restart the app and request the root / again, it will still work.
[info] *** Request 1 (0.111/s) [6160] [Sun Nov 27 16:43:45 2016] ***
[debug] Path is "/"
[debug] "GET" request for "/" from "127.0.0.1"
creating order... at lib/StandaloneApp3.pm line 23.
127.0.0.1 - - [27/Nov/2016:16:43:45 +0100] "GET / HTTP/1.1" 200 5477 "-" "Mozill
a/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0
.2840.99 Safari/537.36"
[debug] Response Code: 200; Content-Type: text/html; charset=utf-8; Content-Leng
th: unknown
[info] Request took 0.011867s (84.267/s)
.------------------------------------------------------------+-----------.
| Action | Time |
+------------------------------------------------------------+-----------+
| /index | 0.000712s |
| /end | 0.000370s |
'------------------------------------------------------------+-----------'
I suggest you take a few minutes and read the CPAN pages of the modules I linked in this answer. I just did the same. Catalyst can become very complex underneath and it's hard to remember all of it even if you use it a lot, so freshening up on the docs once in a while is a good idea.

Related

Can't connect to ... nodename nor servname provided, or not known

My question: why does my perl script--successful via home laptop--not work when run in the context of my hosting website? (Perhaps they have a firewall, for example. Perhaps my website needs to provide credentials. Perhaps this is in the realm of cross-site scripting. I DON'T KNOW and appeal for your help in my understanding what could be the cause and then the solution. Thanks!)
Note that all works fine IF I run the perl script from my laptop at home.
But if I upload the perl script to my web host, where I have a web page whose javascript successfully calls that perl script, there is an error back from the site whose URL is in the perl script (finance.yahoo in this example).
To bypass the javascript, I'm just typing the URL of my perl script, e.g. http://example.com/blah/script.pl
Here is the full error message from finance.yahoo when $url starts with http:
Can't connect to finance.yahoo.com:80 nodename nor servname provided, or not known at C:/Perl/lib/LWP/Protocol/http.pm line 47.
Here is the full error message from finance.yahoo when $url starts with https:
Can't connect to finance.yahoo.com:443 nodename nor servname provided, or not known at C:/Perl/lib/LWP/Protocol/http.pm line 47.
Code:
#!/usr/bin/perl
use strict; use warnings;
use LWP 6; # one site suggested loading this "for all important LWP classes"
use HTTP::Request;
### sample of interest: to scrape historical data and feed massaged facts to my private web page via js ajax
my $url = 'http://finance.yahoo.com/quote/sbux/profile?ltr=1';
my $browser = LWP::UserAgent->new;
# one site suggested having this empty cookie jar could help
$browser->cookie_jar({});
# another site suggested I should provide WAGuess
my #ns_headers = (
'User-Agent' =>
# 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0',
'Accept' => 'text/html, */*',
'Accept-Charset' => 'iso-8859-1,*,utf-8',
'Accept-Language' => 'en-US',
);
my $response = $browser->get($url, #ns_headers);
# for now, I just want to confirm, in my web page itself, that
# the target web page's contents was returned
my $content = $response->content;
# show such content in my web page
print "Content-type: text/html\n\n" . $content;
Well it is not obvious what is your final goal and it is possible that you over complicate the task.
You can retrieve above mentioned page with simpler perl code
#!/usr/bin/env perl
#
# vim: ai:ts=4:sw=4
#
use strict;
use warnings;
use feature 'say';
use HTTP::Tiny;
my $debug = 1;
my $url = 'https://finance.yahoo.com/quote/sbux/profile?ltr=1';
my $responce = HTTP::Tiny->new->get($url);
if ($responce->{success}) {
my $html = $responce->{content};
say $html if $debug;
}
In your post you indicated that javascript is somehow involved -- it is not clear how and what it's purpose in retrieving of the page.
Error message has a reference to at C:/Perl/lib/LWP/Protocol/http.pm line 47 which indicates that web hosting is taking place on Windows machine -- it would be nice to indicate it in your message.
Could you shed some light on purpose of following block in your code?
# WAGuess
$browser->env_proxy;
# WAGuess
$browser->cookie_jar({});
I do not see cookie_jar be utilized in your code anywhere.
Do you plan to use some authentication approach to extract some data under your personal account which is not accessible otherwise?
Please state in a few first sentences what you try to achieve on grand scale.
Perhaps it's about cookies or about using yahoo's "query" url instead.
Yahoo Finance URL not working

Server does not have Digest::SHA 'hmac_sha256_base64' installed, can I use Digest::HMAC_SHA1 'hmac_sha1' instead?

I am preparing a CGI script that needs to confirm that the body of a incoming post request message, when is converted into a HMAC-SHA256 hash, is exactly the same content that also comes in a header tag of the same incoming message.
I have been able to confirm using Python that the procedure is as explained above, but when I do the same functionality on a CGI script I cannot match the contents and probably is because I am not using the correct encryption / hashing library.
My server provider does not have the Digest::SHA library and thus, I can not use the 'hmac_sha256_base64' function. I cannot ask them to install it, I just can use what is already available.
I have checked the available libraries and there is a Digest::HMAC_SHA1 'hmac_sha1' library / function. So I am doing as follows:
my $q = CGI->new;
my %headers = map { $_ => $q->http($_) } $q->http();
# below is the secret key, is an example but I am using the good one
my $channel_secret="abcdabcdabcdabcdabcdabcdabcdabcd"
# Incoming request body string
my $httpRequestBody = $q->param( 'POSTDATA' );
# now, I want to use Digest::SHA hmac_sha256_base64 but this server
# does not have it so I am using the following one...
# because I thought it was the equivalent new function to do the same
# but probably it is not...
use Digest::HMAC_SHA1 'hmac_sha1';
use MIME::Base64 'encode_base64';
$digest = hmac_sha1($httpRequestBody, $channel_secret);
my $signature = encode_base64($digest);
So basically I expect that these two variables contain the same string:
$headers{'A_EXISTING_TAG_OF_THE_HEADER'}
$signature
But they are totally different. I suspect that I am not using the correct algorithm.
So my question is:
If my server provider does not include Digest::SHA 'hmac_sha256_base64' in the available libraries, then what other alternatives do I have to make the same? Is Digest::HMAC_SHA1 'hmac_sha1' the same functionality or not?
Download the tarball for Digest::SHA::PurePerl ( you'll find the download link on this page https://metacpan.org/pod/Digest::SHA::PurePerl )
Create a library folder, something like this
.
|-- library
| `-- Digest
| `-- SHA
| `-- PurePerl.pm
`-- your_script.pl
your_script.pl looks like this, you can implement similarly:
#!/usr/bin/perl
use lib '.';
use lib '/tmp/iadvd/library/';
use Digest::SHA::PurePerl qw(sha1 sha1_hex);
print sha1_hex('Pradeep'),"\n";

can't locate object method "see" via package "Net::IMAP::Simple"

I am trying to fetch unread emails from server and mark them as seen (read) after fetching. I'm using perl script and the package Net::IMAP::Simple. I'm writing this script for the office work.
Below is some code :
use strict;
use warnings;
use Net::IMAP::Simple;
use Email::Simple;
use HTTP::Date;
#some code....
# Create the object
my $server = Net::IMAP::Simple->new($imap_server) or die "Can't connect to server: $imap_server ";
# Log on
my $login = $server->login($imap_user,$imap_passwd) or die "Login failed (bad username or password)";
#some code.....
# set the message as seen
$server->see($i);
# i also used $server->add_flags($i,'\Seen'); but it throws same error.
The Irony is, this code works fine on my Gmail account , which i use for testing. but when i test it in office it throws error ;
can't locate object method "see" via package "Net::IMAP::Simple"
I don't know what's the issue here.
The methods see and unsee were added in Net::IMAP::Simple version 1.1899_05.
> 1.1899_05: Tue Jun 16 06:42:16 EDT 2009
> - I started working on ticket 45953,
> - created sub_flags() and add_flags()
> - taught delete() to use add_flags() -- adds \Deleted
> - providing see() and unsee() for ticket 45953
> - I started building tests for the flag manipulation stuff and
> put reselect stuff ... noticed a possible bug in
> Net::IMAP::Server
You likely have an older version on your production system. Update it, and include a minimum version in your Makefile.PL or cpanfile or whatever you use to track dependencies.

HTTP::Daemon and threads

I have the following code on Windows XP and ActiveState ActivePerl 5.8.
What could be the problem with it? Why does it not work?
I tried to set it as a proxy to my IE but when I connect to some URLs from my IE nothing happens. The code enters the thread function and nothing happens.
use HTTP::Daemon;
use threads;
use HTTP::Status;
use LWP::UserAgent;
my $webServer;
my $d = HTTP::Daemon->new(
LocalAddr => '127.0.0.1',
LocalPort => 80,
Listen => 20
) || die;
print "Web Server started!\n";
print "Server Address: ", $d->sockhost(), "\n";
print "Server Port: ", $d->sockport(), "\n";
while (my $c = $d->accept) {
threads->create(\&process_one_req, $c)->detach();
}
sub process_one_req {
STDOUT->autoflush(1);
my $c = shift;
while (my $r = $c->get_request) {
if ($r->method eq "GET") {
print "Session info\n", $r->header('Host');
my $ua = LWP::UserAgent->new;
my $response = $ua->request($r);
$c->send_response($response);
} else {
$c->send_error(RC_FORBIDDEN);
}
}
$c->close;
undef($c);
}
I added the following line to the code before LWP::UserAgent->new and it seems to be working for me (in linux).
$r->uri("http://" . $r->header('Host') . "/" . $r->uri());
The uri that you got from the HTTP::Request object from the original request does not have the hostname. So added it to make it a absolute uri. Tested as follows:
$ curl -D - -o /dev/null -s -H 'Host: www.yahoo.com' http://localhost:8080/
HTTP/1.1 200 OK
Date: Thu, 27 Jan 2011 12:59:56 GMT
Server: libwww-perl-daemon/5.827
Cache-Control: private
Connection: close
Date: Thu, 27 Jan 2011 12:57:15 GMT
Age: 0
---snip--
UPDATE: Looks like I was completely wrong. I didnt need to make the change to URI object. Your original code worked for me as it is in Linux
If I recall correctly, this is because of the threading model in Windows where file handles are not passed between processes unless specifically asked for. This PerlMonks post seems to shed some light on the underlying problem, and may lead to an approach that works for you (I imagine you may be able to call the windows API on the file descriptor of of the client connection to allow access to it within the spawned thread).
Perl threads on Windows generally make my head hurt, while on UNIX-list systems I find them very easy to deal with. Then again, I imagine figuring out how to correctly use forked processes to emulate threads on a system that ONLY supports threads and not forking would make most people's head hurt.

Why does my simple fastCGI Perl script fail?

I'm not of the Perl world, so some of this is new to me. I'm running Ubuntu Hardy LTS with apache2 and mod_fcgid packages installed. I'd like to get MT4 running under fcgid rather than mod-cgi (it seems to run OK with plain-old CGI).
I can't seem to get even a simple Perl script to run under fcgid. I created a simple "Hello World" app and included the code from this previous question to test if FCGI is running.
I named my script HelloWorld.fcgi (currently fcgid is set to handle .fcgi files only). Code:
#!/usr/bin/perl
use FCGI;
print "Content-type: text/html\n\n";
print "Hello world.\n\n";
my $request = FCGI::Request();
if ( $request->IsFastCGI ) {
print "we're running under FastCGI!\n";
} else {
print "plain old boring CGI\n";
}
When run from the command line, it prints "plain old boring..." When invoked via an http request to apache, I get a 500 Internal Server error and the output of the script is printed to the Apache error log:
Content-type: text/html
Hello world.
we're running under FastCGI!
[Wed Dec 03 22:26:19 2008] [warn] (104)Connection reset by peer: mod_fcgid: read data from fastcgi server error.
[Wed Dec 03 22:26:19 2008] [error] [client 70.23.221.171] Premature end of script headers: HelloWorld.fcgi
[Wed Dec 03 22:26:25 2008] [notice] mod_fcgid: process /www/mt/HelloWorld.fcgi(14189) exit(communication error), terminated by calling exit(), return code: 0
When I run the .cgi version of the same code, it works fine. Any idea why the output of the script is going to the error log? Apache config is the default mod_fcgid config plus, in a VirtualHost directive:
ServerName test1.example.com
DocumentRoot /www/example
<Directory /www/example>
AllowOverride None
AddHandler cgi-script .cgi
AddHandler fcgid-script .fcgi
Options +ExecCGI +Includes +FollowSymLinks
</Directory>
The problem is that the "Content-Type" header is sent outside of the request loop. You must print the "Content-Type" header for every request. If you move
print "Content-type: text/html\n\n";
to the top of the request loop it should fix the problem.
Also, you need to loop over the requests or you'll get no benefit, so following the first poster's example:
my $request = FCGI::Request();
while($request->Accept() >= 0) {
print("Content-type: text/html\n\n");
}
I use CGI::Fast more than FCGI, but the idea is the same, I think. The goal of fast cgi is to load the program once, and iterate in a loop for every request.
FCGI's man page says :
use FCGI;
my $count = 0;
my $request = FCGI::Request();
while($request->Accept() >= 0) {
print("Content-type: text/html\r\n\r\n", ++$count);
}
Which means, you have to Accept the request before being able to print anything back to the browser.
Movable Type uses CGI::Fast for FastCGI. The typical FastCGI script runs in a loop, as mat described. A loop that uses CGI::Fast would look like this:
#!/usr/bin/perl
use strict;
use CGI::Fast;
my $count = 0;
while (my $q = CGI::Fast->new) {
print("Content-Type: text/plain\n\n");
print("Process ID: $$; Count is: " . ++$count);
}
I tested this script on a server with the FCGI and CGI::Fast modules installed and count increments as you'd expect. If the process id changes, count will go back to 1 and then increment within that process. Each process has it's own variable space of course.
For MT, enabling FastCGI a matter of renaming (or symlinking) the cgi scripts to 'fcgi' (or making the handler for 'cgi' scripts fcgid, but that won't work for mt-xmlrpc.cgi which isn't FastCGI friendly yet). You'll also need to add some directives to your mt-config.cgi file so that it knows the new script names. Like this:
AdminScript mt.fcgi
CommentsScript mt-comments.fcgi
And so forth. More documentation specific to FastCGI and Movable Type is available on movabletype.org.
Anyway, based on your server's error logs, it looks like FCGI is working, and being invoked properly, but your script just isn't running in a loop, waiting for the next request to come along. So your test script did accomplish the task -- reporting whether FastCGI is configured or not. So now you should be able to reconfigure MT to use FastCGI.