package JSONRPC::Transport::HTTP; use strict; use JSONRPC; use base qw(JSONRPC); use vars qw($VERSION); use HTTP::Request; use HTTP::Response; $VERSION = 1.02; # # # package JSONRPC::Transport::HTTP::Client; use base qw(JSONRPC::Client); sub send { require LWP::UserAgent; my ($self, $content) = @_; my ($url, $proxy_url) = @{$self->{_proxy}}; my $ua = LWP::UserAgent->new; $ua->proxy(['http','https'], $proxy_url) if($proxy_url); $ua->post($url, Content_Type => 'text/plain', Content => $content); } # # # package JSONRPC::Transport::HTTP::Server; use base qw(JSONRPC); use constant DEFAULT_CHARSET => 'UTF-8'; sub new { my $self = shift; my %opt = @_; unless (ref $self) { my $class = ref($self) || $self; $self = $class->SUPER::new(%opt); } $self->charset( $opt{charset} || DEFAULT_CHARSET ); return $self; } sub handle { my $self = shift; my $jp = $self->jsonParser; unless(ref $self){ $self = $self->new(@_) } my $req; if( $req = $self->request ){ $self->{json_data} = eval q| $jp->parse($req->content) | or return $self->send_response( $self->invalid_request() ); if( defined $self->request_id($self->{json_data}->{id}) ){ my $res = $self->handle_method($req) or return $self->invalid_request(); return $self->send_response( $self->response($res) ); } else{ $self->notification(); $self->send_response( $self->no_response() ); } } else{ $self->send_response( $self->invalid_request() ); } } sub charset { $_[0]->{_charset} = $_[1] if(@_ > 1); $_[0]->{_charset}; } sub response { my $self = shift; my $res = shift; my $charset = $self->charset; my $h = HTTP::Headers->new; $h->header('Content-Type' => "text/plain; charset=$charset"); HTTP::Response->new(200 => undef, $h, $res); } sub invalid_request { my $self = shift; my $charset = $self->charset; my $h = HTTP::Headers->new; $h->header('Content-Type' => "text/plain; charset=$charset"); HTTP::Response->new(500 => undef, $h); } sub no_response { my $self = shift; my $charset = $self->charset; my $h = HTTP::Headers->new; $h->header('Content-Type' => "text/plain; charset=$charset"); HTTP::Response->new(200 => undef, $h); } sub send_response { } # # # package JSONRPC::Transport::HTTP::CGI; use CGI; use base qw(JSONRPC::Transport::HTTP::Server); use constant DEFAULT_CHARSET => 'UTF-8'; use constant MAX_CONTENT_LENGTH => 1024 * 1024 * 5; # 5M sub new { shift->SUPER::new(@_); } sub handle { my $self = shift->new(); my %opt = @_; for my $name (qw/charset paramName query/){ $self->$name( $opt{$name} ) if(exists $opt{$name}); } $self->SUPER::handle(); } sub request { my $self = shift; my $q = $self->query || new CGI; my $len = $ENV{'CONTENT_LENGTH'} || 0; if(MAX_CONTENT_LENGTH < $len){ return; } my $req = HTTP::Request->new($q->request_method, $q->url); return if($req->method ne 'POST'); if(defined $self->paramName){ $req->content( $q->param($self->paramName) ); } else{ my @name = $q->param; $req->content( ((@name == 1) ? $q->param($name[0]) : $q->param('POSTDATA')) ); } return $self->{_request} = $req; } sub send_response { my ($self, $res) = @_; print "Status: " . $res->code . "\015\012" . $res->headers_as_string("\015\012") . "\015\012" . $res->content; } sub query { $_[0]->{_query} = $_[1] if(@_ > 1); $_[0]->{_query}; } sub paramName { $_[0]->{_paramName} = $_[1] if(@_ > 1); $_[0]->{_paramName}; } # # # package JSONRPC::Transport::HTTP::Daemon; use base qw(JSONRPC::Transport::HTTP::Server); sub new { my $self = shift; unless (ref $self) { my $class = ref($self) || $self; $self = $class->SUPER::new(@_); } my $pkg; if( grep { $_ =~ /^SSL_/ } @_ ){ $self->{_daemon_pkg} = $pkg = 'HTTP::Daemon::SSL'; } else{ $self->{_daemon_pkg} = $pkg = 'HTTP::Daemon'; } eval qq| require $pkg; |; if($@){ die $@ } $self->{_daemon} ||= $pkg->new(@_) or die; return $self; } sub handle { my $self = shift; my %opt = @_; my $d = $self->{_daemon} ||= $self->{_daemon_pkg}->new(@_) or die; $self->charset($opt{charset}) if($opt{charset}); while (my $c = $d->accept) { $self->{con} = $c; while (my $r = $c->get_request) { if ($r->method eq 'POST') { $self->request($r); $self->SUPER::handle(); } else { $self->invalid_request(); } last; } $c->close; } } sub request { $_[0]->{_request} = $_[1] if(@_ > 1); $_[0]->{_request}; } sub send_response { my ($self, $res) = @_; $self->{con}->send_response($res); } # # # package JSONRPC::Transport::HTTP::Apache; use base qw(JSONRPC::Transport::HTTP::Server); use constant MAX_CONTENT_LENGTH => 1024 * 1024 * 5; # 5M sub new { my $self = shift; require Apache; require Apache::Constants; unless (ref $self) { my $class = ref($self) || $self; $self = $class->SUPER::new(@_); } return $self; } sub request { my $self = shift; my $r = shift || Apache->request; my $len = $r->header_in('Content-length'); $self->{apr} = $r; return if($r->method ne 'POST'); return if(MAX_CONTENT_LENGTH < $len); my $req = HTTP::Request->new($r->method, $r->uri); my ($buf, $content); while( $r->read($buf,$len) ){ $content .= $buf; } $req->content($content); return $self->{_request} = $req; } sub send_response { my ($self, $res) = @_; my $r = $self->{apr}; $r->send_http_header("text/plain"); $r->print($res->content); return ($res->code == 200) ? &Apache::Constants::OK : &Apache::Constants::SERVER_ERROR; } sub configure { my $self = shift; my $config = shift->dir_config; for my $method (keys %$config) { my @values = split(/\s*,\s*/, $config->{$method}); $self->$method(@values) if($self->can($method)); } $self; } 1; __END__ =head1 NAME JSONRPC::Transport::HTTP =head1 SYNOPSIS #-------------------------- # In your application class package MyApp; sub own_method { # called by clients my ($server, @params) = @_; # $server is JSONRPC object. ... # return a scalar value or a hashref or an arryaref. } #-------------------------- # In your main cgi script. use JSONRPC::Transport::HTTP; use MyApp; # a la XMLRPC::Lite JSONRPC::Transport::HTTP::CGI->dispatch_to('MyApp')->handle(); ################## # Daemon version # ################## use strict; use lib qw(. ./lib); use JSONRPC::Transport::HTTP; my $daemon = JSONRPC::Transport::HTTP::Daemon ->new(LocalPort => 8080) ->dispatch_to('MyApp/Test', 'MyApp/Test2'); $daemon->handle(); ################## # Apache version # ################## http.conf or .htaccess SetHandler perl-script PerlHandler Apache::JSONRPC PerlModule MyApp::Test PerlSetVar dispatch_to "MyApp::Test, MyApp/Test2/" #-------------------------- # Client #-------------------------- use JSONRPC::Transport::HTTP; my $uri = 'http://www.example.com/MyApp/Test/'; my $res = JSONRPC::Transport::HTTP ->proxy($uri) ->call('echo',['This is test.']) ->result; if($res->error){ print $res->error,"\n"; } else{ print $res->result,"\n"; } # or my $client = JSONRPC::Transport::HTTP->proxy($uri); print $client->echo('This is test.'); # the alias, _echo is same. =head1 TRANSITION PLAN In the next large update version, JSON and JSONRPC modules are split. JSONRPC* and Apache::JSONRPC are deleted from JSON dist. JSONRPC::Client, JSONRPC::Server and JSONRPC::Procedure in JSON::RPC dist. Modules in JSON::RPC dist supports JSONRPC protocol v1.1 and 1.0. =head1 DESCRIPTION This module is L subclass. Most ideas were borrowed from L. Currently C provides only CGI server function. =head1 CHARSET When the module returns response, its charset is UTF-8 by default. You can change it via passing a key/value pair into handle(). my %charset = (charset => 'EUC-JP'); JSONRPC::Transport::HTTP::CGI->dispatch_to('MyApp')->handle(%charset); =head1 QUERY OBJECT If you want to use any other query object instead of C for JSONRPC::Transport::HTTP::CGI, you can pass C option and C. my %opt = ( query => $session, # CGI::Session object paramName => 'json', ); JSONRPC::Transport::HTTP::CGI->dispatch_to('MyApp')->handle(%opt); =head1 CAUTION JSONRPC::Transport::HTTP::CGI requires CGI.pm which version is more than 2.9.2. (the core module in Perl 5.8.1.) Since verion 1.0, JSONRPC::Transport::HTTP requires L and L. For using JSONRPC::Transport::HTTP::Client, you need L. =head1 SEE ALSO L L L L =head1 AUTHOR Makamaka Hannyaharamitu, Emakamaka[at]cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright 2005 by Makamaka Hannyaharamitu This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut