Why Teams call analytics are critical to your entire business
Mojo – Simple REST Server
1. Mojo – TL;DR
Simple REST Server
Hendrik Van Belleghem
Hendrik.vanbelleghem@gmail.com
2016 Perl Mongers Vlaanderen Meetup
2. Intro
• REST is typically HTTP based
• Not standardized
• Popularly used for APIs
• Outputs XML, HTML, JSON
• Verbs include GET, POST, DELETE & PUT
•
• https://en.wikipedia.org/wiki/Representational_state_transfer
3. Intro - REST
REST typically uses standard HTTP calls:
Specific entry: GET /User/name/<USER>
Specific entry: GET /User/id/<ID>
All Entries: GET /User
Update entry: PUT /User
Create entry: POST /User
Delete entry: DELETE /User/name/<USER>
Delete entry: DELETE /User/id/<ID>
4. Demo
• Cisco Secure ACS Server is used for Identity Management
• API access to user accounts, devices..
• Mojolicious does REST just fine
• Non-plugin approach
• Proof of Concept / Users only
5. Mojo
• Next generation Web: Websockets, non-blocking,
HTTP/1.1
• Web in a box: Development server & production server
included
• Light-weight: No dependencies
• Flexible routing
• … just like Catalyst
• DBIX::Class plugs in easily
• … just like Catalyst
• Done by the original Catalyst people
See http://www.mojolicious.org/
8. Generate DBIx::Class Schema
Generate DBIX::Class classes automatically based on SQLite database (or any other
database)
# dbicdump -o dump_directory=./lib
-o components='["InflateColumn::DateTime"]'
-o debug=1
Net::Cisco::ACS::Mock::Schema dbi:SQLite:acs.db
Run this as much as needed.. but it will overwrite the files!
Suggestion: Make sure to prepare your foreign keys properly!
9. lib/Net/Cisco/ACS/Mock.pm
package Net::Cisco::ACS::Mock;
use Mojo::Base 'Mojolicious';
use Net::Cisco::ACS::Mock::Schema;
sub startup {
my $self = shift;
my $schema = Net::Cisco::ACS::Mock::Schema->connect('dbi:SQLite:acs.db');
$self->helper(db => sub { return $schema; });
my $r = $self->routes;
$r->get("/Rest/Identity/User/name/:name")->to('User#query');
$r->get('/Rest/Identity/User/id/:id')->to('User#query');
$r->get('/Rest/Identity/User')->to('User#query');
$r->put('/Rest/Identity/User')->to('User#update');
$r->post('/Rest/Identity/User')->to('User#create');
$r->delete('/Rest/Identity/User/name/:name')->to('User#delete');
$r->delete('/Rest/Identity/User/id/:id')->to('User#delete');
}
1;
10. lib/Net/Cisco/ACS/Mock.pm
package Net::Cisco::ACS::Mock;
use Mojo::Base 'Mojolicious';
use Net::Cisco::ACS::Mock::Schema;
sub startup {
my $self = shift;
my $schema = Net::Cisco::ACS::Mock::Schema->connect('dbi:SQLite:acs.db');
$self->helper(db => sub { return $schema; });
my $r = $self->routes;
$r->get("/Rest/Identity/User/name/:name")->to('User#query');
$r->get('/Rest/Identity/User/id/:id')->to('User#query');
$r->get('/Rest/Identity/User')->to('User#query');
$r->put('/Rest/Identity/User')->to('User#update');
$r->post('/Rest/Identity/User')->to('User#create');
$r->delete('/Rest/Identity/User/name/:name')->to('User#delete');
$r->delete('/Rest/Identity/User/id/:id')->to('User#delete');
}
1;
Startup is called on Mojo startup
Store DBIx::Class connection in $self->db
11. lib/Net/Cisco/ACS/Mock.pm
package Net::Cisco::ACS::Mock;
use Mojo::Base 'Mojolicious';
use Net::Cisco::ACS::Mock::Schema;
sub startup {
my $self = shift;
my $schema = Net::Cisco::ACS::Mock::Schema->connect('dbi:SQLite:acs.db');
$self->helper(db => sub { return $schema; });
my $r = $self->routes;
$r->get("/Rest/Identity/User/name/:name")->to('User#query');
$r->get('/Rest/Identity/User/id/:id')->to('User#query');
$r->get('/Rest/Identity/User')->to('User#query');
$r->put('/Rest/Identity/User')->to('User#update');
$r->post('/Rest/Identity/User')->to('User#create');
$r->delete('/Rest/Identity/User/name/:name')->to('User#delete');
$r->delete('/Rest/Identity/User/id/:id')->to('User#delete');
}
1;
12. lib/Net/Cisco/ACS/Mock.pm
package Net::Cisco::ACS::Mock;
use Mojo::Base 'Mojolicious';
use Net::Cisco::ACS::Mock::Schema;
sub startup {
my $self = shift;
my $schema = Net::Cisco::ACS::Mock::Schema->connect('dbi:SQLite:acs.db');
$self->helper(db => sub { return $schema; });
my $r = $self->routes;
$r->get("/Rest/Identity/User/name/:name")->to('User#query');
$r->get('/Rest/Identity/User/id/:id')->to('User#query');
$r->get('/Rest/Identity/User')->to('User#query');
$r->put('/Rest/Identity/User')->to('User#update');
$r->post('/Rest/Identity/User')->to('User#create');
$r->delete('/Rest/Identity/User/name/:name')->to('User#delete');
$r->delete('/Rest/Identity/User/id/:id')->to('User#delete');
}
1;
Send GET request to
/Rest/Identity/User/name/*
to
Net::Cisco::ACS::Mock::Controller::User
Method: query
Argument ‘name’ retrievable through
param
17. lib/Net/Cisco/ACS/Mock/Controller/User.pm
package Net::Cisco::ACS::Mock::Controller::User;
use Mojo::Base 'Mojolicious::Controller';
use XML::Simple;
sub query {
my $self = shift;
my $name = $self->param("name");
my $id = $self->param("id");
my $rs = $self->db->resultset('User');
my $user;
if ($name)
{ my $query_rs = $rs->search({ name => $name });
$user = $query_rs->first;
}
if ($id)
{ my $query_rs = $rs->search({ id => $id });
$user = $query_rs->first;
}
18. lib/Net/Cisco/ACS/Mock/Controller/User.pm
package Net::Cisco::ACS::Mock::Controller::User;
use Mojo::Base 'Mojolicious::Controller';
use XML::Simple;
sub query {
my $self = shift;
my $name = $self->param("name");
my $id = $self->param("id");
my $rs = $self->db->resultset('User');
my $user;
if ($name)
{ my $query_rs = $rs->search({ name => $name });
$user = $query_rs->first;
}
if ($id)
{ my $query_rs = $rs->search({ id => $id });
$user = $query_rs->first;
}
View Controller for /User
Method query maps to #query earlier
19. lib/Net/Cisco/ACS/Mock/Controller/User.pm
package Net::Cisco::ACS::Mock::Controller::User;
use Mojo::Base 'Mojolicious::Controller';
use XML::Simple;
sub query {
my $self = shift;
my $name = $self->param("name");
my $id = $self->param("id");
my $rs = $self->db->resultset('User');
my $user;
if ($name)
{ my $query_rs = $rs->search({ name => $name });
$user = $query_rs->first;
}
if ($id)
{ my $query_rs = $rs->search({ id => $id });
$user = $query_rs->first;
}
Map to :id and :name in URI
20. lib/Net/Cisco/ACS/Mock/Controller/User.pm
package Net::Cisco::ACS::Mock::Controller::User;
use Mojo::Base 'Mojolicious::Controller';
use XML::Simple;
sub query {
my $self = shift;
my $name = $self->param("name");
my $id = $self->param("id");
my $rs = $self->db->resultset('User');
my $user;
if ($name)
{ my $query_rs = $rs->search({ name => $name });
$user = $query_rs->first;
}
if ($id)
{ my $query_rs = $rs->search({ id => $id });
$user = $query_rs->first;
}
Load
Net::Cisco::ACS::Mock::Schema::Result::User
Query table with criteria
21. lib/Net/Cisco/ACS/Mock/Controller/User.pm
if (!$id && !$name)
{ my $query_rs = $rs->search;
my %users = ();
while (my $account = $query_rs->next)
{ $users{$account->name} =
{ # Set the record
};
}
}
$self->stash("users" => %users);
$self->render(template => 'user/queryall', format => 'xml', layout => 'userall',status => 200);
return;
}
$self->stash("user" => $user);
$self->render(template => 'user/query', format => 'xml', layout => 'user', status => 200);
}
22. lib/Net/Cisco/ACS/Mock/Controller/User.pm
if (!$id && !$name)
{ my $query_rs = $rs->search;
my %users = ();
while (my $account = $query_rs->next)
{ $users{$account->name} =
{ # Set the record
};
}
}
$self->stash("users" => %users);
$self->render(template => 'user/queryall', format => 'xml', layout => 'userall');
return;
}
$self->stash("user" => $user);
$self->render(template => 'user/query', format => 'xml', layout => 'user');
}
Stash maps variables to template variables
23. lib/Net/Cisco/ACS/Mock/Controller/User.pm
if (!$id && !$name)
{ my $query_rs = $rs->search;
my %users = ();
while (my $account = $query_rs->next)
{ $users{$account->name} =
{ # Set the record
};
}
}
$self->stash("users" => %users);
$self->render(template => 'user/queryall', format => 'xml', layout => 'userall', status => 200);
return;
}
$self->stash("user" => $user);
$self->render(template => 'user/query', format => 'xml', layout => 'user', status => 200);
}
Render processes templates using
template file,
format prepares HTTP header,
layout wraps around the template
status returns HTTP status 200 (OK)
24. lib/Net/Cisco/ACS/Mock/Controller/User.pm
..CONTINUED
sub update {
my $self = shift;
my $rs = $self->db->resultset('User');
my $data = $self->req->body;
my $xmlsimple = XML::Simple->new();
my $xmlout = $xmlsimple->XMLin($data);
my $query_rs = $rs->search({ name => $xmlout->{"name"} });
my $account = $query_rs->first;
$account->update({ # Set Record
});
$self->render(template => 'user/userresult',
format => 'xml', layout => 'userresult', status => 200);
}
25. lib/Net/Cisco/ACS/Mock/Controller/User.pm
..CONTINUED
sub create {
my $self = shift;
my $data = $self->req->body;
my $xmlsimple = XML::Simple->new();
my $xmlout = $xmlsimple->XMLin($data);
my $rsmax = $self->db->resultset('User')->get_column('Id');
my $maxid = $rsmax->max;
$maxid++;
$self->db->resultset('User')->create({
# Set record
id => $maxid
});
$self->render(template => 'user/userresult', format => 'xml', layout => 'userresult', status => 200);
}
26. lib/Net/Cisco/ACS/Mock/Controller/User.pm
..CONTINUED
sub delete {
my $self = shift;
my $rs = $self->db->resultset('User');
my $name = $self->param("name");
my $id = $self->param("id");
my $user;
if ($name)
{ my $query_rs = $rs->search({ name => $name });
$user = $query_rs->first;
}
if ($id)
{ my $query_rs = $rs->search({ id => $id });
$user = $query_rs->first;
}
$user->delete if $user;
$self->render(template => 'user/userresult', format => 'xml', layout => 'userresult', status =>
200);
}
1;
33. Morbo
Development Server
Single request
Built using Mojolicious
Supports all features
# morbo script/net_cisco_acsmock
Server available at http://127.0.0.1:3000
34. Production Server
Multiple requests, scalable
Built using Mojolicious
Supports all features
# hypnotoad script/net_cisco_acsmock
[Sun Dec 11 15:19:16 2016] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
HypnoToad
35. The proof of the pudding..
#!/usr/bin/perl
use lib qw(Net/Cisco/ACS/lib);
use Net::Cisco::ACS;
use Data::Dumper;
my $acs = Net::Cisco::ACS->new(hostname => '127.0.0.1:8080', ssl=>0, username => 'acsadmin',
password => 'password');
print Dumper $acs->users;