JSON::RPC(3pm)

πŸ’– JSON::RPC πŸ’–

okay so bestie, this is a JSON::RPC page β€” from the User Contributed Perl Documentation, no less β€” and it's literally the whole JSON RPC 2.0 server implementation situation. Buckle up, it's gonna be a fun ride πŸŽ€


✨ NAME ✨

JSON::RPC β€” a whole set of modules that do JSON RPC 2.0 Server Implementation. yes, the real deal πŸ’…


🌸 SYNOPSIS 🌸

# app.psgi
use strict;
use JSON::RPC::Dispatch;

my $dispatch = JSON::RPC::Dispatch->new(
    prefix => "MyApp::JSONRPC::Handler",
    router => Router::Simple->new( ... )
);

sub {
    my $env = shift;
    $dispatch->handle_psgi($env);
};

that's the whole vibe, honestly. πŸ¦‹


πŸ¦‹ DESCRIPTION πŸ¦‹

JSON::RPC is a set of modules implementing the JSON RPC 2.0 protocol. cute, right?

okay important warning time 🚨 if you are using old JSON::RPC code (up to 0.96), DO NOT EXPECT YOUR CODE TO WORK WITH THIS VERSION. THIS VERSION IS BACKWARDS INCOMPATIBLE 😭 like, fr fr, do not come for me when it breaks.

πŸ’œ BASIC USAGE πŸ’œ

The JSON::RPC::Dispatch object is the one responsible for marshalling the request. main character energy ✨

my $dispatch = JSON::RPC::Dispatch->new(
    router => ...,
);

The routing between the JSON RPC methods and their implementors? handled by Router::Simple. so like, if you want to map method "foo" to a MyApp::JSONRPC::Handler object instance's handle_foo method, you put something like this in your router instance:

use Router::Simple::Declare;
my $router = router {
    connect "foo" => {
        handler => "+MyApp::JSONRPC::Handler",
        action  => "handle_foo"
    };
};

my $dispatch = JSON::RPC::Dispatch->new(
    router => $router,
);

okay so that "+" prefix in the handler classname? it denotes that it's already a fully qualified classname πŸ‘‘ without the prefix, the value of prefix in the dispatch object gets used to qualify the classname. and if you specify prefix in your Dispatch instance, you can just omit the prefix part and save yourself some typing, slay:

use JSON::RPC::Dispatch;
use Router::Simple::Declare;

my $router = router {
    connect "foo" => {
        handler => "Foo",
        action  => "process",
    };
    connect "bar" => {
        handler => "Bar",
        action => "process"
    }
};
my $dispatch = JSON::RPC::Dispatch->new(
    prefix => "MyApp::JSONRPC::Handler",
    router => $router,
);

# The above will roughly translate to the following:
#
# for method "foo"
#    my $handler = MyApp::JSONRPC::Handler::Foo->new;
#    $handler->process( ... );
#
# for method "bar"
#    my $handler = MyApp::JSONRPC::Handler::Bar->new;
#    $handler->process( ... );

The implementors are called handlers. handlers are simple objects and they get instantiated automatically for you πŸ’« their return values are converted to JSON objects automatically. no cap.

You may also choose to pass objects in the handler argument to connect in your router. this saves you the cost of instantiating the handler object, plus you don't have to rely on us instantiating your handler object. win-win, honestly 🫢

use Router::Simple::Declare;
use MyApp::JSONRPC::Handler;

my $handler = MyApp::JSONRPC::Handler->new;
my $router = router {
    connect "foo" => {
        handler => $handler,
        action  => "handle_foo"
    };
};

πŸŽ€ HANDLERS πŸŽ€

Your handlers are objects responsible for returning some sort of reference structure that can be properly encoded via JSON/JSON::XS. the handler only needs to implement the methods that you specified in your router. that's it. that's the whole job πŸ’…

The handler methods receive the following parameters:

sub your_handler_method {
    my ($self, $params, $procedure, @extra_args) = @_;

    return $some_structure;
}

In most cases you'll only need the parameters. the exact format of $params is dependent on the caller β€” you'll be passed whatever JSON structure that caller used to call your handler. iykyk 🌈

$procedure is an instance of JSON::RPC::Procedure. use it if you need to figure out more about the procedure πŸ”

@extra_args is optional, and gets filled with whatever extra arguments you passed to handle_psgi(). for example:

# app.psgi
sub {
    $dispatch->handle_psgi($env, "arg1", "arg2", "arg3");
}

will cause your handlers to receive the following arguments:

sub your_handler_method {
    my ($self, $params, $procedure, $arg1, $arg2, $arg3) = @_;

}

This is convenient if you have application-specific data that needs to be passed to your handlers. obsessed. πŸ’–


🌈 EMBED IT IN YOUR WEBAPP 🌈

If you already have a web app (and whatever framework you might already have), you may choose to embed JSON::RPC in your webapp instead of directly calling it in your PSGI application. so flexible of it 🧚

For example, if you would like your webapp's "rpc" handler to marshall the JSON RPC request, you can do something like the following:

package MyApp;
use My::Favorite::WebApp;

sub rpc {
    my ($self, $context) = @_;

    my $dispatch =  ...; # grab it from somewhere
    $dispatch->handle_psgi( $context->env );
}

πŸ’€ ERRORS πŸ’€

When your handler dies, it is automatically included in the response hash β€” unless no response was requested (see "NOTIFICATIONS").

For example, something like below:

sub rpc {
    ...
    if ($bad_thing_happend) {
        die "Argh! I failed!";
    }
}

Would result in a response like:

{
    error => {
        code => -32603,
        message => "Argh! I failed! at ...",
    }
}

However β€” and this is the cute part β€” you can include custom data by die()'ing with a hash:

sub rpc {
    ...
    if ($bad_thing_happend) {
        die { message => "Argh! I failed!", data => time() };
    }
}

This would result in:

{
    error => {
        code => -32603,
        message => "Argh! I failed! at ...",
        data => 1339817722,
    }
}

look at that data => 1339817722 just sitting there. iconic. πŸ”₯


πŸ”” NOTIFICATIONS πŸ””

Notifications are defined as procedures without an id. notification handling does not produce a response. when all procedures are notifications, no content is returned (if the request is valid). πŸ™ˆ

To maintain some basic compatibility with relaxed client implementations, JSON::RPC::Dispatch includes responses when procedures do not have a "jsonrpc" field set to "2.0".

heads up β€” no error is returned in response to a notification when the handler dies or when the requested method is not available. yes, truly. that's the deal πŸ’€

For example, a request structure like this:

[
    {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
    {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
    {"jsonrpc": "2.0", "method": "keep_alive"},
    {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
]

Would result in a response like:

[
    {"jsonrpc": "2.0", "result": 7, "id": "1"},
    {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
]

see how the notification ones just… didn't come back? yeah. that's the whole point bestie ✨


⭐ BACKWARDS COMPATIBILITY ⭐

Eh, not compatible at all. 😭 JSON RPC 0.xx was fine, but it predates PSGI, and things are just... different before and after PSGI. it's giving before-and-after glow-up but in a breaking way.

Code at version 0.96 has been moved to the JSON::RPC::Legacy namespace, so change your application to use JSON::RPC::Legacy if you were using the old version. 🫑


πŸ‘‘ AUTHORS πŸ‘‘

  • Daisuke Maki
  • Shinichiro Aska
  • Yoshimitsu Torii

πŸŽ€ AUTHOR EMERITUS πŸŽ€

Makamaka Hannyaharamitu, <makamaka[at]cpan.org> β€” JSON::RPC modules up to 0.96. respect the elders πŸ’œ


The JSON::RPC module is

Copyright (C) 2011 by Daisuke Maki

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.0 or, at your option, any later version of Perl 5 you may have available.

See JSON::RPC::Legacy for copyrights and license for previous versions.


and that's the whole page, bestie!! πŸ¦‹πŸŒΈ this was JSON::RPC from the User Contributed Perl Documentation, version perl v5.42.0, dated 2015-08-27 β€” mwah, go build something iconic with it πŸ’…βœ¨πŸŽ€

πŸ“¦ where this page comes from