web::machine - simpl{e,y} http

32
Web::Machine Simpl{e,y} HTTP?!

Upload: michael-francis

Post on 08-Aug-2015

82 views

Category:

Software


2 download

TRANSCRIPT

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

Web::MachineSimpl{e,y} HTTP?!

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

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

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

PLACKuse Plack::Builder;

use Bean::API;

builder {

mount ‘/’ => Bean::API->as_psgi_app

}

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

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

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

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

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

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;

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

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;

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

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;

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

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

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

Resource Class

➔ @ISA◆ Web::Machine::Resource

➔ use Moo{se} for fun and profit➔ Kinda like a controller.➔ has request, has response.

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

package Bean::API::Resources::User;

use Moo;

extends ‘Web::Machine::Resource’;

1;

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

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

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

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

}

...

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

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 }

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

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 }

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

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

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

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

]

}

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

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

];

}

...

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

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;

}

...

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

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;

}

...

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

Authentication➔ Authn/Authz

◆ Authentication (401) -> is_authorized(‘Authorization’)◆ Authorization (403) -> forbidden()

➔ Can be a role for all resources

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

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

}

}

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

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

}

}

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

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

}

}

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

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;

}

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

PUT

➔ content_types_accepted◆ Takes a list of

● Hashrefs◆ Key: Content-Type◆ Value: callback for data input◆ First item is default content-type

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

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

}

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

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

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

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

}

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

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

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

DELETE

➔ delete_resource◆ true if delete was/appears successful◆ false if delete failed

➔ delete_completed◆ delete appears successful but isn’t complete

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

DiscussionQuestions, Answers, Understanding