Remote REPL for debugging a Thrasher daemon.
[thrasher.git] / perl / lib / Net / REPL / Server.pm
blob4ade428ab6c084d82731977e7b19740454fd36a3
1 package Net::REPL::Server;
2 use strict;
3 use warnings;
5 =pod
7 =head1 NAME
9 Net::REPL::Server - Eval + Socket = interactive development
11 =head1 DESCRIPTION
13 Perl processes running Server objects accept and run code
14 interactively from remote clients. Perl code is received over a
15 socket, eval'd, and the Dumper'd result(s) are sent back.
17 This class can be used as-is or superclassed to plug into an event
18 loop or provide additional capabilities like Devel::REPL integration
19 or serializing results so that usuable Perl data structures can be
20 exchanged bidirectionally.
22 =head2 Methods
24 =cut
26 use base qw(Net::REPL::Base);
28 use Data::Dumper;
29 use IO::Socket::INET;
31 =head3 new(argument => value...)
33 Arguments:
35 =over
37 =item C<socket_args>
39 Hashref of arguments for L<IO::Socket::INET>.
41 =item C<debug>
43 Debug level (refer to L<Net::REPL::Base>).
45 =back
47 =cut
49 sub new {
50 my $proto = shift;
51 my $class = ref($proto) || $proto;
52 my $self = {
53 'socket_args' => {},
54 @_,
56 bless($self, $class);
58 $self->debug('Server starting:', Dumper($self->{'socket_args'}));
60 my $server = IO::Socket::INET->new(
61 'Proto' => 'tcp',
62 'Reuse' => 1,
63 'Listen' => 1,
64 %{$self->{'socket_args'}},
66 if (not $server) {
67 die("Couldn't start server: $!\n");
69 $self->{'server'} = $server;
70 $self->cb_listen();
72 $self->{'fh'} = undef;
74 return $self;
77 sub DESTROY {
78 my ($self) = @_;
80 if ($self->{'server'}) {
81 $self->{'server'}->close();
82 $self->{'server'} = undef;
84 $self->close_fh();
87 sub close_fh {
88 my ($self) = @_;
90 if ($self->{'fh'}) {
91 $self->{'fh'}->close();
92 $self->{'fh'} = undef;
93 $self->cb_disconnect();
97 =head3 interact()
99 Run one iteration of the REPL. Returns true if the socket remains open
100 for further iterations.
102 Blocks until a line of input can be read from the client socket and
103 the result flushed out to it.
105 =cut
107 sub interact {
108 my ($self) = @_;
110 if (not $self->{'fh'}) {
111 $self->{'fh'} = $self->{'server'}->accept();
112 if ($self->{'fh'}) {
113 $self->cb_connect();
115 else {
116 # Accept didn't.
117 $self->{'fh'} = undef;
118 return;
122 my $input = $self->lv_receive();
123 if (not $input) {
124 $self->close_fh();
125 return 0;
127 my @output = $self->formatted_eval($input);
128 my $output_s = "@output";
129 $self->lv_send($output_s);
130 return 1;
133 =head2 Callback Methods
135 =over
137 =item C<cb_listen()>
139 Called when the Server begins to listen for client connections.
141 =item C<cb_connect()>
143 Called whenever a client connection is accepted.
145 =item C<cb_disconnect()>
147 Called whenever a client disconnects.
149 =back
151 =cut
153 sub cb_listen {
154 my ($self) = @_;
155 $self->debug("Server PID $$ listening.");
158 sub cb_connect {
159 my ($self) = @_;
161 if (not $self->{'fh'}) {
162 return;
164 $self->debug("Server PID $$ connected with "
165 . $self->{'fh'}->peerhost()
166 . ':'
167 . $self->{'fh'}->peerport());
170 sub cb_disconnect {
171 my ($self) = @_;