File Coverage

blib/lib/Starlet/Server.pm
Criterion Covered Total %
statement 311 341 91.2
branch 115 180 63.8
condition 44 79 55.7
subroutine 40 41 97.5
pod 0 9 0.0
total 510 650 78.4


line stmt bran cond sub pod time code
1             package Starlet::Server;
2 114     114   699 use strict;
  114         321  
  114         3292  
3 114     114   755 use warnings;
  114         212  
  114         3062  
4              
5 114     114   656 use Carp ();
  114         222  
  114         2385  
6 114     114   81484 use Plack;
  114         15606  
  114         4660  
7 114     114   81530 use Plack::HTTPParser qw( parse_http_request );
  114         366718  
  114         8022  
8 114     114   881 use IO::Socket::INET;
  114         235  
  114         2067  
9 114     114   178652 use HTTP::Date;
  114         461550  
  114         11078  
10 114     114   22260 use HTTP::Status;
  114         119213  
  114         56826  
11 114     114   718 use List::Util qw(max sum);
  114         239  
  114         7859  
12 114     114   674 use Plack::Util;
  114         274  
  114         3100  
13 114     114   86869 use Plack::TempBuffer;
  114         928438  
  114         3643  
14 114     114   809 use POSIX qw(EINTR EAGAIN EWOULDBLOCK);
  114         251  
  114         1054  
15 114     114   12825 use Socket qw(IPPROTO_TCP TCP_NODELAY);
  114         245  
  114         10671  
16 114     114   619 use File::Temp qw(tempfile);
  114         249  
  114         18083  
17 114     114   578 use Fcntl qw(:flock);
  114         3666  
  114         14176  
18              
19 114     114   668 use Try::Tiny;
  114         207  
  114         7014  
20 114     114   583 use Time::HiRes qw(time);
  114         225  
  114         2449  
21              
22 114     114   36908 use constant MAX_REQUEST_SIZE => 131072;
  114         177  
  114         10903  
23 114     114   580 use constant CHUNKSIZE => 64 * 1024;
  114         354  
  114         6403  
24 114     114   515 use constant MSWin32 => $^O eq 'MSWin32';
  114         213  
  114         225805  
25              
26 114     114   776 my $null_io = do { open my $io, "<", \""; $io };
  114         210  
  114         3787  
27              
28             sub new {
29 114     114 0 434 my($class, %args) = @_;
30              
31             my $self = bless {
32             listens => $args{listens} || [],
33             host => $args{host} || 0,
34             port => $args{port} || $args{socket} || 8080,
35             timeout => $args{timeout} || 300,
36             keepalive_timeout => $args{keepalive_timeout} || 2,
37             max_keepalive_reqs => $args{max_keepalive_reqs} || 1,
38             server_software => $args{server_software} || $class,
39       112     server_ready => $args{server_ready} || sub {},
40             min_reqs_per_child => (
41             defined $args{min_reqs_per_child}
42             ? $args{min_reqs_per_child} : undef,
43             ),
44             max_reqs_per_child => (
45             $args{max_reqs_per_child} || $args{max_requests} || 100,
46             ),
47             spawn_interval => $args{spawn_interval} || 0,
48             err_respawn_interval => (
49             defined $args{err_respawn_interval}
50             ? $args{err_respawn_interval} : undef,
51 114 100 100     8628 ),
    50 100        
      100        
      50        
      50        
      50        
      33        
      100        
      50        
      50        
52             is_multiprocess => Plack::Util::FALSE,
53             _using_defer_accept => undef,
54             }, $class;
55              
56 114 50 33     641 if ($args{max_workers} && $args{max_workers} > 1) {
57 0         0 Carp::carp(
58             "Preforking in $class is deprecated. Falling back to the non-forking mode. ",
59             "If you need preforking, use Starman or Starlet instead and run like `plackup -s Starlet`",
60             );
61             }
62              
63 114         468 $self;
64             }
65              
66             sub run {
67 0     0 0 0 my($self, $app) = @_;
68 0         0 $self->setup_listener();
69 0         0 $self->accept_loop($app);
70             }
71              
72             sub setup_listener {
73 113     113 0 240 my $self = shift;
74 113 100       240 if (scalar(grep {defined $_} @{$self->{listens}}) == 0) {
  72         153  
  113         604  
75 104         156 my $sock;
76 104 100       888 if ($self->{port} =~ /^[0-9]+$/s) {
77             $sock = IO::Socket::INET->new(
78             Listen => SOMAXCONN,
79             LocalPort => $self->{port},
80             LocalAddr => $self->{host},
81 98 50       1951 Proto => 'tcp',
82             ReuseAddr => 1,
83             ) or die "failed to listen to port $self->{port}:$!";
84             } else {
85             $sock = IO::Socket::UNIX->new(
86             Listen => SOMAXCONN,
87             Local => $self->{port},
88 6 50       126 ) or die "failed to listen to socket $self->{port}:$!";
89             }
90             $self->{listens}[fileno($sock)] = {
91             host => $self->{host},
92             port => $self->{port},
93 104         50922 sock => $sock,
94             };
95             }
96              
97 113         269 my @listens = grep {defined $_} @{$self->{listens}};
  500         928  
  113         407  
98 113         430 for my $listen (@listens) {
99 131         1650 my $family = Socket::sockaddr_family(getsockname($listen->{sock}));
100 131         467 $listen->{_is_tcp} = $family != AF_UNIX;
101              
102             # set defer accept
103 131 100 33     1435 if ($^O eq 'linux' && $listen->{_is_tcp}) {
104             setsockopt($listen->{sock}, IPPROTO_TCP, 9, 1)
105 107 50       1152 and $listen->{_using_defer_accept} = 1;
106             }
107             }
108              
109 113 100       438 if (scalar(@listens) > 1) {
110 3   33     18 $self->{lock_path} ||= do {
111 3         78 my ($fh, $lock_path) = tempfile(UNLINK => 1);
112             # closing the file handle explicitly for two reasons
113             # 1) tempfile retains the handle when UNLINK is set
114             # 2) tempfile implicitely locks the file on OS X
115 3         2196 close $fh;
116 3         15 $lock_path;
117             };
118             }
119              
120 113         527 $self->{server_ready}->($self);
121             }
122              
123             sub accept_loop {
124             # TODO handle $max_reqs_per_child
125 101     101 0 1562 my($self, $app, $max_reqs_per_child) = @_;
126 101         1420 my $proc_req_count = 0;
127 101         972 my $is_keepalive = 0;
128              
129             local $SIG{TERM} = sub {
130 84     84   26330448 $self->{term_received} = 1;
131 101         10171 };
132 101         4789 local $SIG{PIPE} = 'IGNORE';
133              
134 101         3042 my $acceptor = $self->_get_acceptor;
135              
136 101   33     4069 while (! defined $max_reqs_per_child || $proc_req_count < $max_reqs_per_child) {
137             # accept (or exit on SIGTERM)
138 272 100       55967 exit 0 if $self->{term_received};
139 188         1268 my ($conn, $peer, $listen) = $acceptor->();
140 188 100       2998 next unless $conn;
141              
142 104         1677 $self->{_is_deferred_accept} = $listen->{_using_defer_accept};
143 104 50       3153 defined($conn->blocking(0))
144             or die "failed to set socket to nonblocking mode:$!";
145 104         5834 my ($peerport, $peerhost, $peeraddr) = (0, undef, undef);
146 104 100       1136 if ($listen->{_is_tcp}) {
147 98 50       2208 $conn->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
148             or die "setsockopt(TCP_NODELAY) failed:$!";
149 98         3204 ($peerport, $peerhost) = unpack_sockaddr_in $peer;
150 98         2021 $peeraddr = inet_ntoa($peerhost);
151             }
152 104         533 my $req_count = 0;
153 104         571 my $pipelined_buf = '';
154              
155 104         526 while (1) {
156 104         464 ++$req_count;
157 104         294 ++$proc_req_count;
158             my $env = {
159             SERVER_PORT => $listen->{port} || 0,
160             SERVER_NAME => $listen->{host} || 0,
161             SCRIPT_NAME => '',
162             REMOTE_ADDR => $peeraddr,
163             REMOTE_PORT => $peerport,
164             'psgi.version' => [ 1, 1 ],
165             'psgi.errors' => *STDERR,
166             'psgi.url_scheme' => 'http',
167             'psgi.run_once' => Plack::Util::FALSE,
168             'psgi.multithread' => Plack::Util::FALSE,
169             'psgi.multiprocess' => $self->{is_multiprocess},
170 104   50     6700 'psgi.streaming' => Plack::Util::TRUE,
      100        
171             'psgi.nonblocking' => Plack::Util::FALSE,
172             'psgix.input.buffered' => Plack::Util::TRUE,
173             'psgix.io' => $conn,
174             'psgix.harakiri' => 1,
175             };
176              
177 104         650 my $may_keepalive = $req_count < $self->{max_keepalive_reqs};
178 104 0 33     764 if ($may_keepalive && $max_reqs_per_child && $proc_req_count >= $max_reqs_per_child) {
      33        
179 0         0 $may_keepalive = undef;
180             }
181 104 50       841 $may_keepalive = 1 if length $pipelined_buf;
182 104         309 my $keepalive;
183 104         2321 ($keepalive, $pipelined_buf) = $self->handle_connection($env, $conn, $app,
184             $may_keepalive, $req_count != 1, $pipelined_buf);
185              
186 103 100       786 if ($env->{'psgix.harakiri.commit'}) {
187 16         172 $conn->close;
188 16         8105 return;
189             }
190 87 50       1225 last unless $keepalive;
191             # TODO add special cases for clients with broken keep-alive support, as well as disabling keep-alive for HTTP/1.0 proxies
192             }
193 87         1152 $conn->close;
194             }
195             }
196              
197             sub _get_acceptor {
198 101     101   1362 my $self = shift;
199 101         1180 my @listens = grep {defined $_} @{$self->{listens}};
  440         3198  
  101         2307  
200              
201 101 100       1392 if (scalar(@listens) == 1) {
202 99         750 my $listen = $listens[0];
203             return sub {
204 179 100   179   12011 if (my ($conn, $peer) = $listen->{sock}->accept) {
205 97         11660373 return ($conn, $peer, $listen);
206             }
207 82         705 return +();
208 99         3712 };
209             }
210             else {
211             # wait for multiple sockets with select(2)
212 2         27 my @fds;
213 2         35 my $rin = '';
214 2         57 for my $listen (@listens) {
215 14 50       349 defined($listen->{sock}->blocking(0))
216             or die "failed to set listening socket to non-blocking mode:$!";
217 14         466 my $fd = fileno($listen->{sock});
218 14         51 push @fds, $fd;
219 14         140 vec($rin, $fd, 1) = 1;
220             }
221              
222             open(my $lock_fh, '>', $self->{lock_path})
223 2 50       562 or die "failed to open lock file:@{[$self->{lock_path}]}:$!";
  0         0  
224              
225             return sub {
226 9 100   9   955571 if (! flock($lock_fh, LOCK_EX)) {
227 1 50       108 die "failed to lock file:@{[$self->{lock_path}]}:$!"
  0         0  
228             if $! != EINTR;
229 1         6 return +();
230             }
231 8         956765 my $nfound = select(my $rout = $rin, undef, undef, undef);
232 8         295 for (my $i = 0; $nfound > 0; ++$i) {
233 28         60 my $fd = $fds[$i];
234 28 100       144 next unless vec($rout, $fd, 1);
235 7         22 --$nfound;
236 7         41 my $listen = $self->{listens}[$fd];
237 7 50       196 if (my ($conn, $peer) = $listen->{sock}->accept) {
238 7         2860 flock($lock_fh, LOCK_UN);
239 7         111 return ($conn, $peer, $listen);
240             }
241             }
242 1         13 flock($lock_fh, LOCK_UN);
243 1         5 return +();
244 2         64 };
245             }
246             }
247              
248             my $bad_response = [ 400, [ 'Content-Type' => 'text/plain', 'Connection' => 'close' ], [ 'Bad Request' ] ];
249             sub handle_connection {
250 104     104 0 534 my($self, $env, $conn, $app, $use_keepalive, $is_keepalive, $prebuf) = @_;
251            
252 104         754 my $buf = '';
253 104         678 my $pipelined_buf='';
254 104         465 my $res = $bad_response;
255            
256 104         264 while (1) {
257 104         306 my $rlen;
258 104 50       1152 if ( $rlen = length $prebuf ) {
259 0         0 $buf = $prebuf;
260 0         0 undef $prebuf;
261             }
262             else {
263             $rlen = $self->read_timeout(
264             $conn, \$buf, MAX_REQUEST_SIZE - length($buf), length($buf),
265             $is_keepalive ? $self->{keepalive_timeout} : $self->{timeout},
266 104 50       2129 ) or return;
    100          
267             }
268 94         1884 my $reqlen = parse_http_request($buf, $env);
269 94 100       37251 if ($reqlen >= 0) {
270             # handle request
271 93         364 my $protocol = $env->{SERVER_PROTOCOL};
272 93 50       489 if ($use_keepalive) {
273 0 0       0 if ($self->{term_received}) {
    0          
274 0         0 $use_keepalive = undef;
275             } elsif ( $protocol eq 'HTTP/1.1' ) {
276 0 0       0 if (my $c = $env->{HTTP_CONNECTION}) {
277 0 0       0 $use_keepalive = undef
278             if $c =~ /^\s*close\s*/i;
279             }
280             } else {
281 0 0       0 if (my $c = $env->{HTTP_CONNECTION}) {
282 0 0       0 $use_keepalive = undef
283             unless $c =~ /^\s*keep-alive\s*/i;
284             } else {
285 0         0 $use_keepalive = undef;
286             }
287             }
288             }
289 93         618 $buf = substr $buf, $reqlen;
290 114     114   734 my $chunked = do { no warnings; lc delete $env->{HTTP_TRANSFER_ENCODING} eq 'chunked' };
  114         230  
  114         261137  
  93         207  
  93         501  
291 93 100       615 if (my $cl = $env->{CONTENT_LENGTH}) {
    100          
292 5         158 my $buffer = Plack::TempBuffer->new($cl);
293 5         639 while ($cl > 0) {
294 5         13 my $chunk;
295 5 100       26 if (length $buf) {
296 4         14 $chunk = $buf;
297 4         22 $buf = '';
298             } else {
299             $self->read_timeout(
300             $conn, \$chunk, $cl, 0, $self->{timeout})
301 1 50       7 or return;
302             }
303 4         26 $buffer->print($chunk);
304 4         467 $cl -= length $chunk;
305             }
306 4         32 $env->{'psgi.input'} = $buffer->rewind;
307             }
308             elsif ($chunked) {
309 1         57 my $buffer = Plack::TempBuffer->new;
310 1         262 my $chunk_buffer = '';
311 1         3 my $length;
312 1         3 DECHUNK: while(1) {
313 3         7 my $chunk;
314 3 100       13 if ( length $buf ) {
315 1         4 $chunk = $buf;
316 1         11 $buf = '';
317             }
318             else {
319             $self->read_timeout($conn, \$chunk, CHUNKSIZE, 0, $self->{timeout})
320 2 50       15 or return;
321             }
322              
323 3         143 $chunk_buffer .= $chunk;
324 3         211 while ( $chunk_buffer =~ s/^(([0-9a-fA-F]+).*\015\012)// ) {
325 79         240 my $trailer = $1;
326 79         204 my $chunk_len = hex $2;
327 79 100       359 if ($chunk_len == 0) {
    50          
328 1         6 last DECHUNK;
329             } elsif (length $chunk_buffer < $chunk_len + 2) {
330 0         0 $chunk_buffer = $trailer . $chunk_buffer;
331 0         0 last;
332             }
333 78         453 $buffer->print(substr $chunk_buffer, 0, $chunk_len, '');
334 78         2218 $chunk_buffer =~ s/^\015\012//;
335 78         812 $length += $chunk_len;
336             }
337             }
338 1         14 $env->{CONTENT_LENGTH} = $length;
339 1         10 $env->{'psgi.input'} = $buffer->rewind;
340             } else {
341 87 50       439 if ( $buf =~ m!^(?:GET|HEAD)! ) { #pipeline
342 0         0 $pipelined_buf = $buf;
343 0         0 $use_keepalive = 1; #force keepalive
344             } # else clear buffer
345 87         643 $env->{'psgi.input'} = $null_io;
346             }
347              
348 92 50       1293 if ( $env->{HTTP_EXPECT} ) {
349 0 0       0 if ( $env->{HTTP_EXPECT} eq '100-continue' ) {
350 0 0       0 $self->write_all($conn, "HTTP/1.1 100 Continue\015\012\015\012")
351             or return;
352             } else {
353 0         0 $res = [417,[ 'Content-Type' => 'text/plain', 'Connection' => 'close' ], [ 'Expectation Failed' ] ];
354 0         0 last;
355             }
356             }
357              
358 92         1951 $res = Plack::Util::run_app $app, $env;
359 91         20228 last;
360             }
361 1 50       8 if ($reqlen == -2) {
    50          
362             # request is incomplete, do nothing
363             } elsif ($reqlen == -1) {
364             # error, close conn
365 1         6 last;
366             }
367             }
368              
369 92 100       529 if (ref $res eq 'ARRAY') {
    50          
370 89         1138 $self->_handle_response($env->{SERVER_PROTOCOL}, $res, $conn, \$use_keepalive);
371             } elsif (ref $res eq 'CODE') {
372             $res->(sub {
373 3     3   302 $self->_handle_response($env->{SERVER_PROTOCOL}, $_[0], $conn, \$use_keepalive);
374 3         60 });
375             } else {
376 0         0 die "Bad response $res";
377             }
378            
379 92         794 return ($use_keepalive, $pipelined_buf);
380             }
381              
382             sub _handle_response {
383 92     92   331 my($self, $protocol, $res, $conn, $use_keepalive_r) = @_;
384 92         309 my $status_code = $res->[0];
385 92         242 my $headers = $res->[1];
386 92         217 my $body = $res->[2];
387            
388 92         241 my @lines;
389             my %send_headers;
390 92         613 for (my $i = 0; $i < @$headers; $i += 2) {
391 107         329 my $k = $headers->[$i];
392 107         395 my $v = $headers->[$i + 1];
393 107         375 my $lck = lc $k;
394 107 100       481 if ($lck eq 'connection') {
395 1 50 33     8 $$use_keepalive_r = undef
396             if $$use_keepalive_r && lc $v ne 'keep-alive';
397             } else {
398 106         493 push @lines, "$k: $v\015\012";
399 106         1162 $send_headers{$lck} = $v;
400             }
401             }
402 92 100       575 if ( ! exists $send_headers{server} ) {
403 91         563 unshift @lines, "Server: $self->{server_software}\015\012";
404             }
405 92 50       593 if ( ! exists $send_headers{date} ) {
406 92         310 unshift @lines, "Date: @{[HTTP::Date::time2str()]}\015\012";
  92         1219  
407             }
408              
409             # try to set content-length when keepalive can be used, or disable it
410 92         4151 my $use_chunked;
411 92 100 100     1337 if (defined($protocol) && $protocol eq 'HTTP/1.1') {
412 81 100 66     3155 if (defined $send_headers{'content-length'}
    100          
413             || defined $send_headers{'transfer-encoding'}) {
414             # ok
415             } elsif (!Plack::Util::status_with_no_entity_body($status_code)) {
416 76         1088 push @lines, "Transfer-Encoding: chunked\015\012";
417 76         242 $use_chunked = 1;
418             }
419 81 50       565 push @lines, "Connection: close\015\012" unless $$use_keepalive_r;
420             } else {
421             # HTTP/1.0
422 11 50       114 if ($$use_keepalive_r) {
423 0 0 0     0 if (defined $send_headers{'content-length'}
    0 0        
424             || defined $send_headers{'transfer-encoding'}) {
425             # ok
426             } elsif (!Plack::Util::status_with_no_entity_body($status_code)
427             && defined(my $cl = Plack::Util::content_length($body))) {
428 0         0 push @lines, "Content-Length: $cl\015\012";
429             } else {
430 0         0 $$use_keepalive_r = undef;
431             }
432             }
433 11 50       41 push @lines, "Connection: keep-alive\015\012" if $$use_keepalive_r;
434 11 50       101 push @lines, "Connection: close\015\012" if !$$use_keepalive_r; #fmm..
435             }
436              
437 92         375 unshift @lines, "HTTP/1.1 $status_code @{[ HTTP::Status::status_message($status_code) ]}\015\012";
  92         1959  
438 92         1699 push @lines, "\015\012";
439            
440 92 100 100     2520 if (defined $body && ref $body eq 'ARRAY' && @$body == 1
      100        
      100        
441             && length $body->[0] < 8192) {
442             # combine response header and small request body
443 82         333 my $buf = $body->[0];
444 82 100       455 if ($use_chunked ) {
445 70         161 my $len = length $buf;
446 70         452 $buf = sprintf("%x",$len) . "\015\012" . $buf . "\015\012" . '0' . "\015\012\015\012";
447             }
448             $self->write_all(
449             $conn, join('', @lines, $buf), $self->{timeout},
450 82         867 );
451 82         500 return;
452             }
453             $self->write_all($conn, join('', @lines), $self->{timeout})
454 10 50       202 or return;
455              
456 10 100       53 if (defined $body) {
457 8         16 my $failed;
458             my $completed;
459 8 100       59 my $body_count = (ref $body eq 'ARRAY') ? $#{$body} + 1 : -1;
  3         14  
460             Plack::Util::foreach(
461             $body,
462             sub {
463 10 50   10   1290 unless ($failed) {
464 10         38 my $buf = $_[0];
465 10         27 --$body_count;
466 10 100       42 if ( $use_chunked ) {
467 7         16 my $len = length $buf;
468 7 50       31 return unless $len;
469 7         399 $buf = sprintf("%x",$len) . "\015\012" . $buf . "\015\012";
470 7 100       40 if ( $body_count == 0 ) {
471 2         13 $buf .= '0' . "\015\012\015\012";
472 2         6 $completed = 1;
473             }
474             }
475             $self->write_all($conn, $buf, $self->{timeout})
476 10 50       52 or $failed = 1;
477             }
478             },
479 8         204 );
480 8 100 100     4493 $self->write_all($conn, '0' . "\015\012\015\012", $self->{timeout}) if $use_chunked && !$completed;
481             } else {
482             return Plack::Util::inline_object
483             write => sub {
484 5     5   445 my $buf = $_[0];
485 5 50       27 if ( $use_chunked ) {
486 5         12 my $len = length $buf;
487 5 100       23 return unless $len;
488 4         29 $buf = sprintf("%x",$len) . "\015\012" . $buf . "\015\012"
489             }
490             $self->write_all($conn, $buf, $self->{timeout})
491 4         20 },
492             close => sub {
493 2 50   2   78 $self->write_all($conn, '0' . "\015\012\015\012", $self->{timeout}) if $use_chunked;
494 2         92 };
495             }
496             }
497              
498             # returns value returned by $cb, or undef on timeout or network error
499             sub do_io {
500 217     217 0 1044 my ($self, $is_write, $sock, $buf, $len, $off, $timeout) = @_;
501 217         485 my $ret;
502 217 100 66     1976 unless ($is_write || delete $self->{_is_deferred_accept}) {
503 9         125 goto DO_SELECT;
504             }
505             DO_READWRITE:
506             # try to do the IO
507 217 100       767 if ($is_write) {
508 110 50       9957 $ret = syswrite $sock, $buf, $len, $off
509             and return $ret;
510             } else {
511 107 100       15335 $ret = sysread $sock, $$buf, $len, $off
512             and return $ret;
513             }
514 11 50 0     111 unless ((! defined($ret)
      33        
515             && ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK))) {
516 11         189 return;
517             }
518             # wait for data
519             DO_SELECT:
520 9         22 while (1) {
521 9         32 my ($rfd, $wfd);
522 9         53 my $efd = '';
523 9         113 vec($efd, fileno($sock), 1) = 1;
524 9 50       227 if ($is_write) {
525 0         0 ($rfd, $wfd) = ('', $efd);
526             } else {
527 9         36 ($rfd, $wfd) = ($efd, '');
528             }
529 9         121 my $start_at = time;
530 9         256 my $nfound = select($rfd, $wfd, $efd, $timeout);
531 9         69 $timeout -= (time - $start_at);
532 9 50       57 last if $nfound;
533 0 0       0 return if $timeout <= 0;
534             }
535 9         28 goto DO_READWRITE;
536             }
537              
538             # returns (positive) number of bytes read, or undef if the socket is to be closed
539             sub read_timeout {
540 107     107 0 469 my ($self, $sock, $buf, $len, $off, $timeout) = @_;
541 107         1831 $self->do_io(undef, $sock, $buf, $len, $off, $timeout);
542             }
543              
544             # returns (positive) number of bytes written, or undef if the socket is to be closed
545             sub write_timeout {
546 110     110 0 338 my ($self, $sock, $buf, $len, $off, $timeout) = @_;
547 110         461 $self->do_io(1, $sock, $buf, $len, $off, $timeout);
548             }
549              
550             # writes all data in buf and returns number of bytes written or undef if failed
551             sub write_all {
552 110     110 0 470 my ($self, $sock, $buf, $timeout) = @_;
553 110         276 my $off = 0;
554 110         676 while (my $len = length($buf) - $off) {
555 110 50       1426 my $ret = $self->write_timeout($sock, $buf, $len, $off, $timeout)
556             or return;
557 110         563 $off += $ret;
558             }
559 110         580 return length $buf;
560             }
561              
562             1;