Mojo – Simple REST Server

37
Mojo – TL;DR Simple REST Server Hendrik Van Belleghem [email protected] 2016 Perl Mongers Vlaanderen Meetup

Transcript of Mojo – Simple REST Server

Page 1: Mojo – Simple REST Server

Mojo – TL;DRSimple REST Server

Hendrik Van [email protected]

2016 Perl Mongers Vlaanderen Meetup

Page 2: Mojo – Simple REST Server

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

Page 3: Mojo – Simple REST Server

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>

Page 4: Mojo – Simple REST Server

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

Page 5: Mojo – Simple REST Server

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/

Page 6: Mojo – Simple REST Server

Mojo

Generate application skeleton:

# mojo generate app Net::Cisco::ACS::Mock

Output [mkdir] /home/hendrik/net_cisco_acsmock/script [write] /home/hendrik/net_cisco_acsmock/script/net_cisco_acsmock [chmod] /home/hendrik/net_cisco_acsmock/script/net_cisco_acsmock 744 [mkdir] /home/hendrik/net_cisco_acsmock/lib/Net/Cisco/ACS [write] /home/hendrik/net_cisco_acsmock/lib/Net/Cisco/ACS/Mock.pm [mkdir] /home/hendrik/net_cisco_acsmock/lib/Net/Cisco/ACS/Mock/Controller [write] /home/hendrik/net_cisco_acsmock/lib/Net/Cisco/ACS/Mock/Controller/Example.pm [mkdir] /home/hendrik/net_cisco_acsmock/t [write] /home/hendrik/net_cisco_acsmock/t/basic.t [mkdir] /home/hendrik/net_cisco_acsmock/public [write] /home/hendrik/net_cisco_acsmock/public/index.html [mkdir] /home/hendrik/net_cisco_acsmock/templates/layouts [write] /home/hendrik/net_cisco_acsmock/templates/layouts/default.html.ep [mkdir] /home/hendrik/net_cisco_acsmock/templates/example [write] /home/hendrik/net_cisco_acsmock/templates/example/welcome.html.ep

Page 7: Mojo – Simple REST Server

Create SQLite DB# sqlite3 acs.db

sqlite > create table users(id integer, description text, name text, identitygroupname text, changepassword text, enablepassword text, enabled integer, password text, passwordneverexpires integer, passwordtype text, dateexceeds text, dateexceedsenabled integer);sqlite > insert into users (id, description, name, identitygroupname, changepassword, enablepassword, enabled, password,

passwordneverexpires, passwordtype, dateexceeds, dateexceedsenabled)values(1, 'Description for #1', 'Foo', 'All Groups', 'Secret Change','Secret Enable',1,'Secret',1,'Internal','2020-01-01',1);sqlite > insert into users (id, description, name, identitygroupname, changepassword, enablepassword, enabled, password,

passwordneverexpires, passwordtype, dateexceeds, dateexceedsenabled)values(2, 'Description for #2', 'Bar', 'All Groups', 'Secret Change','Secret Enable',1,'Secret',1,'Internal','2020-01-01',1);

sqlite > .quit

Page 8: Mojo – Simple REST Server

Generate DBIx::Class SchemaGenerate 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!

Page 9: Mojo – Simple REST Server

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;

Page 10: Mojo – Simple REST Server

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 startupStore DBIx::Class connection in $self->db

Page 11: Mojo – Simple REST Server

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;

Page 12: Mojo – Simple REST Server

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: queryArgument ‘name’ retrievable through

param

Page 13: Mojo – Simple REST Server

lib/Net/Cisco/ACS/Mock/Schema.pm

package Net::Cisco::ACS::Mock::Schema;

# based on the DBIx::Class Schema base classuse base qw/DBIx::Class::Schema/;

# This will load any classes within__PACKAGE__->load_namespaces();

1;

Generated by dbicdump

Page 14: Mojo – Simple REST Server

lib/Net/Cisco/ACS/Mock/Schema/Result/User.pm

package Net::Cisco::ACS::Mock::Schema::Result::User;use base qw/DBIx::Class::Core/;

# Associated table in database__PACKAGE__->table('users');

# Column definition__PACKAGE__->add_columns( id => { data_type => 'integer', is_auto_increment => 1 }, description => { data_type => 'text’ }, identitygroupname => { data_type => 'text‘ }, name => { data_type => 'text‘ }, changepassword => { data_type => 'text‘ }, enablepassword => { data_type => 'text‘ }, enabled => { data_type => 'integer’ },

Page 15: Mojo – Simple REST Server

lib/Net/Cisco/ACS/Mock/Schema/Result/User.pm

package Net::Cisco::ACS::Mock::Schema::Result::User;use base qw/DBIx::Class::Core/;

# Associated table in database__PACKAGE__->table('users');

# Column definition__PACKAGE__->add_columns( id => { data_type => 'integer', is_auto_increment => 1 }, description => { data_type => 'text’ }, identitygroupname => { data_type => 'text‘ }, name => { data_type => 'text‘ }, changepassword => { data_type => 'text‘ }, enablepassword => { data_type => 'text‘ }, enabled => { data_type => 'integer’ },

Generated by dbicdumpResult package

Connects to DB table Users

Page 16: Mojo – Simple REST Server

lib/Net/Cisco/ACS/Mock/Schema/Result/User.pm

..CONTINUED

password => { data_type => 'text‘ }, passwordneverexpires => { data_type => 'integer’ }, passwordtype => { data_type => 'text‘ }, dateexceeds => { data_type => 'text’ }, dateexceedsenabled => { data_type => 'integer’ },);

# Tell DBIC that 'id' is the primary key __PACKAGE__->set_primary_key('id');

1;

Page 17: Mojo – Simple REST Server

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; }

Page 18: Mojo – Simple REST Server

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 /UserMethod query maps to #query earlier

Page 19: Mojo – Simple REST Server

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

Page 20: Mojo – Simple REST Server

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

Page 21: Mojo – Simple REST Server

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);}

Page 22: Mojo – Simple REST Server

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

Page 23: Mojo – Simple REST Server

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 usingtemplate file,

format prepares HTTP header,layout wraps around the template

status returns HTTP status 200 (OK)

Page 24: Mojo – Simple REST Server

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);}

Page 25: Mojo – Simple REST Server

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);

}

Page 26: Mojo – Simple REST Server

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;

Page 27: Mojo – Simple REST Server

templates/user/queryall.xml.ep

% foreach my $user (sort keys %{$users}) { <User><id><%= $users->{$user}->{id} %></id><description><%= $users->{$user}->{description} %></description><identityGroupName><%= $users->{$user}->{identitygroupname} %></identityGroupName><name><%= $users->{$user}->{name} %></name><changePassword><%= $users->{$user}->{changepassword} %></changePassword><enablePassword><%= $users->{$user}->{enablepassword} %></enablePassword><enabled><%= $users->{$user}->{enabled} %></enabled><password><%= $users->{$user}->{password} %></password><passwordNeverExpires><%= $users->{$user}->{passwordneverexpires}

%></passwordNeverExpires><passwordType><%= $users->{$user}->{passwordtype} %></passwordType><dateExceeds><%= $users->{$user}->{dateexceeds} %></dateExceeds><dateExceedsEnabled><%= $users->{$user}->{dateexceedsenabled} %></dateExceedsEnabled></User>% }

Page 28: Mojo – Simple REST Server

templates/user/queryall.xml.ep

% foreach my $user (sort keys %{$users}) { <User><id><%= $users->{$user}->{id} %></id><description><%= $users->{$user}->{description} %></description><identityGroupName><%= $users->{$user}->{identitygroupname} %></identityGroupName><name><%= $users->{$user}->{name} %></name><changePassword><%= $users->{$user}->{changepassword} %></changePassword><enablePassword><%= $users->{$user}->{enablepassword} %></enablePassword><enabled><%= $users->{$user}->{enabled} %></enabled><password><%= $users->{$user}->{password} %></password><passwordNeverExpires><%= $users->{$user}->{passwordneverexpires}

%></passwordNeverExpires><passwordType><%= $users->{$user}->{passwordtype} %></passwordType><dateExceeds><%= $users->{$user}->{dateexceeds} %></dateExceeds><dateExceedsEnabled><%= $users->{$user}->{dateexceedsenabled} %></dateExceedsEnabled></User>% }

Template can contained embedded Perl,in this case a for loop, generating elements

Page 29: Mojo – Simple REST Server

templates/layouts/userall.xml.ep

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:users xmlns:ns2="identity.rest.mgmt.acs.nm.cisco.com">

<%= content %></ns2:users>

Page 30: Mojo – Simple REST Server

templates/layouts/userall.xml.ep

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:users xmlns:ns2="identity.rest.mgmt.acs.nm.cisco.com">

<%= content %></ns2:users>

Layout wraps around template file.Content is placed in <%= content %>

Page 31: Mojo – Simple REST Server

templates/user/query.xml.ep

<id><%= $user->id %></id><description><%= $user->description %></description><identityGroupName><%= $user->identitygroupname %></identityGroupName><name><%= $user->name %></name><changePassword><%= $user->changepassword %></changePassword><enablePassword><%= $user->enablepassword %></enablePassword><enabled><%= $user->enabled %></enabled><password><%= $user->password %></password><passwordNeverExpires><%= $user->passwordneverexpires %></passwordNeverExpires><passwordType><%= $user->passwordtype %></passwordType><dateExceeds><%= $user->dateexceeds %></dateExceeds><dateExceedsEnabled><%= $user->dateexceedsenabled %></dateExceedsEnabled>

Page 32: Mojo – Simple REST Server

templates/layouts/user.xml.ep

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:user xmlns:ns2="identity.rest.mgmt.acs.nm.cisco.com">

<%= content %></ns2:user>

Page 33: Mojo – Simple REST Server

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

Page 34: Mojo – Simple REST Server

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

Page 35: Mojo – Simple REST Server

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;

Page 36: Mojo – Simple REST Server

OutputNet::Cisco::ACS:

$VAR1 = { 'Foo' => bless( { 'id' => '1', 'passwordType' => 'Internal', 'name' => 'Foo', 'enablePassword' => 'Secret Enable', 'passwordNeverExpires' => '1', 'password' => 'Secret', 'description' => 'Description for #1', 'changePassword' => 'Secret Change', 'identityGroupName' => 'All Groups', 'dateExceedsEnabled' => '1', 'enabled' => '0', 'dateExceeds' => '2020-01-01' }, 'Net::Cisco::ACS::User' ),

'Bar' => bless( { 'dateExceedsEnabled' => '1', 'enabled' => '0', 'dateExceeds' => '2020-01-01', 'description' => 'Description for #2', 'passwordNeverExpires' => '1', 'password' => 'Secret', 'identityGroupName' => 'All Groups', 'changePassword' => 'Secret Change', 'enablePassword' => 'Secret Enable', 'name' => 'Bar', 'passwordType' => 'Internal', 'id' => '2' }, 'Net::Cisco::ACS::User' ) };

Page 37: Mojo – Simple REST Server

Outputhttp://localhost:3000/Rest/Identity/User/name/Foo

<?xml version="1.0" encoding="UTF-8" standalone="true"?><ns2:user xmlns:ns2="identity.rest.mgmt.acs.nm.cisco.com"><id>1</id><description>Description for #1</description><identityGroupName>All Groups</identityGroupName><name>Foo</name><changePassword>Secret Change</changePassword><enablePassword>Secret Enable</enablePassword><enabled>1</enabled><password>Secret</password><passwordNeverExpires>1</passwordNeverExpires><passwordType>Internal</passwordType><dateExceeds>2020-01-01</dateExceeds><dateExceedsEnabled>1</dateExceedsEnabled></ns2:user>