AnyEvent-HTTPD-ExtDirect

 view release on metacpan or  search on metacpan

lib/AnyEvent/HTTPD/ExtDirect.pm  view on Meta::CPAN

### PACKAGE GLOBAL VARIABLE ###
#
# Version of the module
#

our $VERSION = '3.20';

### PUBLIC CLASS METHOD (CONSTRUCTOR) ###
#
# Instantiate a new AnyEvent::HTTPD::ExtDirect object
#

sub new {
    my $class = shift;
    
    my %arg = @_ == 1 && 'HASH' eq ref $_[0] ? %{ $_[0] }
            :                                  @_
            ;
    
    my $api    = delete $arg{api}    || RPC::ExtDirect->get_api();
    my $config = delete $arg{config} || $api->config;
    
    $config->add_accessors(
        overwrite => 1,
        complex   => [{
            accessor => 'router_class_anyevent',
            fallback => 'router_class',
        }, {
            accessor => 'eventprovider_class_anyevent',
            fallback => 'eventprovider_class',
        }],
    );
    
    for my $var ( qw/ router_class eventprovider_class / ) {
        my $method = "${var}_anyevent";
        
        $config->$method( delete $arg{$var} ) if exists $arg{$var};
    }
    
    # AnyEvent::HTTPD wants only IP addresses
    $arg{host} = '127.0.0.1' if $arg{host} =~ /localhost/io;

    my $self = $class->SUPER::new(%arg);
    
    $self->config($config);
    $self->api($api);

    return $self;
}

### PUBLIC INSTANCE METHOD ###
#
# Run the server
#

sub run {
    my ($self) = @_;
    
    my $config = $self->config;

    $self->set_callbacks(
        api_path    => $config->api_path,
        router_path => $config->router_path,
        poll_path   => $config->poll_path,
    );

    $self->SUPER::run();
}

### PUBLIC INSTANCE METHOD ###
#
# Handle Ext.Direct API calls
#

sub handle_api {
    my ($self, $req) = @_;

    # Get the API JavaScript chunk
    my $js = eval {
        $self->api->get_remoting_api( config => $self->config )
    };

    # If JS API call failed, return error
    return $self->_error_response if $@;

    # Content length should be in octets
    my $content_length = do { use bytes; my $len = length $js };

    $req->respond([
        200,
        'OK',
        {
            'Content-Type'   => 'application/javascript',
            'Content-Length' => $content_length,
        },
        $js
    ]);

    $self->stop_request;
}

### PUBLIC INSTANCE METHOD ###
#
# Handle Ext.Direct method requests
#

sub handle_router {
    my ($self, $req) = @_;
    
    if ( $req->method ne 'POST' ) {
        $req->respond( $self->_error_response );
        $self->stop_request;
        
        return;
    }
    
    my $config = $self->config;
    my $api    = $self->api;

    # Naked AnyEvent::HTTPD::Request object doesn't provide several
    # utility methods we'll need down below, and we will need it as

lib/AnyEvent/HTTPD/ExtDirect.pm  view on Meta::CPAN

        +{ @{ $result->[1] } },
        $result->[2]->[0],
    ]);
    
    $self->stop_request;
}

### PUBLIC INSTANCE METHOD ###
#
# Polls Event handlers for events, returning serialized stream
#

sub handle_events {
    my ($self, $req) = @_;
    
    # Only GET and POST methods are supported for polling
    my $method = $req->method;
    
    if ( $method ne 'GET' && $method ne 'POST' ) {
        $req->respond( $self->_error_response );
        $self->stop_request;
        
        return;
    }
    
    my $config = $self->config;
    my $api    = $self->api;
    
    my $env = bless $req, __PACKAGE__.'::Env';
    
    my $provider_class = $config->eventprovider_class_anyevent;
    
    eval "require $provider_class";
    
    my $provider = $provider_class->new(
        config => $config,
        api    => $api,
    );
    
    # Polling for Events is safe from exceptions
    my $http_body = $provider->poll($env);
    
    my $content_length
        = do { no warnings 'void'; use bytes; length $http_body };
    
    $req->respond([
        200,
        'OK',
        {
            'Content-Type'   => 'application/json; charset=utf-8',
            'Content-Length' => $content_length,
        },
        $http_body,
    ]);
    
    $self->stop_request;
}

### PUBLIC INSTANCE METHOD ###
#
# Register the callbacks for Ext.Direct handlers.
# This effectively "primes" the server but does not make it
# enter a blocking wait.
#

sub set_callbacks {
    my ($self, %arg) = @_;

    my $config = $self->config;
    
    my $api_path    = $arg{api_path}    || $config->api_path;
    my $router_path = $arg{router_path} || $config->router_path;
    my $poll_path   = $arg{poll_path}   || $config->poll_path;
     
    $self->reg_cb(
        $api_path    => $self->can('handle_api'),
        $router_path => $self->can('handle_router'),
        $poll_path   => $self->can('handle_events'),
    );
}

### PUBLIC INSTANCE METHODS ###
#
# Read-write accessors.
#

RPC::ExtDirect::Util::Accessor::mk_accessors(
    simple => [qw/ api config /],
);

############## PRIVATE METHODS BELOW ##############

### PRIVATE INSTANCE METHOD ###
#
# Deals with intricacies of POST-fu and returns something suitable to
# feed to Router (string or hashref, really). Or undef if something
# goes too wrong to recover.
#
# This code was mostly copied from the Plack gateway and adapted
# for AnyEvent::HTTPD.
#

sub _extract_post_data {
    my ($self, $req) = @_;

    # The smartest way to tell if a form was submitted that *I* know of
    # is to look for 'extAction' and 'extMethod' keywords in form params.
    my $is_form = $req->param('extAction') && $req->param('extMethod');

    # If form is not involved, it's easy: just return raw POST (or undef)
    if ( !$is_form ) {
        my $postdata = $req->content;
        return $postdata ne '' ? $postdata
               :                 undef
               ;
    };

    # If any files are attached, extUpload field will be set to 'true'
    my $has_uploads = $req->param('extUpload') eq 'true';

    # Outgoing hash
    my %keyword;

    # Pluck all parameters from the Request object
    for my $param ( $req->params ) {
        my @values = $req->param($param);



( run in 2.289 seconds using v1.01-cache-2.11-cpan-5735350b133 )