File Coverage

blib/lib/Net/HTTPS/NB.pm
Criterion Covered Total %
statement 44 102 43.1
branch 14 32 43.7
condition 2 14 14.2
subroutine 9 13 69.2
pod 7 7 100.0
total 76 168 45.2


line stmt bran cond sub pod time code
1             package Net::HTTPS::NB;
2              
3 3     3   184569 use strict;
  3         9  
  3         177  
4 3     3   4014 use Net::HTTP;
  3         386703  
  3         48  
5 3     3   7326 use IO::Socket::SSL 0.98;
  3         407133  
  3         33  
6 3     3   747 use Exporter;
  3         6  
  3         135  
7 3     3   18 use vars qw($VERSION @ISA @EXPORT $HTTPS_ERROR);
  3         6  
  3         480  
8              
9             $VERSION = 0.13;
10              
11             =head1 NAME
12              
13             Net::HTTPS::NB - Non-blocking HTTPS client
14              
15             =head1 SYNOPSIS
16              
17             =over
18              
19             =item Example from L
20              
21             use Net::HTTPS::NB;
22             use IO::Select;
23             use strict;
24              
25             my $s = Net::HTTPS::NB->new(Host => "pause.perl.org") || die $@;
26             $s->write_request(GET => "/");
27              
28             my $sel = IO::Select->new($s);
29              
30             READ_HEADER: {
31             die "Header timeout" unless $sel->can_read(10);
32             my($code, $mess, %h) = $s->read_response_headers;
33             redo READ_HEADER unless $code;
34             }
35              
36             while (1) {
37             die "Body timeout" unless $sel->can_read(10);
38             my $buf;
39             my $n = $s->read_entity_body($buf, 1024);
40             last unless $n;
41             print $buf;
42             }
43              
44             =item Example of non-blocking connect
45              
46             use strict;
47             use Net::HTTPS::NB;
48             use IO::Select;
49              
50             my $sock = Net::HTTPS::NB->new(Host => 'encrypted.google.com', Blocking => 0);
51             my $sele = IO::Select->new($sock);
52              
53             until ($sock->connected) {
54             if ($HTTPS_ERROR == HTTPS_WANT_READ) {
55             $sele->can_read();
56             }
57             elsif($HTTPS_ERROR == HTTPS_WANT_WRITE) {
58             $sele->can_write();
59             }
60             else {
61             die 'Unknown error: ', $HTTPS_ERROR;
62             }
63             }
64              
65             =back
66              
67             See `examples' subdirectory for more examples.
68              
69             =head1 DESCRIPTION
70              
71             Same interface as Net::HTTPS but it will never try multiple reads when the
72             read_response_headers() or read_entity_body() methods are invoked. In addition
73             allows non-blocking connect.
74              
75             =over
76              
77             =item If read_response_headers() did not see enough data to complete the headers an empty list is returned.
78              
79             =item If read_entity_body() did not see new entity data in its read the value -1 is returned.
80              
81             =back
82              
83             =cut
84              
85             # we only supports IO::Socket::SSL now
86             # use it force
87             $Net::HTTPS::SSL_SOCKET_CLASS = 'IO::Socket::SSL';
88             require Net::HTTPS;
89              
90             # make aliases to IO::Socket::SSL variables and constants
91             use constant {
92 3         4872 HTTPS_WANT_READ => SSL_WANT_READ,
93             HTTPS_WANT_WRITE => SSL_WANT_WRITE,
94 3     3   18 };
  3         6  
95             *HTTPS_ERROR = \$SSL_ERROR;
96              
97             =head1 PACKAGE CONSTANTS
98              
99             Imported by default
100              
101             HTTPS_WANT_READ
102             HTTPS_WANT_WRITE
103              
104             =head1 PACKAGE VARIABLES
105              
106             Imported by default
107              
108             $HTTPS_ERROR
109              
110             =cut
111              
112             # need export some stuff for error handling
113             @EXPORT = qw($HTTPS_ERROR HTTPS_WANT_READ HTTPS_WANT_WRITE);
114             @ISA = qw(Net::HTTPS Exporter);
115              
116             =head1 METHODS
117              
118             =head2 new(%cfg)
119              
120             Same as Net::HTTPS::new, but in addition allows `Blocking' parameter. By setting
121             this parameter to 0 you can perform non-blocking connect. See connected() to
122             determine when connection completed.
123              
124             =cut
125              
126             sub new {
127 3     3 1 45323 my ($class, %args) = @_;
128            
129 3         49 my %ssl_opts;
130 3         63 while (my $name = each %args) {
131 7 50       78 if (substr($name, 0, 4) eq 'SSL_') {
132 0         0 $ssl_opts{$name} = delete $args{$name};
133             }
134             }
135            
136 3 50       49 unless (exists $args{PeerPort}) {
137 0         0 $args{PeerPort} = 443;
138             }
139            
140             # create plain socket first
141 3 50       149 my $self = Net::HTTP->new(%args)
142             or return;
143            
144             # and upgrade it to SSL then
145 3 50       29795 $class->start_SSL($self, %ssl_opts, SSL_startHandshake => 0)
146             or return;
147            
148 3 100 66     21204 if (!exists($args{Blocking}) || $args{Blocking}) {
149             # blocking connect
150 2 50       28 $self->connected()
151             or return;
152             }
153             # non-blocking handshake will be started after SUPER::connected
154            
155 1         31 return $self;
156             }
157              
158             =head2 connected()
159              
160             Returns true value when connection completed (https handshake done). Otherwise
161             returns false. In this case you can check $HTTPS_ERROR to determine what handshake
162             need for, read or write. $HTTPS_ERROR could be HTTPS_WANT_READ or HTTPS_WANT_WRITE
163             respectively. See L.
164              
165             =cut
166              
167             sub connected {
168 5     5 1 1361 my $self = shift;
169            
170 5 50       14 if (exists ${*$self}{httpsnb_connected}) {
  5         43  
171             # already connected or disconnected
172 0         0 return ${*$self}{httpsnb_connected};
  0         0  
173             }
174            
175 5 100       12 if (${*$self}{httpsnb_super_connected}) {
  5         22  
176             # SUPER already connected
177             # start/continue SSL handshaking
178 2 50       50 if ( $self->connect_SSL() ) {
179 0         0 return ${*$self}{httpsnb_connected} = 1;
  0         0  
180             }
181 2         798 return 0;
182             }
183            
184 3 100       58 if ($self->SUPER::connected) {
185             # SUPER just connected. Start handshaking
186 2         112 ${*$self}{httpsnb_super_connected} = 1;
  2         36  
187 2         26 return $self->connected;
188             }
189            
190             # SUPER still not connected
191 1 50       130 if ($! = $self->sockopt(SO_ERROR)) {
192             # some error while connecting
193 0         0 $HTTPS_ERROR = $!;
194             }
195             else {
196 1         82 $HTTPS_ERROR = HTTPS_WANT_WRITE;
197             }
198 1         16 return 0;
199             }
200              
201             sub close {
202 0     0 1 0 my $self = shift;
203             # need some cleanup
204 0         0 ${*$self}{httpsnb_connected} = 0;
  0         0  
205 0         0 return $self->SUPER::close();
206             }
207              
208             =head2 blocking($flag)
209              
210             As opposed to Net::HTTPS where blocking method consciously broken you
211             can set socket blocking. For example you can return socket to blocking state
212             after non-blocking connect.
213              
214             =cut
215              
216             sub blocking {
217             # blocking() is breaked in Net::HTTPS
218             # restore it here
219 2     2 1 5955500 my $self = shift;
220 2         40 $self->IO::Socket::blocking(@_);
221             }
222              
223             # code below copied from Net::HTTP::NB with some modifications
224             # Author: Gisle Aas
225              
226             sub sysread {
227 0     0 1   my $self = shift;
228 0 0         unless (${*$self}{'httpsnb_reading'}) {
  0            
229             # allow reading without restrictions when called
230             # not from our methods
231 0           return $self->SUPER::sysread(@_);
232             }
233            
234 0 0         if (${*$self}{'httpsnb_read_count'}++) {
  0            
235 0           ${*$self}{'http_buf'} = ${*$self}{'httpsnb_save'};
  0            
  0            
236 0           die "Multi-read\n";
237             }
238            
239 0   0       my $offset = $_[2] || 0;
240 0           my $n = $self->SUPER::sysread($_[0], $_[1], $offset);
241 0           ${*$self}{'httpsnb_save'} .= substr($_[0], $offset);
  0            
242 0           return $n;
243             }
244              
245             sub read_response_headers {
246 0     0 1   my $self = shift;
247 0           ${*$self}{'httpsnb_reading'} = 1;
  0            
248 0           ${*$self}{'httpsnb_read_count'} = 0;
  0            
249 0           ${*$self}{'httpsnb_save'} = ${*$self}{'http_buf'};
  0            
  0            
250 0           my @h = eval { $self->SUPER::read_response_headers(@_) };
  0            
251 0           ${*$self}{'httpsnb_reading'} = 0;
  0            
252 0 0         if ($@) {
253 0 0 0       return if $@ eq "Multi-read\n" || $HTTPS_ERROR == HTTPS_WANT_READ;
254 0           die;
255             }
256 0           return @h;
257             }
258              
259             sub read_entity_body {
260 0     0 1   my $self = shift;
261 0           ${*$self}{'httpsnb_reading'} = 1;
  0            
262 0           ${*$self}{'httpsnb_read_count'} = 0;
  0            
263 0           ${*$self}{'httpsnb_save'} = ${*$self}{'http_buf'};
  0            
  0            
264             # XXX I'm not so sure this does the correct thing in case of
265             # transfer-encoding tranforms
266 0           my $n = eval { $self->SUPER::read_entity_body(@_) };
  0            
267 0           ${*$self}{'httpsnb_reading'} = 0;
  0            
268 0 0 0       if ($@ || (!defined($n) && $HTTPS_ERROR == HTTPS_WANT_READ)) {
      0        
269 0           $_[0] = "";
270 0           return -1;
271             }
272 0           return $n;
273             }
274              
275             1;
276              
277             =head1 SEE ALSO
278              
279             L, L
280              
281             =head1 COPYRIGHT
282              
283             Copyright 2011-2013 Oleg G .
284              
285             This library is free software; you can redistribute it and/or
286             modify it under the same terms as Perl itself.
287              
288             =cut