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