I'm building an app with Mojolicious Lite and I'm looking to give myself a way to watch any and all data about the active sessions. I'm mostly doing this because this is my first foray into using sessions with Mojolicious Lite, and I want to watch what's going on under the hood.
A couple notes:
I'm pretty new to Mojolicious, as you might guess by the fact that I'm using Mojolicious Lite. Any Mojolicious Lite apps I've written before have been pretty trivial, so my familiarity with it is not deep.
For that matter, I'm still 'early intermediate, at best' with perl, so poking around the inner workings of anything OO in perl is largely foreign territory for me.
That said, I made myself a few little routes like so:
get '/firstpage' => sub{
my $self = shift;
my $usr = $self->session(user => 'first_user');
$self->render(text => $usr);
};
get '/secondpage' => sub{
my $self = shift;
my $usr = $self->session(user => 'second_user');
$self->render(text => $usr);
};
get '/sessions' => sub{
my $self = shift;
$self->render(text => Dumper(app->sessions));
};
I'm working off the assumption that, after I visit the first two urls, Mojolicious will have 'some' data somewhere that would confirm that it knows about first_user and second_user. (I could also be totally off base in my understanding of how to use Mojolicious Lite sessions...honestly, from the documentation, I'm not really sure.)
Sadly, /sessions just shows me the contents of the Mojolicious::Sessions object:
$VAR1 = bless( {
'cookie_path' => '/',
'secure' => 0,
'cookie_name' => 'mojolicious',
'default_expiration' => 3600
}, 'Mojolicious::Sessions' );
But I'm assuming that, somewhere, I can get to a hash of all of the session-related data that Mojolicious has. I've been poking around the documentation for a while but I have yet to find any leads.
Any insight?
I'm working off the assumption that, after I visit the first two urls, Mojolicious will have 'some' data somewhere that would confirm that it knows about first_user and second_user. (I could also be totally off base in my understand of how to use Mojolicious Lite sessions...honestly, from the documentation, I'm not really sure.)
Yeah, I think you're missing the point of sessions. The server/app doesn't remember the state of every user who visits. To allow it to look as if it did, we have cookies. A session is a per-client persistence thing.
Session information is just a hashreference, encoded as JSON and stored in a cookie on the client side. This is useful for remembering that you are logged in, as what username, perhaps an arrayref of things in your shopping cart. When you request a page, this cookie is sent back to the server, which can access the data and prepare the response for you knowing the state of your session.
As such there is no record of "active sessions". All that information is distributed amongst all the clients.
If you would like to get a better idea of what's going on, may I recommend tempire's plugin Mojolicious::Plugin::ConsoleLogger which for the current request shows all of the relevant information (session, stash etc) in your browser's javascript console.
Here is an example.
#!/usr/bin/env perl
use Mojolicious::Lite;
#plugin 'ConsoleLogger'; # if desired
any '/' => sub {
my $self = shift;
my $name = $self->session('name') || 'Unknown'; # get the name from the session
$self->render( text => "Hello $name" );
};
any '/name/:name' => sub {
my $self = shift;
my $name = $self->stash('name'); # get name from path
$self->session( name => $name ); # and store it in the session
$self->redirect_to('/');
};
any '/logout' => sub {
my $self = shift;
$self->session( expires => 1 );
$self->redirect_to('/');
};
app->start;
If you visit /name/ghorahn it will store your name in a cookie. From then on, every time you visit / it will say hello to you until:
Your session expires (default 1 hour from your last visit)
You change your name via /name/whatever
You visit /logout to manually expire the session
You will notice that another user (either on another computer or even a different browser on your same computer) may have a different name, but both are persistent. That is what a session is for. :-)
Related
my $jira = JIRA::REST->new({
url => 'https://something.com:8443',
username => 'username',
password => 'password',
session => 1,
});
The above code doesn't work and fails with below error probably due to port number at the end
Can't connect to something.com:8443 (Bad file descriptor)
is there a way/variable to mention the port number?
You need to add the setting ssl_verify_none => 1 into the hash used in your constructor.
The underlying LWP code allows you to not verify the certs of systems you connect to (which is not recommended for production), or it also allows you to specify a Certificate Authority (CA) cert that can be used to verify certs of systems you connect to. It looks like JIRA::REST has only supported the first option.
You might be better off just using the underlying LWP code, like this:
use LWP::UserAgent;
my $ua = LWP::UserAgent->new(
ssl_opts => { verify_hostname => 1, SSL_ca_file => 'myCA.cer' },
protocols_allowed => ['https'],
);
my $req = HTTP::Request->new(
GET => 'https://something.com:8443/rest/api/latest/issue/ABC-123',
);
$req->authorization_basic('username','password');
my $res = $ua->request($req);
It looks like JIRA::REST is just providing the raw JSON response to you anyway, so it's not really saving you all that much processing.
The main advantage of REST::Client is that it saves some stuff for you to add to each request implicitly. There's nothing particularly REST-y or helpful beyond sending a request and giving its response back to you.
The JIRA::Client has a few advantages, though, since it knows how to get a session cookie and properly attach files, and, more importantly, deal with paginated results. But often, when I'm doing things with Jira, I want more power.
Back in LWP's heyday, it was very frustrating to track transactions: a request-response pair. You could, but you had to manage it yourself. And, there weren't hooks in the process, so you had to create everything every time.
Then, LWP tried to work around some SSL issues (verifying host names, etc) and also split out LWP::Protocol::https. That's not a big deal when you understand that, but even though I do, it's something I have to remember every time I want to use LWP for something. There are reasons for everything that happened, but that doesn't make it any less annoying. That leads to the sort of work jimtut showed in his answer. Every time. But, it's a small speed bump on the way to insecurity.
I like Mojolicious much more because it represents complete transactions but also has hooks (well, events) that allow you to fiddle with things automatically while the process is chugging along.
Here's an example from Mojo Web Clients that shows me creating a user-agent for each service and setting some stuff for each transaction. I can adjust the request any way that I please before it does its work (and this is mostly what that REST::Client and JIRA::Client are doing for you):
my $travis_ua = Mojo::UserAgent->new();
$travis_ua->on( start => sub {
my( $ua, $tx ) = #_;
$tx->req->headers->authorization(
"token $ENV{TRAVIS_API_KEY}" );
$tx->req->headers->accept(
'application/vnd.travis-ci.2.1+json' );
} );
my $appveyor_ua = Mojo::UserAgent->new();
$appveyor_ua->on( start => sub {
my( $ua, $tx ) = #_;
$tx->req->headers->authorization(
"Bearer $ENV{APPVEYOR_API_KEY}" );
} );
In the Basic auth case, it's just a different value in that header:
use Mojo::Util qw(b64_encode);
my $jira_ua = Mojo::UserAgent->new();
$jira_ua->on( start => sub {
my( $ua, $tx ) = #_;
$tx->req->headers->authorization(
'Basic ' . b64_encode( join ':', $username, $password ) );
} );
Now, when I use those user-agents, the auth stuff is automatically added:
my $tx = $travis_ua->get( $url );
And, that $tx gives me access to the request and the response, so I don't need REST::Client to handle that for me either.
Since Mojolicious is handling all of this in one convenient package, I don't have to wrangle different objects. As such, there's not much left over that REST::Client can do for me.
I am trying to use the Twitter streaming API to demo tweets in the console but nothing is being printed on screen at the moment. I am using the AnyEvent::Twitter::Stream package to do so, I have taken the OAuth example contained on that page and edited it slightly:
#!/usr/local/bin/perl -w
use AnyEvent::Twitter::Stream;
# to use OAuth authentication
my $listener = AnyEvent::Twitter::Stream->new(
consumer_key => "my_consumer_key",
consumer_secret => "my_consumer_secret",
token => "my_token",
token_secret => "my_token_secret",
method => "sample",
api_url => "https://stream.twitter.com/1.1/statuses/sample.json",
track => "apple",
on_tweet => sub {
my $tweet = shift;
print "$tweet->{user}{screen_name}: $tweet->{text}\n";
},
);
I was expecting this to print to stdout all tweets mentioning "apple", but when I execute this code, the script exits immediately and I am back to the shell. I was kind of expecting this to keep running and keep printing messages to stdout. Have I misunderstood how this works?
You may want to check out the provided example:
http://cpansearch.perl.org/src/MIYAGAWA/AnyEvent-Twitter-Stream-0.27/eg/track.pl
It involves creating an AE::cv object, and, after setting up the Twitter stream listener, calling recv on that object so that it actually loops waiting for data.
I've got an object that stores an LWP::UserAgent. I want to use different cookie jars for different calls with that UA, so I decided to make the cookie_jar local when doing a call.
The following code shows what I did without debug stuff (for reading, not running). Below is another version with lots of debugging output.
package Foo;
use strictures;
use Moo;
use LWP::UserAgent;
has ua => (
is => 'ro',
default => sub { my $ua = LWP::UserAgent->new; $ua->cookie_jar( {} ); return $ua; },
);
sub request {
my ($self, $cookie_jar) = #_;
local $self->{ua}->{cookie_jar} = $cookie_jar;
$self->ua->get('http://www.stackoverflow.com');
}
package main;
my $foo = Foo->new;
my $new_jar = HTTP::Cookies->new;
$foo->request( $new_jar );
So basically I decided to locally overwrite the cookie jar. Unfortunately, when we call get it will still use the cookie jar that is originally inside the UA object.
package Foo;
use strictures;
use Moo;
use LWP::UserAgent;
use HTTP::Cookies;
use Data::Printer;
use feature 'say';
has ua => (
is => 'ro',
default => sub { my $ua = LWP::UserAgent->new; $ua->cookie_jar( {} ); return $ua; },
);
sub request {
my ($self, $cookie_jar) = #_;
say "before local " . $self->{ua}->{cookie_jar};
local $self->{ua}->{cookie_jar} = $cookie_jar;
$self->ua->get('http://www.stackoverflow.com');
print "local jar " . p $self->{ua}->{cookie_jar};
say "after local " . $self->{ua}->{cookie_jar};
}
package main;
use Data::Printer;
use HTTP::Cookies;
my $foo = Foo->new;
say "before outside of local " . $foo->{ua}->{cookie_jar};
my $new_jar = HTTP::Cookies->new;
say "before outside of local " . $new_jar;
$foo->request( $new_jar );
say "after outside of local " . $foo->{ua}->{cookie_jar};
print "global jar " . p $foo->ua->cookie_jar;
__END__
before outside of local HTTP::Cookies=HASH(0x30e1848)
before outside of local HTTP::Cookies=HASH(0x30e3b20)
before local HTTP::Cookies=HASH(0x30e1848)
local jar HTTP::Cookies {
public methods (13) : add_cookie_header, as_string, clear, clear_temporary_cookies, DESTROY, extract_cookies, load, new, revert, save, scan, set_cookie, set_cookie_ok
private methods (3) : _host, _normalize_path, _url_path
internals: {
COOKIES {}
}
}after local HTTP::Cookies=HASH(0x30e3b20)
after outside of local HTTP::Cookies=HASH(0x30e1848)
global jar HTTP::Cookies {
public methods (13) : add_cookie_header, as_string, clear, clear_temporary_cookies, DESTROY, extract_cookies, load, new, revert, save, scan, set_cookie, set_cookie_ok
private methods (3) : _host, _normalize_path, _url_path
internals: {
COOKIES {
stackoverflow.com {
/ {
prov [
[0] 0,
[1] "185e95c6-a7f4-419a-8802-42394776ef63",
[2] undef,
[3] 1,
[4] undef,
[5] 2682374400,
[6] undef,
[7] {
HttpOnly undef
}
]
}
}
}
}
}
As you can see, the HTTP::Cookies object gets localized and replaced correctly. The addresses look totally correct.
But the output of p tells a different story. LWP::UA has not used the local cookie jar at all. That remains a fresh, empty one.
How can I make it use the local one instead?
I have tried using Moo, Moose and classic bless objects. All show this behaviour.
Edit: Since this came up in the comments, let me give a little more background why I need to do this. This is going to be a bit of a rant.
TLDR: Why I do not want alternative solution but understand and fix the problem
I'm building a Dancer2-based webapp that will run with Plack and multiple workers (Twiggy::Prefork - multiple threads in multiple forks). It will allow users to use a service of a third company. That company offers a SOAP webservice. Think of my application as a custom frontend to this service. There is a call to 'log the user in' on the webservice. It returns a cookie (sessionid) for that specific user and we need to pass that cookie with each consecutive call.
To do the SOAP-stuff I am using XML::Compile::WSDL11. Compiling the thing is pretty costly, so I do not want to do that each time a route is handled. That would be way inefficient. Thus the SOAP client will be compiled from the WSDL file when the application starts. It will then be shared by all workers.
If the client object is shared, the user agent inside is shared as well. And so is the cookie jar. That means that if there are two requests at the same time, the sessionids might get mixed up. The app could end up sending wrong stuff to the users.
That's why I decided to localize the cookie jar. If it's a local unique one for a request, it will never be able to interfere with another worker's request that is happening in parallel. Just making a new cookie jar for each request will not cut it. They would still be shared, and might even get lost because they would overwrite each other in the worst case.
Another approach would be to implement a locking mechanism, but that would totally beat the purpose of having multiple workers.
The only other solution I see is using another SOAP-client alltogether. There is SOAP::WSDL, which does not run on newer Perls. according to CPAN testers it breaks on 5.18 andI have verified that. It would be more efficient as it works like a code generator and precreates classes that are cheaper to use than just compiling the WSDL file every time. But since it's broken, it is out of the question.
SOAP::Lite will compile the WSDL, and badly. It is not something anyone should use in production if it can be avoided in my opinion. The only alternative left that I see is to implement the calls without using the WSDL file and parsing the results directly with an XML parser, ignoring the schema. But those are BIG results. It would be very inconvenient.
My conclusion to this rant is that I would really like to understand why Perl does not want to localize the cookie jar in this case and fix that.
Perhaps instead of using local you use the clone and cookie_jar methods of LWP::UserAgent.
...
sub request {
my ($self, $new_cookie_jar) = #_;
my $ua = $self->ua; # cache user agent
if( defined $new_cookie_jar ){
# create a new user agent with the new cookie jar
$ua = $ua->clone;
$ua->cookie_jar( $new_cookie_jar );
}
my $result = $ua->get('http://www.stackoverflow.com');
# allow returning the newly cloned user agent
return ( $result, $ua ) if wantarray;
return $result;
}
If you don't want to do that, you should at least use the methods instead of manipulating the internals of the objects.
...
sub request {
my ($self, $new_cookie_jar) = #_;
my $ua = $self->ua; # cache user agent
my $old_cookie_jar = $ua->cookie_jar( $new_cookie_jar );
my $result = $ua->get('http://www.stackoverflow.com');
# put the old cookie jar back in place
$ua->cookie_jar( $old_cookie_jar );
return $result;
}
I am using CGI.pm to write out cookies. Now during the course of the user using my site, other cookies are added to the "test.com" cookie set, (as shown in the broswer history)
But now I want to log the user out, and "clean" the PC. Since I don't know what scripts the user has used, I can't foresee what cookies would be on the PC.
In short, it there a way to read all the cookies for "test.com" back into a script so I can then print them out again with a 1s duration, (effectively 'deleting' them) ** I know you can read the cookie back in with $xyz=cookie('$name') ... but how can I create the array holding the $name variable so I can loop through it? The script will also run on "test.com", so the cross site policy is not an issue
+++++
brian d foy added a partial answer below. So this how I envisage the code might be strung together.
use CGI::Cookie;
%cookies = CGI::Cookie->fetch;
for (keys %cookies) {
$del_cookie.="cookie(-NAME=>'$cookies[$_]',-PATH=>'/',-EXPIRES=>'+1s');";
}
print header(-cookie=>[$del_cookie]);
I wondered how the script would recognise the domain. Appears the script is intelligent enough to only load the cookies for the domain for which the script is being executed on. (Now I've just got to find out why Firefox doesn't delete expired cookies!! Just found some listed that expired 29th - 31st Jan within my test domain, and at first wondered why they didn't appear in my cookie list!)
If you are trying to do this from your CGI script, you'll only have access to the cookies for that domain. You can get that list and reset them by giving them a time in the past.
It sounds like you aren't asking a cookie question at all. You're asking how to make an array. The CGI::Cookies (which comes with CGI.pm) has an example to deal with all the cookies you have access to under that domain:
%cookies = CGI::Cookie->fetch;
for (keys %cookies) {
do_something($cookies{$_});
}
This is what I ended up with:
use CGI::Cookies;
%cookies = CGI::Cookie->fetch;
#cookie = keys %cookies;
for($x=0; $x<#cookie; $x++){
my $c = CGI::Cookie->new(-name => $cookie[$x],-value => '-',-expires => '+1s');
print "Set-Cookie: $c\n";
}
print "content-type: text/html\n\n";
Firefox still leaves the cookies intact, (apparently that's a "design issue" and not a bug!!) but they are reset to a void value, and set to expire / become redundant in 1 second. Plus, quite why the "print" statement being sent before the "content-type" header doesn't cause a server error I don't know. OK, so purists will probably find a simpler system, and use "foreach" rather than for/next loop ... but I understand how the latter works!
I would like to write a script to login to a web application and then move to other parts
of the application:
use HTTP::Request::Common qw(POST);
use LWP::UserAgent;
use Data::Dumper;
$ua = LWP::UserAgent->new(keep_alive=>1);
my $req = POST "http://example.com:5002/index.php",
[ user_name => 'username',
user_password => "password",
module => 'Users',
action => 'Authenticate',
return_module => 'Users',
return_action => 'Login',
];
my $res = $ua->request($req);
print Dumper(\$res);
if ( $res->is_success ) {
print $res->as_string;
}
When I try this code I am not able to login to the application. The HTTP status code returned is 302 that is found, but with no data.
If I post username/password with all required things then it should return the home page of the application and keep the connection live to move other parts of the application.
You may be able to use WWW::Mechanize for this purpose:
Mech supports performing a sequence of page fetches including following links and submitting forms. Each fetched page is parsed and its links and forms are extracted. A link or a form can be selected, form fields can be filled and the next page can be fetched. Mech also stores a history of the URLs you've visited, which can be queried and revisited.
I'm guessing that LWP isn't following the redirect:
push #{ $ua->requests_redirectable }, 'POST';
Any reason why you're not using WWW::Mechanize?
I've used LWP to log in to plenty of web sites and do stuff with the content, so there should be no problem doing what you want. Your code looks good so far but two things I'd suggest:
As mentioned, you may need to make the requests redirectable
You may also need to enable cookies:
$ua->cookie_jar( {} );
Hope this helps