Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Web::Machine
Simpl{e,y} HTTP?!
Web::Machine?
➔ Simple HTTP State Machine…
➔ Represented as Resource classes
◆ See API Design talk
➔ Provides hooks for HT...
PLACK
use Plack::Builder;
use Bean::API;
builder {
mount ‘/’ => Bean::API->as_psgi_app
}
sub as_psgi_app {
my ($self) = @_;
$self = ref $self ? $self : $self->new;
$self->router->add_route('/users/:id' =>
valida...
sub as_psgi_app {
my ($self) = @_;
$self = ref $self ? $self : $self->new;
$self->router->add_route('/users/:id' =>
valida...
my $app = Web::Machine->new(
# Resource is a Web::Machine::Resource subclass
resource => Bean::API::Resources::User',
# re...
my $app = Web::Machine->new(
# Resource is a Web::Machine::Resource subclass
resource => Bean::API::Resources::User',
# re...
my $app = Web::Machine->new(
# Resource is a Web::Machine::Resource subclass
resource => Bean::API::Resources::User',
# re...
sub as_psgi_app {
my ($self) = @_;
$self = ref $self ? $self : $self->new;
$self->router->add_route('/users/:id' =>
valida...
Resource Class
➔ @ISA
◆ Web::Machine::Resource
➔ use Moo{se} for fun and profit
➔ Kinda like a controller.
➔ has request, ...
package Bean::API::Resources::User;
use Moo;
extends ‘Web::Machine::Resource’;
1;
package Bean::API::Resources::User;
use Moo;
extends ‘Web::Machine::Resource’;
use Types::Standard qw/Int/
has user_id => ...
package Bean::API::Resources::User;
use Moo;
extends ‘Web::Machine::Resource’;
use Types::Standard qw/Str/;
has user_id =>...
sub resource_exists { 1 } sub service_available { 1 }
sub is_authorized { 1 } sub forbidden { 0 }
sub allow_missing_post {...
sub resource_exists { 1 } sub service_available { 1 }
sub is_authorized { 1 } sub forbidden { 0 }
sub allow_missing_post {...
GET
➔ content_types_provided
◆ Takes a list of
● Hashrefs
◆ Key: Content-Type
◆ Value: callback for data output
◆ First it...
package Bean::API::Resources::User;
…
# TRUE ? continue : return 404 RESOURCE NOT FOUND
sub resource_exists {
return !! $s...
…
use CPanel::JSON::XS;
use Template::Toolkit;
sub user_to_json {
# May not handle inflated data! But fine for simple data...
package Bean::API::Resources::User;
…
sub resource_exists {
return !! $self->user;
}
sub content_types_provided {
return [...
package Bean::API::Resources::User;
…
sub resource_exists {
return !! $self->user;
}
sub content_types_provided {
return [...
Authentication
➔ Authn/Authz
◆ Authentication (401) -> is_authorized(‘Authorization’)
◆ Authorization (403) -> forbidden()...
sub _basic_authn {
my ($self, $authn_string) = @_;
my ($username, $password) =
split(‘:’, decode_base64($authn_string));
m...
sub _basic_authn {
my ($self, $authn_string) = @_;
my ($username, $password) =
split(‘:’, decode_base64($authn_string));
m...
sub _basic_authn {
my ($self, $authn_string) = @_;
my ($username, $password) =
split(‘:’, decode_base64($authn_string));
m...
package Bean::API::Resources::User;
# HTTP is bad at the distinction between Authz and Authn
sub forbidden {
my ($self) = ...
PUT
➔ content_types_accepted
◆ Takes a list of
● Hashrefs
◆ Key: Content-Type
◆ Value: callback for data input
◆ First ite...
package Bean::API::Resources::User;
sub content_types_accepted {
return [
{‘application/json’ => ‘user_from_json’},
];
}
#...
POST
➔ post_is_create
◆ create_path / create_path_after_handler
◆ Then the post is treated like a PUT
➔ process_post
◆ han...
package Bean::API::Resources::User;
sub process_post {
my ($self) = @_;
# Do whatever you want
# No. really. anything!
# R...
package Bean::API::Resources::User;
sub content_types_accepted {
return [
{‘application/json’ => ‘user_from_json’},
{‘appl...
DELETE
➔ delete_resource
◆ true if delete was/appears successful
◆ false if delete failed
➔ delete_completed
◆ delete appe...
Discussion
Questions, Answers, Understanding
Upcoming SlideShare
Loading in …5
×

Web::Machine - Simpl{e,y} HTTP

554 views

Published on

A brief overview of the complicate Web::Machine Perl Module.

Published in: Software
  • Be the first to comment

Web::Machine - Simpl{e,y} HTTP

  1. 1. Web::Machine Simpl{e,y} HTTP?!
  2. 2. Web::Machine? ➔ Simple HTTP State Machine… ➔ Represented as Resource classes ◆ See API Design talk ➔ Provides hooks for HTTP states ➔ Replacement for MVC style Web Frameworks ➔ Plack compatible
  3. 3. PLACK use Plack::Builder; use Bean::API; builder { mount ‘/’ => Bean::API->as_psgi_app }
  4. 4. sub as_psgi_app { my ($self) = @_; $self = ref $self ? $self : $self->new; $self->router->add_route('/users/:id' => validations => { id => Int, }, target => sub { my ($request, $id) = @_; my $app = Web::Machine->new( resource => Bean::API::Resources::User', resource_args => [ user_id => $id, ] )->to_app; return $app->($request->env); } ); return Plack::App::Path::Router->new(router => $self->router)->to_app; }
  5. 5. sub as_psgi_app { my ($self) = @_; $self = ref $self ? $self : $self->new; $self->router->add_route('/users/:id' => validations => { id => Int, }, target => sub { my ($request, $id) = @_; my $app = Web::Machine->new( resource => Bean::API::Resources::User', resource_args => [ user_id => $id, ] )->to_app; return $app->($request->env); } ); return Plack::App::Path::Router->new(router => $self->router)->to_app; }
  6. 6. my $app = Web::Machine->new( # Resource is a Web::Machine::Resource subclass resource => Bean::API::Resources::User', # resource_args are passed to the Resource subclass on initialization resource_args => [ user_id => $id, ] )->to_app;
  7. 7. my $app = Web::Machine->new( # Resource is a Web::Machine::Resource subclass resource => Bean::API::Resources::User', # resource_args are passed to the Resource subclass on initialization resource_args => [ user_id => $id, ] )->to_app;
  8. 8. my $app = Web::Machine->new( # Resource is a Web::Machine::Resource subclass resource => Bean::API::Resources::User', # resource_args are passed to the Resource subclass on initialization resource_args => [ user_id => $id, ] )->to_app;
  9. 9. sub as_psgi_app { my ($self) = @_; $self = ref $self ? $self : $self->new; $self->router->add_route('/users/:id' => validations => { id => Int, }, target => sub { my ($request, $id) = @_; my $app = Web::Machine->new( resource => Bean::API::Resources::User', resource_args => [ user_id => $id, ] )->to_app; return $app->($request->env); } ); return Plack::App::Path::Router->new(router => $self->router)->to_app; }
  10. 10. Resource Class ➔ @ISA ◆ Web::Machine::Resource ➔ use Moo{se} for fun and profit ➔ Kinda like a controller. ➔ has request, has response.
  11. 11. package Bean::API::Resources::User; use Moo; extends ‘Web::Machine::Resource’; 1;
  12. 12. package Bean::API::Resources::User; use Moo; extends ‘Web::Machine::Resource’; use Types::Standard qw/Int/ has user_id => ( is => ‘ro’, isa => Int, required => 1 ); 1; sub as_psgi_app { my ($self) = @_; $self = ref $self ? $self : $self->new; $self->router->add_route('/users/:id' => validations => { id => Int, }, target => sub { my ($request, $id) = @_; my $app = Web::Machine->new( resource => Bean::API::Resources::User', resource_args => [ user_id => $id, ] )->to_app; return $app->($request->env); } ); return Plack::App::Path::Router->new(router => $self->router)->to_app; }
  13. 13. package Bean::API::Resources::User; use Moo; extends ‘Web::Machine::Resource’; use Types::Standard qw/Str/; has user_id => ( isa => Str, is => ‘ro’, required => 1 ); has user => ( is => ‘lazy’, isa => Maybe[‘Bean::Schema::ResultUser’], builder => 1, ); ... sub _build_user { return $schema->resultset(‘User’)->find($self->user_id); } ...
  14. 14. sub resource_exists { 1 } sub service_available { 1 } sub is_authorized { 1 } sub forbidden { 0 } sub allow_missing_post { 0 } sub malformed_request { 0 } sub uri_too_long { 0 } sub known_content_type { 1 } sub valid_content_headers { 1 } sub valid_entity_length { 1 } sub options { +{} } sub allowed_methods { [ qw[GET HEAD] ] } sub known_methods { [qw[ GET HEAD POST PUT DELETE TRACE CONNECT OPTIONS ]]} sub delete_resource { 0 } sub delete_completed { 1 } sub post_is_create { 0 } sub create_path { undef } sub base_uri { undef } sub process_post { 0 } sub content_types_provided { [] } sub content_types_accepted { [] } sub charsets_provided { [] } sub default_charset {} sub languages_provided { [] } sub encodings_provided { { 'identity' => sub { $_[1] } } } sub variances { [] } sub is_conflict { 0 } sub multiple_choices { 0 } sub previously_existed { 0 } sub moved_permanently { 0 } sub moved_temporarily { 0 } sub last_modified { undef } sub expires { undef } sub generate_etag { undef } sub finish_request {} sub create_path_after_handler { 0 }
  15. 15. sub resource_exists { 1 } sub service_available { 1 } sub is_authorized { 1 } sub forbidden { 0 } sub allow_missing_post { 0 } sub malformed_request { 0 } sub uri_too_long { 0 } sub known_content_type { 1 } sub valid_content_headers { 1 } sub valid_entity_length { 1 } sub options { +{} } sub allowed_methods { [ qw[ GET HEAD ] ] } sub known_methods { [qw[ GET HEAD POST PUT DELETE TRACE CONNECT OPTIONS ]] } sub delete_resource { 0 } sub delete_completed { 1 } sub post_is_create { 0 } sub create_path { undef } sub base_uri { undef } sub process_post { 0 } sub content_types_provided { [] } sub content_types_accepted { [] } sub charsets_provided { [] } sub default_charset {} sub languages_provided { [] } sub encodings_provided { { 'identity' => sub { $_[1] } } } sub variances { [] } sub is_conflict { 0 } sub multiple_choices { 0 } sub previously_existed { 0 } sub moved_permanently { 0 } sub moved_temporarily { 0 } sub last_modified { undef } sub expires { undef } sub generate_etag { undef } sub finish_request {} sub create_path_after_handler { 0 }
  16. 16. GET ➔ content_types_provided ◆ Takes a list of ● Hashrefs ◆ Key: Content-Type ◆ Value: callback for data output ◆ First item is default content-type ➔ resource_exists ◆ returns 404 if this returns false ➔ Auto encoding if charset
  17. 17. package Bean::API::Resources::User; … # TRUE ? continue : return 404 RESOURCE NOT FOUND sub resource_exists { return !! $self->user; } sub content_types_provided { return [ {‘application/json’ => ‘user_to_json’}, {‘text/html’ => ‘user_to_html’}, {‘application/x-tar => ‘user_to_tar’} ] }
  18. 18. … use CPanel::JSON::XS; use Template::Toolkit; sub user_to_json { # May not handle inflated data! But fine for simple data return encode_json({$self->user->get_columns}); } sub user_to_html { return $mason->run(‘/user’)->output; } ... package Bean::API::Resources::User; … sub resource_exists { return !! $self->user; } # HashRefs of content type and handler name # FOR GET REQUESTS sub content_types_provided { return [ {‘application/json’ => ‘user_to_json’}, {‘text/html’ => ‘user_to_html’}, {‘application/x-tar => ‘user_to_tar’} ]; } ...
  19. 19. package Bean::API::Resources::User; … sub resource_exists { return !! $self->user; } sub content_types_provided { return [ {‘application/json’ => ‘user_to_json’}, {‘text/html’ => ‘user_to_html’}, {‘application/x-tar => ‘user_to_tar’} ]; } ... … use CPanel::JSON::XS; use Template::Toolkit; sub user_to_json { # May not handle inflated data! But fine for simple data return encode_json({$self->user->get_columns}); } sub user_to_html { return $mason->run(‘/user’)->output; } ...
  20. 20. package Bean::API::Resources::User; … sub resource_exists { return !! $self->user; } sub content_types_provided { return [ {‘application/json’ => ‘user_to_json’}, {‘text/html’ => ‘user_to_html’}, {‘application/x-tar => ‘user_to_tar’} ]; } ... … use CPanel::JSON::XS; use Mason; sub user_to_json { # May not handle inflated data! But fine for simple data return encode_json({$self->user->get_columns}); } sub user_to_html { return $mason->run(‘/user’)->output; } ...
  21. 21. Authentication ➔ Authn/Authz ◆ Authentication (401) -> is_authorized(‘Authorization’) ◆ Authorization (403) -> forbidden() ➔ Can be a role for all resources
  22. 22. sub _basic_authn { my ($self, $authn_string) = @_; my ($username, $password) = split(‘:’, decode_base64($authn_string)); my $is_authenticated = $self->schema->resultset(‘User’) ->find({username => $username}) ->is_authenticated($password); if ($is_authenticated){ return 1; } else { return create_header(WWWAuthenticate => [ ‘Basic’ => (realm => ”BB-LDAP”’) ]); } } package Bean::API::Auth; use Moo::Role; use Web::Machine::Utils qw/create_header/; # HTTP is bad at the distinction between Authz and Authn sub is_authorized { my ($self, $authn_header) = @_; my ($authn_type, $authn_string) = split(‘ ‘, $authn_header); if ($authn_type eq ‘Basic’){ return $self->_basic_authn($authn_string); } }
  23. 23. sub _basic_authn { my ($self, $authn_string) = @_; my ($username, $password) = split(‘:’, decode_base64($authn_string)); my $is_authenticated = $self ->schema ->resultset(‘User’) ->find({username => $username}) ->is_authenticated($password); if ($is_authenticated){ return 1; } else { return ‘Basic realm=”BB-LDAP”’ } } package Bean::API::Auth; use Moo::Role; # HTTP is bad at the distinction between Authz and Authn sub is_authorized { my ($self, $authn_header) = @_; my ($authn_type, $authn_string) = split(‘ ‘, $authn_header); if ($authn_type eq ‘Basic’){ return $self->_basic_authn($authn_string); } }
  24. 24. sub _basic_authn { my ($self, $authn_string) = @_; my ($username, $password) = split(‘:’, decode_base64($authn_string)); my $is_authenticated = $self ->schema ->resultset(‘User’) ->find({username => $username}) ->is_authenticated($password); if ($is_authenticated){ return 1; } else { return ‘Basic realm=”BB-LDAP”’ } } package Bean::API::Auth; use Moo::Role; # HTTP is bad at the distinction between Authz and Authn sub is_authorized { my ($self, $authn_header) = @_; my ($authn_type, $authn_string) = split(‘ ‘, $authn_header); if ($authn_type eq ‘Basic’){ return $self->_basic_authn($authn_string); } }
  25. 25. package Bean::API::Resources::User; # HTTP is bad at the distinction between Authz and Authn sub forbidden { my ($self) = @_; my $is_authorized = 0; if ($self->request->method eq ‘GET’){ $is_authorized = $self->active_user->can_retrieve($self->user); } return $is_authorized; }
  26. 26. PUT ➔ content_types_accepted ◆ Takes a list of ● Hashrefs ◆ Key: Content-Type ◆ Value: callback for data input ◆ First item is default content-type
  27. 27. package Bean::API::Resources::User; sub content_types_accepted { return [ {‘application/json’ => ‘user_from_json’}, ]; } # PUT user with 204 response sub user_from_json { my ($self) = @_; $self->user->delete; $self->user->create(decode_json($self->request->body)); }
  28. 28. POST ➔ post_is_create ◆ create_path / create_path_after_handler ◆ Then the post is treated like a PUT ➔ process_post ◆ handles all other post request ◆ No Support for Content-type based handlers
  29. 29. package Bean::API::Resources::User; sub process_post { my ($self) = @_; # Do whatever you want # No. really. anything! # Return a status code # If you set the response->body then it will be encoded with the correct charset if set }
  30. 30. package Bean::API::Resources::User; sub content_types_accepted { return [ {‘application/json’ => ‘user_from_json’}, {‘application/x-webform-url-encoded’ => ‘user_from_webform’}, ]; } sub user_from_json { # create/update user (POST/PUT) } sub post_is_create {1} sub create_path_after_handler {return ‘/user/’.$self->user->id)}
  31. 31. DELETE ➔ delete_resource ◆ true if delete was/appears successful ◆ false if delete failed ➔ delete_completed ◆ delete appears successful but isn’t complete
  32. 32. Discussion Questions, Answers, Understanding

×