File Coverage

blib/lib/JSON/Server.pm
Criterion Covered Total %
statement 114 155 73.5
branch 34 64 53.1
condition 2 3 66.6
subroutine 16 17 94.1
pod 3 9 33.3
total 169 248 68.1


line stmt bran cond sub pod time code
1             package JSON::Server;
2 10     10   731168 use warnings;
  10         98  
  10         338  
3 10     10   66 use strict;
  10         18  
  10         196  
4 10     10   50 use Carp;
  10         16  
  10         548  
5 10     10   66 use utf8;
  10         36  
  10         128  
6             our $VERSION = '0.02';
7              
8 10     10   6208 use IO::Socket;
  10         218578  
  10         44  
9 10     10   8870 use IO::Select;
  10         16606  
  10         612  
10 10     10   4602 use JSON::Create '0.34', ':all';
  10         13326  
  10         1568  
11 10     10   4548 use JSON::Parse '0.61', ':all';
  10         12384  
  10         16840  
12              
13             $SIG{PIPE} = sub {
14             croak "Aborting on SIGPIPE";
15             };
16              
17             sub set_opt
18             {
19 20     20 0 216 my ($gs, $o, $nm) = @_;
20             # Use exists here so that, e.g. verbose => $verbose, $verbose =
21             # undef works OK.
22 20 100       277 if (exists $o->{$nm}) {
23 11         110 $gs->{$nm} = $o->{$nm};
24 11         59 delete $o->{$nm};
25             }
26             }
27              
28             sub new
29             {
30 5     5 1 8940 my ($class, %o) = @_;
31 5         188 my $gs = {};
32 5         199 set_opt ($gs, \%o, 'verbose');
33 5         56 set_opt ($gs, \%o, 'port');
34 5         87 set_opt ($gs, \%o, 'handler');
35 5         52 set_opt ($gs, \%o, 'data');
36 5         78 for my $k (keys %o) {
37 0         0 carp "Unknown option '$k'";
38 0         0 delete $o{$k};
39             }
40 5 50       93 if (! $gs->{port}) {
41 0         0 carp "No port specified";
42             }
43 5         303 $gs->{jc} = JSON::Create->new (
44             indent => 1,
45             sort => 1,
46             downgrade_utf8 => 1,
47             );
48 5         716 $gs->{jc}->bool ('boolean');
49 5         400 $gs->{jp} = JSON::Parse->new ();
50 5         108 $gs->{jp}->upgrade_utf8 (1);
51 5         94 return bless $gs;
52             }
53              
54             sub so
55             {
56 13     13 0 296 my %so = (
57             Domain => IO::Socket::AF_INET,
58             Proto => 'tcp',
59             Type => IO::Socket::SOCK_STREAM,
60             );
61             # https://stackoverflow.com/a/2229946
62 13 50       156 if (defined eval { SO_REUSEPORT }) {
  13         155  
63 13         125 $so{ReusePort} = 1;
64             }
65 13         245 return %so;
66             }
67              
68             sub serve
69             {
70 5     5 1 84 my ($gs) = @_;
71 5         57 my %so = so ();
72             %so = (
73             %so,
74             Listen => 5,
75             LocalPort => $gs->{port},
76 5         286 );
77 5 50       55 if ($gs->{verbose}) {
78 0         0 vmsg ("Serving on $gs->{port}");
79             }
80 5         308 my $server = IO::Socket->new (%so);
81 5 50       6413 if (! $server) {
82 0         0 carp "Error from IO::Socket->new: $@";
83 0         0 return;
84             }
85 5         173 my $s = IO::Select->new ();
86 5         180 $s->add ($server);
87 5         605 while (my @ready = $s->can_read ()) {
88 14 50       5003628 if ($gs->{verbose}) {
89 0         0 vmsg ("Reading from @ready");
90             }
91 14         127 for my $fh (@ready) {
92 14 100       88 if ($fh == $server) {
93 5         200 my $new = $server->accept ();
94 5         1801 $s->add ($new);
95 5         440 next;
96             }
97 9         123 my $got = '';
98 9         45 my ($ok) = eval {
99 9 50       80 if ($gs->{verbose}) {
100 0         0 vmsg ("Got a message");
101             }
102 9         35 my $data;
103 9         25 my $max = 1000;
104 9   66     58 while (! defined $data || length ($data) == $max) {
105 9         28 $data = '';
106 9         209 my $recv_ret = $fh->recv ($data, $max);
107 9 50       545 if (! defined $recv_ret) {
108 0 0       0 if ($gs->{verbose}) {
109 0         0 vmsg ("recv had an error $@");
110             }
111 0         0 last;
112             }
113 9         60 $got .= $data;
114 9 100       243 if ($got =~ s/\x{00}$//) {
115 6         25 last;
116             }
117             }
118 9         54 1;
119             };
120 9 50       57 if (! $ok) {
121 0         0 carp "accept failed: $@";
122 0         0 next;
123             }
124 9 50       42 if ($gs->{verbose}) {
125 0         0 vmsg ("Received " . length ($got) . " bytes of data");
126             }
127 9 100       138 if (length ($got) == 0) {
128 3 50       25 if ($gs->{verbose}) {
129 0         0 vmsg ("Connection was closed");
130             }
131 3         378 return;
132             }
133 6 50       196 if (! valid_json ($got)) {
134 0 0       0 if ($gs->{verbose}) {
135 0         0 vmsg ("Not valid json");
136             }
137 0         0 $gs->reply ($fh, {error => 'invalid JSON'});
138 0         0 next;
139             }
140 6 50       302 if ($gs->{verbose}) {
141 0         0 vmsg ("Validated as JSON");
142             }
143 6         65 my $input = $gs->{jp}->parse ($got);
144 6 100       303 if (ref $input eq 'HASH') {
145 5         17 my $control = $input->{'JSON::Server::control'};
146 5 100       31 if (defined $control) {
147 2 50       12 if ($control eq 'stop') {
148 2 50       23 if ($gs->{verbose}) {
149 0         0 vmsg ("Received control message to stop");
150             }
151 2         74 $gs->reply ($fh, {'JSON::Server::response' => 'stopping'});
152 2 50       12 if ($gs->{verbose}) {
153 0         0 vmsg ("Responded to control message to stop");
154             }
155 2         11 $gs->close ($fh);
156 2         304 return;
157             }
158 0 0       0 if ($control eq 'close') {
159 0         0 $gs->reply ($fh, {'JSON::Server::response' => 'closing'});
160 0 0       0 if ($gs->{verbose}) {
161 0         0 vmsg ("Responded to control message to close connection");
162             }
163 0         0 $gs->close ($fh);
164 0         0 next;
165             }
166 0         0 warn "Unknown control command '$control'";
167             }
168             }
169 4         52 $gs->respond ($fh, $input);
170             }
171             }
172             }
173              
174             sub respond
175             {
176 4     4 0 20 my ($gs, $fh, $input) = @_;
177 4         13 my $reply;
178 4 50       26 if (! $gs->{handler}) {
179 0         0 carp "Handler is not set, will echo input back";
180 0         0 $gs->{handler} = \&echo;
181             }
182 4         20 my $ok = eval {
183 4         28 $reply = &{$gs->{handler}} ($gs->{data}, $input);
  4         98  
184 4         2125 1;
185             };
186 4 50       25 if (! $ok) {
187 0         0 carp "Handler crashed: $@";
188 0         0 $gs->reply ($fh, {error => "Handler crashed: $@"});
189 0         0 return;
190             }
191 4 50       189 if ($gs->{verbose}) {
192 0         0 vmsg ("Replying");
193             }
194 4         44 $gs->reply ($fh, $reply);
195             }
196              
197             sub reply
198             {
199 6     6 0 29 my ($gs, $fh, $msg) = @_;
200 6         229 my $json_msg = $gs->{jc}->create ($msg);
201 6 50       57 if ($gs->{verbose}) {
202 0         0 vmsg ("Sending $json_msg");
203             }
204 6         32 $json_msg .= chr (0);
205 6         157 my $sent = $fh->send ($json_msg);
206 6 50       897 if (! defined $sent) {
207 0         0 warn "Error sending: $@\n";
208             }
209 6 50       78 if ($gs->{verbose}) {
210 0         0 vmsg ("Sent");
211             }
212             }
213              
214             sub JSON::Server::close
215             {
216 2     2 1 9 my ($gs, $fh) = @_;
217 2 50       11 if ($gs->{verbose}) {
218 0         0 vmsg ("Closing connection");
219             }
220 2         21 $fh->close ();
221             }
222              
223             # This is the default callback of the server.
224              
225             sub echo
226             {
227 2     2 0 20 my ($data, $input) = @_;
228 2         8 return $input;
229             }
230              
231             sub vmsg
232             {
233 0     0 0   my ($msg) = @_;
234 0           print __PACKAGE__ . ": $msg.\n";
235             }
236              
237              
238             1;