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. cmd is 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, spawn dies rather than clobber it.

  • commands -- hash reference of name => \&handler pairs to register. See "COMMAND HANDLERS".

  • socket_mode -- if set (e.g. 0600), chmod the socket to these permissions after binding. Unix socket permissions govern who may connect, so setting this is recommended.

  • alias -- POE session alias. Defaults to json_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

die with 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 $ctx somewhere, 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. status defaults to ok and the request id is 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