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   610710 use warnings;
  10         92  
  10         460  
3 10     10   48 use strict;
  10         18  
  10         196  
4 10     10   38 use Carp;
  10         52  
  10         608  
5 10     10   56 use utf8;
  10         12  
  10         76  
6             our $VERSION = '0.03';
7              
8 10     10   4836 use IO::Socket;
  10         183650  
  10         38  
9 10     10   7388 use IO::Select;
  10         13878  
  10         622  
10 10     10   3678 use JSON::Create '0.35', ':all';
  10         11088  
  10         1394  
11 10     10   3738 use JSON::Parse '0.61', ':all';
  10         11520  
  10         13938  
12              
13             $SIG{PIPE} = sub {
14             croak "Aborting on SIGPIPE";
15             };
16              
17             sub set_opt
18             {
19 20     20 0 187 my ($gs, $o, $nm) = @_;
20             # Use exists here so that, e.g. verbose => $verbose, $verbose =
21             # undef works OK.
22 20 100       237 if (exists $o->{$nm}) {
23 11         71 $gs->{$nm} = $o->{$nm};
24 11         78 delete $o->{$nm};
25             }
26             }
27              
28             sub new
29             {
30 5     5 1 19576 my ($class, %o) = @_;
31 5         164 my $gs = {};
32 5         192 set_opt ($gs, \%o, 'verbose');
33 5         100 set_opt ($gs, \%o, 'port');
34 5         60 set_opt ($gs, \%o, 'handler');
35 5         46 set_opt ($gs, \%o, 'data');
36 5         81 for my $k (keys %o) {
37 0         0 carp "Unknown option '$k'";
38 0         0 delete $o{$k};
39             }
40 5 50       138 if (! $gs->{port}) {
41 0         0 carp "No port specified";
42             }
43 5         339 $gs->{jc} = JSON::Create->new (
44             indent => 1,
45             sort => 1,
46             downgrade_utf8 => 1,
47             );
48 5         669 $gs->{jc}->bool ('boolean');
49 5         552 $gs->{jp} = JSON::Parse->new ();
50 5         109 $gs->{jp}->upgrade_utf8 (1);
51 5         85 return bless $gs;
52             }
53              
54             sub so
55             {
56 13     13 0 292 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       185 if (defined eval { SO_REUSEPORT }) {
  13         179  
63 13         162 $so{ReusePort} = 1;
64             }
65 13         244 return %so;
66             }
67              
68             sub serve
69             {
70 5     5 1 104 my ($gs) = @_;
71 5         45 my %so = so ();
72             %so = (
73             %so,
74             Listen => 5,
75             LocalPort => $gs->{port},
76 5         302 );
77 5 50       62 if ($gs->{verbose}) {
78 0         0 vmsg ("Serving on $gs->{port}");
79             }
80 5         144 my $server = IO::Socket->new (%so);
81 5 50       5144 if (! $server) {
82 0         0 carp "Error from IO::Socket->new: $@";
83 0         0 return;
84             }
85 5         138 my $s = IO::Select->new ();
86 5         107 $s->add ($server);
87 5         580 while (my @ready = $s->can_read ()) {
88 14 50       5007677 if ($gs->{verbose}) {
89 0         0 vmsg ("Reading from @ready");
90             }
91 14         114 for my $fh (@ready) {
92 14 100       86 if ($fh == $server) {
93 5         229 my $new = $server->accept ();
94 5         1479 $s->add ($new);
95 5         391 next;
96             }
97 9         80 my $got = '';
98 9         38 my ($ok) = eval {
99 9 50       55 if ($gs->{verbose}) {
100 0         0 vmsg ("Got a message");
101             }
102 9         22 my $data;
103 9         22 my $max = 1000;
104 9   66     59 while (! defined $data || length ($data) == $max) {
105 9         94 $data = '';
106 9         126 my $recv_ret = $fh->recv ($data, $max);
107 9 50       496 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         45 $got .= $data;
114 9 100       223 if ($got =~ s/\x{00}$//) {
115 6         24 last;
116             }
117             }
118 9         42 1;
119             };
120 9 50       63 if (! $ok) {
121 0         0 carp "accept failed: $@";
122 0         0 next;
123             }
124 9 50       43 if ($gs->{verbose}) {
125 0         0 vmsg ("Received " . length ($got) . " bytes of data");
126             }
127 9 100       56 if (length ($got) == 0) {
128 3 50       15 if ($gs->{verbose}) {
129 0         0 vmsg ("Connection was closed");
130             }
131 3         305 return;
132             }
133 6 50       114 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       241 if ($gs->{verbose}) {
141 0         0 vmsg ("Validated as JSON");
142             }
143 6         142 my $input = $gs->{jp}->parse ($got);
144 6 100       334 if (ref $input eq 'HASH') {
145 5         27 my $control = $input->{'JSON::Server::control'};
146 5 100       40 if (defined $control) {
147 2 50       13 if ($control eq 'stop') {
148 2 50       11 if ($gs->{verbose}) {
149 0         0 vmsg ("Received control message to stop");
150             }
151 2         84 $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         12 $gs->close ($fh);
156 2         241 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         35 $gs->respond ($fh, $input);
170             }
171             }
172             }
173              
174             sub respond
175             {
176 4     4 0 22 my ($gs, $fh, $input) = @_;
177 4         18 my $reply;
178 4 50       32 if (! $gs->{handler}) {
179 0         0 carp "Handler is not set, will echo input back";
180 0         0 $gs->{handler} = \&echo;
181             }
182 4         16 my $ok = eval {
183 4         25 $reply = &{$gs->{handler}} ($gs->{data}, $input);
  4         67  
184 4         2202 1;
185             };
186 4 50       38 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       60 if ($gs->{verbose}) {
192 0         0 vmsg ("Replying");
193             }
194 4         30 $gs->reply ($fh, $reply);
195             }
196              
197             sub reply
198             {
199 6     6 0 39 my ($gs, $fh, $msg) = @_;
200 6         251 my $json_msg = $gs->{jc}->create ($msg);
201 6 50       67 if ($gs->{verbose}) {
202 0         0 vmsg ("Sending $json_msg");
203             }
204 6         17 $json_msg .= chr (0);
205 6         84 my $sent = $fh->send ($json_msg);
206 6 50       731 if (! defined $sent) {
207 0         0 warn "Error sending: $@\n";
208             }
209 6 50       85 if ($gs->{verbose}) {
210 0         0 vmsg ("Sent");
211             }
212             }
213              
214             sub JSON::Server::close
215             {
216 2     2 1 7 my ($gs, $fh) = @_;
217 2 50       10 if ($gs->{verbose}) {
218 0         0 vmsg ("Closing connection");
219             }
220 2         39 $fh->close ();
221             }
222              
223             # This is the default callback of the server.
224              
225             sub echo
226             {
227 2     2 0 9 my ($data, $input) = @_;
228 2         5 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;