NAME
POE::Component::Server::JSONUnix - pluggable JSON-over-Unix-socket server for POE
SYNOPSIS
use POE;
use POE::Component::Server::JSONUnix;
my $server = POE::Component::Server::JSONUnix->spawn(
socket_path => '/tmp/app.sock',
socket_mode => 0600,
commands => {
echo => sub {
my ($server, $request, $ctx) = @_;
return { echoed => $request->{args} };
},
},
);
# Add more commands at any time.
$server->register(
add => sub {
my ($server, $request, $ctx) = @_;
my $sum = 0;
$sum += $_ for @{ $request->{args}{numbers} // [] };
return { sum => $sum };
},
);
$poe_kernel->run;
DESCRIPTION
This module is a small, event-driven server that listens on a Unix domain socket and speaks a simple JSON request/response protocol. It is built on POE and is designed to be extended: the set of commands it understands is a plain dispatch table you can add to at construction time, at run time, or by subclassing.
It is suitable as a local control or RPC endpoint for a daemon -- the sort of thing you talk to from a command-line tool, a cron job, or another process on the same host.
PROTOCOL
The framing is newline-delimited JSON: each message is a single JSON object on its own line, terminated by \n. (Pretty-printed, multi-line JSON is not supported by the default filter; see "Changing the framing".)
A request looks like:
{"command":"add","args":{"numbers":[1,2,3]},"id":7}
command(required) -- the name of the command to run.cmdis accepted as an alias.args(optional) -- an arbitrary payload passed straight through to the handler.id(optional) -- an opaque value echoed back in the response so asynchronous clients can correlate replies with requests.
A successful response:
{"id":7,"status":"ok","result":{"sum":6}}
An error response:
{"id":7,"status":"error","error":"unknown command: subtract"}
Malformed JSON, a non-object request, a missing command, an unknown command, or a handler that dies all produce an error response rather than disturbing the server or other clients.
CONSTRUCTOR
spawn
my $server = POE::Component::Server::JSONUnix->spawn(%args);
Creates the server's POE session and returns the server object. Recognised arguments:
socket_path(required) -- filesystem path of the Unix domain socket to listen on. If a stale socket file is present it is removed; if another process is actively listening there,spawndies rather than clobber it.commands-- hash reference ofname => \&handlerpairs to register. See "COMMAND HANDLERS".socket_mode-- if set (e.g.0600),chmodthe socket to these permissions after binding. Unix socket permissions govern who may connect, so setting this is recommended.alias-- POE session alias. Defaults tojson_unix_server. Set this if you run more than one server in a single process.unlink_existing-- whether to remove a stale (not-in-use) socket file on startup. Defaults to true.on_error-- code reference called as$cb->($operation, $errnum, $errstr [, $wheel_id])on listen and connection I/O errors. Normal client disconnects are not reported.
METHODS
register
$server->register(name => \&handler, ...);
Add or replace commands. Returns the server object. Croaks if a handler is not a code reference.
command_names
my $names = $server->command_names; # array reference, sorted
The names of all currently registered commands. (Also available to clients as the built-in commands command.)
shutdown
$server->shutdown;
Stop accepting connections, close all clients, remove the socket file, and let the session end.
COMMAND HANDLERS
A handler is a code reference called as:
$handler->($server, $request, $ctx)
where $request is the decoded request hash and $ctx is a context object (see "THE CONTEXT OBJECT"). A handler answers in one of three ways:
- Synchronously
-
Return a value. It is wrapped and sent as
{status => 'ok', result => $value}. - By raising an error
-
diewith a string (sent as{status => 'error', error => $string}, with the trailing "at FILE line N" trimmed) or with a hash reference (merged into the error response). - Asynchronously
-
Return
undef, stash$ctxsomewhere, and call$ctx->respond_result(...)(or$ctx->error(...)) later -- for example after a timer fires or a backend request completes.
ADDING COMMANDS
Commands can be registered three ways. When names collide, later wins, in this order: built-ins, then cmd_* methods, then the commands argument and register.
1. At construction
POE::Component::Server::JSONUnix->spawn(
socket_path => $path,
commands => { hello => sub { ... } },
);
2. With register
$server->register(name => sub { ... }, name2 => sub { ... });
From inside another POE session you can instead post to the server's alias:
$poe_kernel->post($alias => register_command => $name => \&handler);
3. By subclassing
Any method named cmd_<name> anywhere in the class hierarchy is discovered automatically and exposed as a command. It is invoked as $server->cmd_name($request, $ctx).
package MyApp::Server;
use parent 'POE::Component::Server::JSONUnix';
sub cmd_whoami { my ($self, $req, $ctx) = @_; return { user => $ENV{USER} } }
sub cmd_uptime { my ($self, $req, $ctx) = @_; return { up => time() - $^T } }
MyApp::Server->spawn(socket_path => '/tmp/app.sock');
THE CONTEXT OBJECT
Each handler receives a context object (an instance of POE::Component::Server::JSONUnix::Context) as its third argument. It carries the request and provides the reply methods, which is what makes asynchronous handlers possible: keep the object alive past the handler's return and answer when ready.
$ctx->respond_result($data)-
Send
{status => 'ok', result => $data}. $ctx->error($message, %extra)-
Send
{status => 'error', error => $message, %extra}. $ctx->respond(\%envelope)-
Send a raw response envelope.
statusdefaults tookand the requestidis added automatically. Only the first reply on a context has any effect. $ctx->request,$ctx->id,$ctx->command-
Accessors for the decoded request, its
id, and the command name. $ctx->close-
Close this client's connection once any queued output has been flushed.
BUILT-IN COMMANDS
ping-
Returns
{pong => 1, time => <epoch>}. commands-
Returns
{commands => [ ...names... ]}-- handy for discovery.
Changing the framing
The default filter is POE::Filter::Line, giving one-object-per-line framing. If you would rather frame on complete JSON values (allowing pretty-printed input), replace the filter in the connection setup with POE::Filter::JSON.
DEPENDENCIES
POE and JSON::MaybeXS. Installing Cpanel::JSON::XS or JSON::XS lets JSON::MaybeXS pick a fast XS backend automatically.
SEE ALSO
POE, POE::Wheel::SocketFactory, POE::Wheel::ReadWrite, POE::Filter::Line, JSON::MaybeXS.
AUTHOR
Zane C. Bowers-Hadley, <vvelox at vvelox.net>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2026 by Zane C. Bowers-Hadley.
This is free software, licensed under:
The GNU Lesser General Public License, Version 2.1, February 1999